dsh-tdai-memory 0.2.13 → 0.2.14

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.
Files changed (69) hide show
  1. package/LICENSE +21 -21
  2. package/THIRD-PARTY-NOTICES.md +20 -20
  3. package/client.js +8 -4
  4. package/cordis.patch.yml +8 -8
  5. package/index.js +414 -414
  6. package/package.json +1 -1
  7. package/recall-inject.js +86 -86
  8. package/vendor/tdai/adapters/index.js +15 -15
  9. package/vendor/tdai/adapters/openclaw/host-adapter.js +83 -83
  10. package/vendor/tdai/adapters/openclaw/index.js +5 -5
  11. package/vendor/tdai/adapters/openclaw/llm-runner.js +69 -69
  12. package/vendor/tdai/adapters/standalone/host-adapter.js +58 -58
  13. package/vendor/tdai/adapters/standalone/index.js +5 -5
  14. package/vendor/tdai/adapters/standalone/llm-runner.js +246 -246
  15. package/vendor/tdai/config.js +363 -363
  16. package/vendor/tdai/core/conversation/l0-recorder.js +420 -420
  17. package/vendor/tdai/core/hooks/auto-capture.js +236 -236
  18. package/vendor/tdai/core/hooks/auto-recall.js +653 -653
  19. package/vendor/tdai/core/index.js +8 -8
  20. package/vendor/tdai/core/persona/persona-generator.js +184 -184
  21. package/vendor/tdai/core/persona/persona-trigger.js +94 -94
  22. package/vendor/tdai/core/profile/profile-sync.js +200 -200
  23. package/vendor/tdai/core/prompts/l1-dedup.js +136 -136
  24. package/vendor/tdai/core/prompts/l1-extraction.js +132 -132
  25. package/vendor/tdai/core/prompts/persona-generation.js +160 -160
  26. package/vendor/tdai/core/prompts/scene-extraction.js +269 -269
  27. package/vendor/tdai/core/record/l1-dedup.js +318 -318
  28. package/vendor/tdai/core/record/l1-extractor.js +379 -379
  29. package/vendor/tdai/core/record/l1-reader.js +178 -178
  30. package/vendor/tdai/core/record/l1-writer.js +141 -141
  31. package/vendor/tdai/core/report/reporter.js +86 -86
  32. package/vendor/tdai/core/scene/filename-normalizer.js +161 -161
  33. package/vendor/tdai/core/scene/scene-extractor.js +410 -410
  34. package/vendor/tdai/core/scene/scene-format.js +53 -53
  35. package/vendor/tdai/core/scene/scene-index.js +82 -82
  36. package/vendor/tdai/core/scene/scene-navigation.js +60 -60
  37. package/vendor/tdai/core/seed/input.js +404 -404
  38. package/vendor/tdai/core/seed/seed-runtime.js +314 -314
  39. package/vendor/tdai/core/seed/types.js +9 -9
  40. package/vendor/tdai/core/store/bm25-client.js +117 -117
  41. package/vendor/tdai/core/store/bm25-local.js +68 -68
  42. package/vendor/tdai/core/store/embedding.js +632 -632
  43. package/vendor/tdai/core/store/factory.js +95 -95
  44. package/vendor/tdai/core/store/search-utils.js +54 -54
  45. package/vendor/tdai/core/store/sqlite.js +1822 -1822
  46. package/vendor/tdai/core/store/tcvdb-client.js +221 -221
  47. package/vendor/tdai/core/store/tcvdb.js +1047 -1047
  48. package/vendor/tdai/core/store/types.js +17 -17
  49. package/vendor/tdai/core/tdai-core.js +446 -446
  50. package/vendor/tdai/core/tools/conversation-search.js +199 -199
  51. package/vendor/tdai/core/tools/memory-search.js +207 -207
  52. package/vendor/tdai/core/types.js +12 -12
  53. package/vendor/tdai/utils/backup.js +189 -189
  54. package/vendor/tdai/utils/checkpoint.js +360 -360
  55. package/vendor/tdai/utils/clean-context-runner.js +472 -472
  56. package/vendor/tdai/utils/ensure-hook-policy.js +213 -213
  57. package/vendor/tdai/utils/env.js +13 -13
  58. package/vendor/tdai/utils/managed-timer.js +127 -127
  59. package/vendor/tdai/utils/manifest.js +91 -91
  60. package/vendor/tdai/utils/memory-cleaner.js +300 -300
  61. package/vendor/tdai/utils/no-think-fetch.js +109 -109
  62. package/vendor/tdai/utils/openclaw-state-dir.js +26 -26
  63. package/vendor/tdai/utils/pipeline-factory.js +517 -517
  64. package/vendor/tdai/utils/pipeline-manager.js +892 -892
  65. package/vendor/tdai/utils/sanitize.js +360 -360
  66. package/vendor/tdai/utils/serial-queue.js +103 -103
  67. package/vendor/tdai/utils/session-filter.js +88 -88
  68. package/vendor/tdai/utils/text-utils.js +29 -29
  69. package/vendor/tdai/utils/time.js +252 -252
