dsh-tdai-memory 0.2.13 → 0.3.0

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 (72) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +15 -13
  3. package/README.zh.md +9 -7
  4. package/THIRD-PARTY-NOTICES.md +20 -20
  5. package/client.js +21 -4
  6. package/cordis.patch.yml +8 -8
  7. package/index.js +36 -14
  8. package/package.json +77 -78
  9. package/recall-inject.js +86 -86
  10. package/vendor/tdai/adapters/index.js +15 -15
  11. package/vendor/tdai/adapters/openclaw/host-adapter.js +83 -83
  12. package/vendor/tdai/adapters/openclaw/index.js +5 -5
  13. package/vendor/tdai/adapters/openclaw/llm-runner.js +69 -69
  14. package/vendor/tdai/adapters/standalone/host-adapter.js +58 -58
  15. package/vendor/tdai/adapters/standalone/index.js +5 -5
  16. package/vendor/tdai/adapters/standalone/llm-runner.js +34 -6
  17. package/vendor/tdai/config.js +3 -0
  18. package/vendor/tdai/core/conversation/l0-recorder.js +420 -420
  19. package/vendor/tdai/core/hooks/auto-capture.js +236 -236
  20. package/vendor/tdai/core/hooks/auto-recall.js +640 -640
  21. package/vendor/tdai/core/index.js +8 -8
  22. package/vendor/tdai/core/persona/persona-generator.js +184 -184
  23. package/vendor/tdai/core/persona/persona-trigger.js +94 -94
  24. package/vendor/tdai/core/profile/profile-sync.js +200 -200
  25. package/vendor/tdai/core/prompts/l1-dedup.js +72 -72
  26. package/vendor/tdai/core/prompts/l1-extraction.js +34 -34
  27. package/vendor/tdai/core/prompts/persona-generation.js +35 -35
  28. package/vendor/tdai/core/prompts/scene-extraction.js +45 -45
  29. package/vendor/tdai/core/record/l1-dedup.js +318 -318
  30. package/vendor/tdai/core/record/l1-extractor.js +379 -379
  31. package/vendor/tdai/core/record/l1-reader.js +178 -178
  32. package/vendor/tdai/core/record/l1-writer.js +141 -141
  33. package/vendor/tdai/core/report/reporter.js +86 -86
  34. package/vendor/tdai/core/scene/filename-normalizer.js +161 -161
  35. package/vendor/tdai/core/scene/scene-extractor.js +410 -410
  36. package/vendor/tdai/core/scene/scene-format.js +53 -53
  37. package/vendor/tdai/core/scene/scene-index.js +82 -82
  38. package/vendor/tdai/core/scene/scene-navigation.js +57 -57
  39. package/vendor/tdai/core/seed/input.js +404 -404
  40. package/vendor/tdai/core/seed/seed-runtime.js +4 -0
  41. package/vendor/tdai/core/seed/types.js +9 -9
  42. package/vendor/tdai/core/store/bm25-client.js +117 -117
  43. package/vendor/tdai/core/store/bm25-local.js +68 -68
  44. package/vendor/tdai/core/store/embedding.js +632 -632
  45. package/vendor/tdai/core/store/factory.js +95 -95
  46. package/vendor/tdai/core/store/search-utils.js +54 -54
  47. package/vendor/tdai/core/store/sqlite.js +1632 -1632
  48. package/vendor/tdai/core/store/tcvdb-client.js +221 -221
  49. package/vendor/tdai/core/store/tcvdb.js +1047 -1047
  50. package/vendor/tdai/core/store/types.js +17 -17
  51. package/vendor/tdai/core/tdai-core.js +4 -0
  52. package/vendor/tdai/core/tools/conversation-search.js +199 -199
  53. package/vendor/tdai/core/tools/memory-search.js +207 -207
  54. package/vendor/tdai/core/types.js +12 -12
  55. package/vendor/tdai/utils/backup.js +189 -189
  56. package/vendor/tdai/utils/checkpoint.js +360 -360
  57. package/vendor/tdai/utils/clean-context-runner.js +472 -472
  58. package/vendor/tdai/utils/ensure-hook-policy.js +213 -213
  59. package/vendor/tdai/utils/env.js +13 -13
  60. package/vendor/tdai/utils/managed-timer.js +127 -127
  61. package/vendor/tdai/utils/manifest.js +91 -91
  62. package/vendor/tdai/utils/memory-cleaner.js +300 -300
  63. package/vendor/tdai/utils/no-think-fetch.js +109 -109
  64. package/vendor/tdai/utils/openclaw-state-dir.js +26 -26
  65. package/vendor/tdai/utils/pipeline-factory.js +517 -517
  66. package/vendor/tdai/utils/pipeline-manager.js +892 -892
  67. package/vendor/tdai/utils/sanitize.js +360 -360
  68. package/vendor/tdai/utils/serial-queue.js +103 -103
  69. package/vendor/tdai/utils/session-filter.js +88 -88
  70. package/vendor/tdai/utils/session-header-fetch.js +88 -0
  71. package/vendor/tdai/utils/text-utils.js +29 -29
  72. package/vendor/tdai/utils/time.js +252 -252
@@ -1,189 +1,189 @@
1
- /**
2
- * BackupManager: generic file/directory backup utility.
3
- *
4
- * Provides two backup modes:
5
- * - `backupFile(src, category, tag, maxKeep)` — copy a single file
6
- * - `backupDirectory(src, category, tag, maxKeep)` — copy an entire directory
7
- *
8
- * All backups land under `<backupRoot>/<category>/` with timestamped names.
9
- * After each backup, entries beyond `maxKeep` are automatically pruned
10
- * (oldest first, by lexicographic order on the timestamp-embedded name).
11
- */
12
- import fs from "node:fs/promises";
13
- import path from "node:path";
14
- export class BackupManager {
15
- backupRoot;
16
- /**
17
- * @param backupRoot - Absolute path to the root backup directory
18
- * (e.g. `<dataDir>/.backup`).
19
- */
20
- constructor(backupRoot) {
21
- this.backupRoot = backupRoot;
22
- }
23
- /**
24
- * Backup a single file.
25
- *
26
- * Destination: `<backupRoot>/<category>/<category>_<timestamp>_<tag>.<ext>`
27
- *
28
- * @param srcFile - Absolute path to the source file
29
- * @param category - Logical grouping (e.g. "persona")
30
- * @param tag - Additional identifier (e.g. "offset42")
31
- * @param maxKeep - Max backup files to retain in this category (0 = unlimited)
32
- */
33
- async backupFile(srcFile, category, tag, maxKeep) {
34
- try {
35
- await fs.access(srcFile);
36
- }
37
- catch {
38
- return; // Source file doesn't exist, nothing to backup
39
- }
40
- const destDir = path.join(this.backupRoot, category);
41
- await fs.mkdir(destDir, { recursive: true });
42
- const ext = path.extname(srcFile); // e.g. ".md"
43
- const timestamp = formatTimestamp(new Date());
44
- const destName = `${category}_${timestamp}_${tag}${ext}`;
45
- await fs.copyFile(srcFile, path.join(destDir, destName));
46
- if (maxKeep > 0) {
47
- await pruneOldEntries(destDir, maxKeep, "file");
48
- }
49
- }
50
- /**
51
- * Backup an entire directory (shallow copy of all files).
52
- *
53
- * Destination: `<backupRoot>/<category>/<category>_<timestamp>_<tag>/`
54
- *
55
- * @param srcDir - Absolute path to the source directory
56
- * @param category - Logical grouping (e.g. "scene_blocks")
57
- * @param tag - Additional identifier (e.g. "offset42")
58
- * @param maxKeep - Max backup directories to retain in this category (0 = unlimited)
59
- */
60
- async backupDirectory(srcDir, category, tag, maxKeep) {
61
- let entries;
62
- try {
63
- entries = await fs.readdir(srcDir, { withFileTypes: true });
64
- }
65
- catch {
66
- return; // Source directory doesn't exist
67
- }
68
- // Only backup regular files (skip subdirectories to avoid EISDIR errors)
69
- const files = entries.filter((e) => e.isFile()).map((e) => e.name);
70
- if (files.length === 0)
71
- return;
72
- const parentDir = path.join(this.backupRoot, category);
73
- const timestamp = formatTimestamp(new Date());
74
- const destDir = path.join(parentDir, `${category}_${timestamp}_${tag}`);
75
- await fs.mkdir(destDir, { recursive: true });
76
- for (const file of files) {
77
- await fs.copyFile(path.join(srcDir, file), path.join(destDir, file));
78
- }
79
- if (maxKeep > 0) {
80
- await pruneOldEntries(parentDir, maxKeep, "directory");
81
- }
82
- }
83
- /**
84
- * Find the latest backup directory for a category.
85
- *
86
- * Backup directory names are `<category>_<timestamp>_<tag>` where the
87
- * timestamp is `YYYYMMDD_HHmmss` (lexicographic order = chronological order),
88
- * so the lexicographically largest entry is the most recent one.
89
- *
90
- * @param category - Logical grouping (e.g. "scene_blocks")
91
- * @returns Absolute path to the latest backup directory, or undefined if none.
92
- */
93
- async findLatestBackup(category) {
94
- const parentDir = path.join(this.backupRoot, category);
95
- let entries;
96
- try {
97
- entries = await fs.readdir(parentDir, { withFileTypes: true });
98
- }
99
- catch {
100
- return undefined; // No backup directory yet
101
- }
102
- const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
103
- if (dirs.length === 0)
104
- return undefined;
105
- dirs.sort(); // ascending — oldest first; last = newest
106
- return path.join(parentDir, dirs[dirs.length - 1]);
107
- }
108
- /**
109
- * Restore the latest backup of `category` into `destDir`.
110
- *
111
- * Strategy:
112
- * 1. Find the latest backup directory; if none exists, do nothing
113
- * (fail-soft: never clobber the destination when there is no
114
- * ground truth to restore from).
115
- * 2. Wipe `destDir` and recreate it.
116
- * 3. Copy every regular file from the backup directory into `destDir`.
117
- *
118
- * @param category - Logical grouping (e.g. "scene_blocks")
119
- * @param destDir - Absolute path to the directory to restore into
120
- * @returns `{ restored: true, from }` when a backup was applied,
121
- * `{ restored: false }` when no backup was found.
122
- * @throws Lets fs errors during wipe/copy propagate so callers can decide
123
- * whether to fail-soft (log) or fail-hard.
124
- */
125
- async restoreLatestDirectory(category, destDir) {
126
- const from = await this.findLatestBackup(category);
127
- if (!from)
128
- return { restored: false };
129
- // Wipe the destination first so any partial LLM writes are removed,
130
- // then recreate the directory and copy regular files back.
131
- await fs.rm(destDir, { recursive: true, force: true });
132
- await fs.mkdir(destDir, { recursive: true });
133
- const entries = await fs.readdir(from, { withFileTypes: true });
134
- for (const entry of entries) {
135
- if (!entry.isFile())
136
- continue;
137
- await fs.copyFile(path.join(from, entry.name), path.join(destDir, entry.name));
138
- }
139
- return { restored: true, from };
140
- }
141
- }
142
- // ============================
143
- // Helpers
144
- // ============================
145
- function formatTimestamp(d) {
146
- const pad = (n) => String(n).padStart(2, "0");
147
- return [
148
- d.getFullYear(),
149
- pad(d.getMonth() + 1),
150
- pad(d.getDate()),
151
- "_",
152
- pad(d.getHours()),
153
- pad(d.getMinutes()),
154
- pad(d.getSeconds()),
155
- ].join("");
156
- }
157
- /**
158
- * Keep only the newest `maxKeep` entries in a directory.
159
- * Entries are sorted by name ascending (oldest first) since backup names
160
- * embed timestamps, so lexicographic order = chronological order.
161
- *
162
- * @param dir - Directory containing the backup entries
163
- * @param maxKeep - Number of entries to retain
164
- * @param kind - "file" to unlink, "directory" to rm -rf
165
- */
166
- async function pruneOldEntries(dir, maxKeep, kind) {
167
- let entries;
168
- try {
169
- entries = await fs.readdir(dir);
170
- }
171
- catch {
172
- return;
173
- }
174
- entries.sort(); // ascending — oldest first
175
- const toRemove = entries.slice(0, Math.max(0, entries.length - maxKeep));
176
- for (const name of toRemove) {
177
- try {
178
- if (kind === "file") {
179
- await fs.unlink(path.join(dir, name));
180
- }
181
- else {
182
- await fs.rm(path.join(dir, name), { recursive: true, force: true });
183
- }
184
- }
185
- catch {
186
- // best-effort
187
- }
188
- }
189
- }
1
+ /**
2
+ * BackupManager: generic file/directory backup utility.
3
+ *
4
+ * Provides two backup modes:
5
+ * - `backupFile(src, category, tag, maxKeep)` — copy a single file
6
+ * - `backupDirectory(src, category, tag, maxKeep)` — copy an entire directory
7
+ *
8
+ * All backups land under `<backupRoot>/<category>/` with timestamped names.
9
+ * After each backup, entries beyond `maxKeep` are automatically pruned
10
+ * (oldest first, by lexicographic order on the timestamp-embedded name).
11
+ */
12
+ import fs from "node:fs/promises";
13
+ import path from "node:path";
14
+ export class BackupManager {
15
+ backupRoot;
16
+ /**
17
+ * @param backupRoot - Absolute path to the root backup directory
18
+ * (e.g. `<dataDir>/.backup`).
19
+ */
20
+ constructor(backupRoot) {
21
+ this.backupRoot = backupRoot;
22
+ }
23
+ /**
24
+ * Backup a single file.
25
+ *
26
+ * Destination: `<backupRoot>/<category>/<category>_<timestamp>_<tag>.<ext>`
27
+ *
28
+ * @param srcFile - Absolute path to the source file
29
+ * @param category - Logical grouping (e.g. "persona")
30
+ * @param tag - Additional identifier (e.g. "offset42")
31
+ * @param maxKeep - Max backup files to retain in this category (0 = unlimited)
32
+ */
33
+ async backupFile(srcFile, category, tag, maxKeep) {
34
+ try {
35
+ await fs.access(srcFile);
36
+ }
37
+ catch {
38
+ return; // Source file doesn't exist, nothing to backup
39
+ }
40
+ const destDir = path.join(this.backupRoot, category);
41
+ await fs.mkdir(destDir, { recursive: true });
42
+ const ext = path.extname(srcFile); // e.g. ".md"
43
+ const timestamp = formatTimestamp(new Date());
44
+ const destName = `${category}_${timestamp}_${tag}${ext}`;
45
+ await fs.copyFile(srcFile, path.join(destDir, destName));
46
+ if (maxKeep > 0) {
47
+ await pruneOldEntries(destDir, maxKeep, "file");
48
+ }
49
+ }
50
+ /**
51
+ * Backup an entire directory (shallow copy of all files).
52
+ *
53
+ * Destination: `<backupRoot>/<category>/<category>_<timestamp>_<tag>/`
54
+ *
55
+ * @param srcDir - Absolute path to the source directory
56
+ * @param category - Logical grouping (e.g. "scene_blocks")
57
+ * @param tag - Additional identifier (e.g. "offset42")
58
+ * @param maxKeep - Max backup directories to retain in this category (0 = unlimited)
59
+ */
60
+ async backupDirectory(srcDir, category, tag, maxKeep) {
61
+ let entries;
62
+ try {
63
+ entries = await fs.readdir(srcDir, { withFileTypes: true });
64
+ }
65
+ catch {
66
+ return; // Source directory doesn't exist
67
+ }
68
+ // Only backup regular files (skip subdirectories to avoid EISDIR errors)
69
+ const files = entries.filter((e) => e.isFile()).map((e) => e.name);
70
+ if (files.length === 0)
71
+ return;
72
+ const parentDir = path.join(this.backupRoot, category);
73
+ const timestamp = formatTimestamp(new Date());
74
+ const destDir = path.join(parentDir, `${category}_${timestamp}_${tag}`);
75
+ await fs.mkdir(destDir, { recursive: true });
76
+ for (const file of files) {
77
+ await fs.copyFile(path.join(srcDir, file), path.join(destDir, file));
78
+ }
79
+ if (maxKeep > 0) {
80
+ await pruneOldEntries(parentDir, maxKeep, "directory");
81
+ }
82
+ }
83
+ /**
84
+ * Find the latest backup directory for a category.
85
+ *
86
+ * Backup directory names are `<category>_<timestamp>_<tag>` where the
87
+ * timestamp is `YYYYMMDD_HHmmss` (lexicographic order = chronological order),
88
+ * so the lexicographically largest entry is the most recent one.
89
+ *
90
+ * @param category - Logical grouping (e.g. "scene_blocks")
91
+ * @returns Absolute path to the latest backup directory, or undefined if none.
92
+ */
93
+ async findLatestBackup(category) {
94
+ const parentDir = path.join(this.backupRoot, category);
95
+ let entries;
96
+ try {
97
+ entries = await fs.readdir(parentDir, { withFileTypes: true });
98
+ }
99
+ catch {
100
+ return undefined; // No backup directory yet
101
+ }
102
+ const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
103
+ if (dirs.length === 0)
104
+ return undefined;
105
+ dirs.sort(); // ascending — oldest first; last = newest
106
+ return path.join(parentDir, dirs[dirs.length - 1]);
107
+ }
108
+ /**
109
+ * Restore the latest backup of `category` into `destDir`.
110
+ *
111
+ * Strategy:
112
+ * 1. Find the latest backup directory; if none exists, do nothing
113
+ * (fail-soft: never clobber the destination when there is no
114
+ * ground truth to restore from).
115
+ * 2. Wipe `destDir` and recreate it.
116
+ * 3. Copy every regular file from the backup directory into `destDir`.
117
+ *
118
+ * @param category - Logical grouping (e.g. "scene_blocks")
119
+ * @param destDir - Absolute path to the directory to restore into
120
+ * @returns `{ restored: true, from }` when a backup was applied,
121
+ * `{ restored: false }` when no backup was found.
122
+ * @throws Lets fs errors during wipe/copy propagate so callers can decide
123
+ * whether to fail-soft (log) or fail-hard.
124
+ */
125
+ async restoreLatestDirectory(category, destDir) {
126
+ const from = await this.findLatestBackup(category);
127
+ if (!from)
128
+ return { restored: false };
129
+ // Wipe the destination first so any partial LLM writes are removed,
130
+ // then recreate the directory and copy regular files back.
131
+ await fs.rm(destDir, { recursive: true, force: true });
132
+ await fs.mkdir(destDir, { recursive: true });
133
+ const entries = await fs.readdir(from, { withFileTypes: true });
134
+ for (const entry of entries) {
135
+ if (!entry.isFile())
136
+ continue;
137
+ await fs.copyFile(path.join(from, entry.name), path.join(destDir, entry.name));
138
+ }
139
+ return { restored: true, from };
140
+ }
141
+ }
142
+ // ============================
143
+ // Helpers
144
+ // ============================
145
+ function formatTimestamp(d) {
146
+ const pad = (n) => String(n).padStart(2, "0");
147
+ return [
148
+ d.getFullYear(),
149
+ pad(d.getMonth() + 1),
150
+ pad(d.getDate()),
151
+ "_",
152
+ pad(d.getHours()),
153
+ pad(d.getMinutes()),
154
+ pad(d.getSeconds()),
155
+ ].join("");
156
+ }
157
+ /**
158
+ * Keep only the newest `maxKeep` entries in a directory.
159
+ * Entries are sorted by name ascending (oldest first) since backup names
160
+ * embed timestamps, so lexicographic order = chronological order.
161
+ *
162
+ * @param dir - Directory containing the backup entries
163
+ * @param maxKeep - Number of entries to retain
164
+ * @param kind - "file" to unlink, "directory" to rm -rf
165
+ */
166
+ async function pruneOldEntries(dir, maxKeep, kind) {
167
+ let entries;
168
+ try {
169
+ entries = await fs.readdir(dir);
170
+ }
171
+ catch {
172
+ return;
173
+ }
174
+ entries.sort(); // ascending — oldest first
175
+ const toRemove = entries.slice(0, Math.max(0, entries.length - maxKeep));
176
+ for (const name of toRemove) {
177
+ try {
178
+ if (kind === "file") {
179
+ await fs.unlink(path.join(dir, name));
180
+ }
181
+ else {
182
+ await fs.rm(path.join(dir, name), { recursive: true, force: true });
183
+ }
184
+ }
185
+ catch {
186
+ // best-effort
187
+ }
188
+ }
189
+ }