@@ -1,103 +1,103 @@
1
- /**
2
- * SerialQueue: a lightweight task queue with concurrency=1.
3
- *
4
- * Equivalent to `new PQueue({ concurrency: 1 })` but with zero external
5
- * dependencies. Supports:
6
- * - Serial execution (FIFO)
7
- * - `add(fn)` to enqueue a task (returns the task's result promise)
8
- * - `onIdle()` to wait until all queued tasks have completed
9
- * - `pause()` / `start()` to suspend/resume execution
10
- * - `size` to check pending task count
11
- * - Optional debug logger for enqueue/dequeue/complete diagnostics
12
- */
13
- export class SerialQueue {
14
- /** Human-readable name for logging / diagnostics. */
15
- name;
16
- queue = [];
17
- running = false;
18
- paused = false;
19
- idleResolvers = [];
20
- /** Optional debug logger — receives diagnostic messages for enqueue/dequeue/complete. */
21
- debugFn;
22
- constructor(name = "unnamed") {
23
- this.name = name;
24
- }
25
- /** Set a debug logger for queue diagnostics. */
26
- setDebugLogger(fn) {
27
- this.debugFn = fn;
28
- }
29
- /** Number of tasks waiting to be executed. */
30
- get size() {
31
- return this.queue.length;
32
- }
33
- /** Whether a task is currently executing. */
34
- get pending() {
35
- return this.running;
36
- }
37
- /** Whether the queue is idle (no queued tasks and nothing running). */
38
- get idle() {
39
- return this.queue.length === 0 && !this.running;
40
- }
41
- /** Add a task to the queue. Returns the task's result promise. */
42
- add(task) {
43
- return new Promise((resolve, reject) => {
44
- this.queue.push({
45
- task: task,
46
- resolve: resolve,
47
- reject,
48
- });
49
- this.debugFn?.(`[queue:${this.name}] enqueued, pending=${this.queue.length}, running=${this.running}`);
50
- this.drain();
51
- });
52
- }
53
- /** Pause the queue. Currently running task will finish, but no new tasks start. */
54
- pause() {
55
- this.paused = true;
56
- }
57
- /** Resume the queue after pause(). */
58
- start() {
59
- this.paused = false;
60
- this.drain();
61
- }
62
- /** Returns a promise that resolves when all queued tasks have completed. */
63
- onIdle() {
64
- if (this.queue.length === 0 && !this.running) {
65
- return Promise.resolve();
66
- }
67
- return new Promise((resolve) => {
68
- this.idleResolvers.push(resolve);
69
- });
70
- }
71
- /** Clear all pending (not yet started) tasks. */
72
- clear() {
73
- for (const entry of this.queue) {
74
- entry.reject(new Error("Queue cleared"));
75
- }
76
- this.queue = [];
77
- }
78
- drain() {
79
- if (this.running || this.paused || this.queue.length === 0)
80
- return;
81
- const entry = this.queue.shift();
82
- this.running = true;
83
- this.debugFn?.(`[queue:${this.name}] dequeued, starting execution (remaining=${this.queue.length})`);
84
- entry
85
- .task()
86
- .then((result) => entry.resolve(result))
87
- .catch((err) => entry.reject(err))
88
- .finally(() => {
89
- this.running = false;
90
- this.debugFn?.(`[queue:${this.name}] task completed (remaining=${this.queue.length})`);
91
- if (this.queue.length === 0) {
92
- // Notify idle waiters
93
- const resolvers = this.idleResolvers;
94
- this.idleResolvers = [];
95
- for (const resolve of resolvers)
96
- resolve();
97
- }
98
- else {
99
- this.drain();
100
- }
101
- });
102
- }
103
- }
1
+ /**
2
+ * SerialQueue: a lightweight task queue with concurrency=1.
3
+ *
4
+ * Equivalent to `new PQueue({ concurrency: 1 })` but with zero external
5
+ * dependencies. Supports:
6
+ * - Serial execution (FIFO)
7
+ * - `add(fn)` to enqueue a task (returns the task's result promise)
8
+ * - `onIdle()` to wait until all queued tasks have completed
9
+ * - `pause()` / `start()` to suspend/resume execution
10
+ * - `size` to check pending task count
11
+ * - Optional debug logger for enqueue/dequeue/complete diagnostics
12
+ */
13
+ export class SerialQueue {
14
+ /** Human-readable name for logging / diagnostics. */
15
+ name;
16
+ queue = [];
17
+ running = false;
18
+ paused = false;
19
+ idleResolvers = [];
20
+ /** Optional debug logger — receives diagnostic messages for enqueue/dequeue/complete. */
21
+ debugFn;
22
+ constructor(name = "unnamed") {
23
+ this.name = name;
24
+ }
25
+ /** Set a debug logger for queue diagnostics. */
26
+ setDebugLogger(fn) {
27
+ this.debugFn = fn;
28
+ }
29
+ /** Number of tasks waiting to be executed. */
30
+ get size() {
31
+ return this.queue.length;
32
+ }
33
+ /** Whether a task is currently executing. */
34
+ get pending() {
35
+ return this.running;
36
+ }
37
+ /** Whether the queue is idle (no queued tasks and nothing running). */
38
+ get idle() {
39
+ return this.queue.length === 0 && !this.running;
40
+ }
41
+ /** Add a task to the queue. Returns the task's result promise. */
42
+ add(task) {
43
+ return new Promise((resolve, reject) => {
44
+ this.queue.push({
45
+ task: task,
46
+ resolve: resolve,
47
+ reject,
48
+ });
49
+ this.debugFn?.(`[queue:${this.name}] enqueued, pending=${this.queue.length}, running=${this.running}`);
50
+ this.drain();
51
+ });
52
+ }
53
+ /** Pause the queue. Currently running task will finish, but no new tasks start. */
54
+ pause() {
55
+ this.paused = true;
56
+ }
57
+ /** Resume the queue after pause(). */
58
+ start() {
59
+ this.paused = false;
60
+ this.drain();
61
+ }
62
+ /** Returns a promise that resolves when all queued tasks have completed. */
63
+ onIdle() {
64
+ if (this.queue.length === 0 && !this.running) {
65
+ return Promise.resolve();
66
+ }
67
+ return new Promise((resolve) => {
68
+ this.idleResolvers.push(resolve);
69
+ });
70
+ }
71
+ /** Clear all pending (not yet started) tasks. */
72
+ clear() {
73
+ for (const entry of this.queue) {
74
+ entry.reject(new Error("Queue cleared"));
75
+ }
76
+ this.queue = [];
77
+ }
78
+ drain() {
79
+ if (this.running || this.paused || this.queue.length === 0)
80
+ return;
81
+ const entry = this.queue.shift();
82
+ this.running = true;
83
+ this.debugFn?.(`[queue:${this.name}] dequeued, starting execution (remaining=${this.queue.length})`);
84
+ entry
85
+ .task()
86
+ .then((result) => entry.resolve(result))
87
+ .catch((err) => entry.reject(err))
88
+ .finally(() => {
89
+ this.running = false;
90
+ this.debugFn?.(`[queue:${this.name}] task completed (remaining=${this.queue.length})`);
91
+ if (this.queue.length === 0) {
92
+ // Notify idle waiters
93
+ const resolvers = this.idleResolvers;
94
+ this.idleResolvers = [];
95
+ for (const resolve of resolvers)
96
+ resolve();
97
+ }
98
+ else {
99
+ this.drain();
100
+ }
101
+ });
102
+ }
103
+ }
@@ -1,88 +1,88 @@
1
- /**
2
- * Session filtering for memory-tdai.
3
- *
4
- * Decides whether a session should be ignored by the memory plugin
5
- * (capture, recall, pipeline scheduling). All skip rules are compiled
6
- * into a flat list of matchers at construction time — zero per-call overhead.
7
- */
8
- // ============================
9
- // Non-interactive trigger detection
10
- // ============================
11
- const SKIP_TRIGGERS = new Set(["cron", "heartbeat", "automation", "schedule"]);
12
- /**
13
- * Returns true when the hook was fired by a non-interactive trigger
14
- * (heartbeat, cron job, automation, etc.) — these produce no meaningful
15
- * user conversation and should not be captured or counted.
16
- */
17
- export function isNonInteractiveTrigger(trigger, sessionKey) {
18
- if (trigger && SKIP_TRIGGERS.has(trigger.toLowerCase()))
19
- return true;
20
- if (sessionKey) {
21
- if (/:cron:/i.test(sessionKey) || /:heartbeat:/i.test(sessionKey))
22
- return true;
23
- }
24
- return false;
25
- }
26
- // ============================
27
- // Built-in skip rules (always active)
28
- // ============================
29
- /**
30
- * Hard-coded matchers that identify internal / non-user sessions.
31
- * These are always applied regardless of user configuration.
32
- */
33
- const BUILTIN_MATCHERS = [
34
- // Scene extraction runner sessions
35
- (key) => key.includes(":memory-scene-extract-"),
36
- // OpenClaw subagent sessions
37
- (key) => key.includes(":subagent:"),
38
- // Temporary / internal utility sessions (e.g. temp:slug-generator)
39
- (key) => key.startsWith("temp:"),
40
- ];
41
- // ============================
42
- // Glob → matcher compiler
43
- // ============================
44
- /**
45
- * Turn a simple glob pattern (only `*` supported) into a matcher
46
- * that tests the full sessionKey.
47
- *
48
- * Since sessionKeys look like `agent:<agentId>:...`, we match the
49
- * glob against the whole key so users can write patterns like
50
- * `bench-judge-*` (matched anywhere) or more specific ones.
51
- */
52
- function globToMatcher(pattern) {
53
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
54
- const re = new RegExp(escaped);
55
- return (key) => re.test(key);
56
- }
57
- // ============================
58
- // SessionFilter
59
- // ============================
60
- /**
61
- * Unified filter: construct once at plugin startup, then call
62
- * `shouldSkip(sessionKey)` or `shouldSkipCtx(ctx)` at each gate.
63
- */
64
- export class SessionFilter {
65
- matchers;
66
- constructor(excludeAgents = []) {
67
- // Merge built-in rules + user-configured exclude patterns into one flat list
68
- const userMatchers = excludeAgents
69
- .map((p) => p.trim())
70
- .filter((p) => p.length > 0)
71
- .map(globToMatcher);
72
- this.matchers = [...BUILTIN_MATCHERS, ...userMatchers];
73
- }
74
- /** Should this sessionKey be skipped? */
75
- shouldSkip(sessionKey) {
76
- return this.matchers.some((m) => m(sessionKey));
77
- }
78
- /** Should this hook context be skipped? */
79
- shouldSkipCtx(ctx) {
80
- if (!ctx.sessionKey)
81
- return true;
82
- if (ctx.sessionId?.startsWith("memory-"))
83
- return true;
84
- if (isNonInteractiveTrigger(ctx.trigger, ctx.sessionKey))
85
- return true;
86
- return this.shouldSkip(ctx.sessionKey);
87
- }
88
- }
1
+ /**
2
+ * Session filtering for memory-tdai.
3
+ *
4
+ * Decides whether a session should be ignored by the memory plugin
5
+ * (capture, recall, pipeline scheduling). All skip rules are compiled
6
+ * into a flat list of matchers at construction time — zero per-call overhead.
7
+ */
8
+ // ============================
9
+ // Non-interactive trigger detection
10
+ // ============================
11
+ const SKIP_TRIGGERS = new Set(["cron", "heartbeat", "automation", "schedule"]);
12
+ /**
13
+ * Returns true when the hook was fired by a non-interactive trigger
14
+ * (heartbeat, cron job, automation, etc.) — these produce no meaningful
15
+ * user conversation and should not be captured or counted.
16
+ */
17
+ export function isNonInteractiveTrigger(trigger, sessionKey) {
18
+ if (trigger && SKIP_TRIGGERS.has(trigger.toLowerCase()))
19
+ return true;
20
+ if (sessionKey) {
21
+ if (/:cron:/i.test(sessionKey) || /:heartbeat:/i.test(sessionKey))
22
+ return true;
23
+ }
24
+ return false;
25
+ }
26
+ // ============================
27
+ // Built-in skip rules (always active)
28
+ // ============================
29
+ /**
30
+ * Hard-coded matchers that identify internal / non-user sessions.
31
+ * These are always applied regardless of user configuration.
32
+ */
33
+ const BUILTIN_MATCHERS = [
34
+ // Scene extraction runner sessions
35
+ (key) => key.includes(":memory-scene-extract-"),
36
+ // OpenClaw subagent sessions
37
+ (key) => key.includes(":subagent:"),
38
+ // Temporary / internal utility sessions (e.g. temp:slug-generator)
39
+ (key) => key.startsWith("temp:"),
40
+ ];
41
+ // ============================
42
+ // Glob → matcher compiler
43
+ // ============================
44
+ /**
45
+ * Turn a simple glob pattern (only `*` supported) into a matcher
46
+ * that tests the full sessionKey.
47
+ *
48
+ * Since sessionKeys look like `agent:<agentId>:...`, we match the
49
+ * glob against the whole key so users can write patterns like
50
+ * `bench-judge-*` (matched anywhere) or more specific ones.
51
+ */
52
+ function globToMatcher(pattern) {
53
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
54
+ const re = new RegExp(escaped);
55
+ return (key) => re.test(key);
56
+ }
57
+ // ============================
58
+ // SessionFilter
59
+ // ============================
60
+ /**
61
+ * Unified filter: construct once at plugin startup, then call
62
+ * `shouldSkip(sessionKey)` or `shouldSkipCtx(ctx)` at each gate.
63
+ */
64
+ export class SessionFilter {
65
+ matchers;
66
+ constructor(excludeAgents = []) {
67
+ // Merge built-in rules + user-configured exclude patterns into one flat list
68
+ const userMatchers = excludeAgents
69
+ .map((p) => p.trim())
70
+ .filter((p) => p.length > 0)
71
+ .map(globToMatcher);
72
+ this.matchers = [...BUILTIN_MATCHERS, ...userMatchers];
73
+ }
74
+ /** Should this sessionKey be skipped? */
75
+ shouldSkip(sessionKey) {
76
+ return this.matchers.some((m) => m(sessionKey));
77
+ }
78
+ /** Should this hook context be skipped? */
79
+ shouldSkipCtx(ctx) {
80
+ if (!ctx.sessionKey)
81
+ return true;
82
+ if (ctx.sessionId?.startsWith("memory-"))
83
+ return true;
84
+ if (isNonInteractiveTrigger(ctx.trigger, ctx.sessionKey))
85
+ return true;
86
+ return this.shouldSkip(ctx.sessionKey);
87
+ }
88
+ }
@@ -1,29 +1,29 @@
1
- /**
2
- * Shared text utility functions for the memory-tdai plugin.
3
- */
4
- /**
5
- * Extract meaningful words from text (supports CJK and Latin).
6
- *
7
- * Used by both auto-recall (keyword search) and l1-dedup (keyword candidate recall).
8
- * Extracted to a shared module to prevent implementation drift.
9
- */
10
- export function extractWords(text) {
11
- const words = new Set();
12
- // Latin words (2+ chars)
13
- const latinWords = text.toLowerCase().match(/[a-z0-9]{2,}/g);
14
- if (latinWords) {
15
- for (const w of latinWords)
16
- words.add(w);
17
- }
18
- // CJK characters (each char as a "word", plus 2-gram)
19
- const cjkChars = text.match(/[\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]/g);
20
- if (cjkChars) {
21
- for (const c of cjkChars)
22
- words.add(c);
23
- // 2-grams for better matching
24
- for (let i = 0; i < cjkChars.length - 1; i++) {
25
- words.add(cjkChars[i] + cjkChars[i + 1]);
26
- }
27
- }
28
- return words;
29
- }
1
+ /**
2
+ * Shared text utility functions for the memory-tdai plugin.
3
+ */
4
+ /**
5
+ * Extract meaningful words from text (supports CJK and Latin).
6
+ *
7
+ * Used by both auto-recall (keyword search) and l1-dedup (keyword candidate recall).
8
+ * Extracted to a shared module to prevent implementation drift.
9
+ */
10
+ export function extractWords(text) {
11
+ const words = new Set();
12
+ // Latin words (2+ chars)
13
+ const latinWords = text.toLowerCase().match(/[a-z0-9]{2,}/g);
14
+ if (latinWords) {
15
+ for (const w of latinWords)
16
+ words.add(w);
17
+ }
18
+ // CJK characters (each char as a "word", plus 2-gram)
19
+ const cjkChars = text.match(/[\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]/g);
20
+ if (cjkChars) {
21
+ for (const c of cjkChars)
22
+ words.add(c);
23
+ // 2-grams for better matching
24
+ for (let i = 0; i < cjkChars.length - 1; i++) {
25
+ words.add(cjkChars[i] + cjkChars[i + 1]);
26
+ }
27
+ }
28
+ return words;
29
+ }