teleton 0.8.4 → 0.8.6

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 (84) hide show
  1. package/README.md +40 -17
  2. package/dist/{bootstrap-NNEI3Z5H.js → bootstrap-PFBH6ALD.js} +11 -8
  3. package/dist/bridge-guards-HZTNH7IB.js +9 -0
  4. package/dist/{chunk-NH2CNRKJ.js → chunk-2UUGRY5B.js} +151 -159
  5. package/dist/{chunk-UMUONAD6.js → chunk-4MFN75ZK.js} +5941 -2716
  6. package/dist/{chunk-LC4TV3KL.js → chunk-4MG2AROG.js} +5 -7
  7. package/dist/{chunk-LZQOX6YY.js → chunk-6IFNQWIM.js} +7714 -8748
  8. package/dist/chunk-7KI25UJU.js +215 -0
  9. package/dist/chunk-AX5NBEHX.js +12 -0
  10. package/dist/{chunk-5LOHRZYY.js → chunk-BLUES3FJ.js} +80 -101
  11. package/dist/{chunk-CUE4UZXR.js → chunk-BT2I3ETV.js} +3 -3
  12. package/dist/chunk-CXTZPOTA.js +107 -0
  13. package/dist/{chunk-LVTKJQ7O.js → chunk-D3GT6YIY.js} +59 -7
  14. package/dist/chunk-EKCXKL5M.js +53 -0
  15. package/dist/{chunk-XDZDOKIF.js → chunk-F6S3L3OV.js} +3 -3
  16. package/dist/{chunk-C4NKJT2Z.js → chunk-J4WDJ7XS.js} +1 -1
  17. package/dist/{chunk-G7PCW63M.js → chunk-JYF2MM5I.js} +147 -113
  18. package/dist/{chunk-NVKBBTI6.js → chunk-K3QSIIMZ.js} +9 -6
  19. package/dist/{chunk-EYWNOHMJ.js → chunk-L653KKCR.js} +1 -0
  20. package/dist/chunk-OMQIAWEU.js +273 -0
  21. package/dist/chunk-PCT7GYBP.js +274 -0
  22. package/dist/chunk-QYZBWU2D.js +139 -0
  23. package/dist/{chunk-WTDAICGT.js → chunk-R6W4DJRK.js} +7 -7
  24. package/dist/{chunk-5SEMA47R.js → chunk-RILOEIK6.js} +1 -1
  25. package/dist/{chunk-6OOHHJ4N.js → chunk-TFTNZZDH.js} +20 -20
  26. package/dist/chunk-TTOZCZWE.js +96 -0
  27. package/dist/chunk-UJ54YT2T.js +12 -0
  28. package/dist/{chunk-GHMXWAXI.js → chunk-ULVL2W3D.js} +211 -445
  29. package/dist/{chunk-NQ6FZKCE.js → chunk-V3S3NXBQ.js} +3 -1
  30. package/dist/{chunk-H7MFXJZK.js → chunk-WSL4KIOI.js} +31 -26
  31. package/dist/{chunk-35MX4ZUI.js → chunk-Z5WY7BSB.js} +5 -5
  32. package/dist/{chunk-ALKAAG4O.js → chunk-ZGKE3OTA.js} +112 -49
  33. package/dist/{chunk-JROBTXWY.js → chunk-ZHRDETCX.js} +38 -4
  34. package/dist/cli/index.d.ts +2 -0
  35. package/dist/cli/index.js +272 -159
  36. package/dist/{client-5KD25NOP.js → client-S5UIK6OG.js} +10 -8
  37. package/dist/daily-logs-3WXGYAQF.js +25 -0
  38. package/dist/{get-my-gifts-Y7EN7RK4.js → get-my-gifts-3YSYM3LI.js} +3 -2
  39. package/dist/harden-permissions-PV5SGV5D.js +100 -0
  40. package/dist/index.d.ts +923 -0
  41. package/dist/index.js +29 -20
  42. package/dist/knowledge-RRWUIO3G.js +19 -0
  43. package/dist/{local-IHKJFQJS.js → local-MSZAXWUL.js} +3 -3
  44. package/dist/mcp-loader-OELDFR63.js +15 -0
  45. package/dist/{memory-QMJRM3XJ.js → memory-6U6HGRK2.js} +23 -12
  46. package/dist/memory-hook-T7Y235KY.js +19 -0
  47. package/dist/messages-KV5ADNJB.js +17 -0
  48. package/dist/{migrate-5VBAP52B.js → migrate-AX3HOKOO.js} +10 -7
  49. package/dist/{paths-XA2RJH4S.js → paths-WMVV7ZAJ.js} +1 -1
  50. package/dist/{server-WWGVDFPW.js → server-MFRYOGHR.js} +21 -23
  51. package/dist/{server-AJCOURH7.js → server-SFLCAZFR.js} +221 -27
  52. package/dist/{setup-server-VDY64CWW.js → setup-server-YWAPKZVE.js} +26 -26
  53. package/dist/{store-BY7S6IFN.js → store-PGHQASBC.js} +11 -8
  54. package/dist/{task-dependency-resolver-L6UUMTHK.js → task-dependency-resolver-YQKADDEU.js} +24 -10
  55. package/dist/{task-executor-XBNJLUCS.js → task-executor-LWAWD225.js} +4 -4
  56. package/dist/{tool-adapter-IVX2XQJE.js → tool-adapter-VKLUZSQS.js} +1 -1
  57. package/dist/{tool-index-FTERJSZK.js → tool-index-YEWDF5CK.js} +5 -5
  58. package/dist/{transcript-IM7G25OS.js → transcript-4Y3Z2BJ3.js} +3 -3
  59. package/dist/web/assets/Config-MNxA69ib.js +1 -0
  60. package/dist/web/assets/Conversations-Dk958paA.js +1 -0
  61. package/dist/web/assets/Dashboard-dM18fGOm.js +1 -0
  62. package/dist/web/assets/Hooks-D2griQnI.js +1 -0
  63. package/dist/web/assets/Mcp-CtWNzwsz.js +1 -0
  64. package/dist/web/assets/Memory-CfLwH45G.js +1 -0
  65. package/dist/web/assets/Plugins-3hoJprFo.js +1 -0
  66. package/dist/web/assets/SearchInput-CpcETdpE.js +1 -0
  67. package/dist/web/assets/Soul-BSxE73aK.js +1 -0
  68. package/dist/web/assets/Tasks-DkCkfu3A.js +1 -0
  69. package/dist/web/assets/TelegramSettingsPanel-BRzc5G6e.js +1 -0
  70. package/dist/web/assets/Tools-Du8B8Mb4.js +1 -0
  71. package/dist/web/assets/Wallet-BLILP2Gn.js +1 -0
  72. package/dist/web/assets/Workspace-qklcXpXV.js +1 -0
  73. package/dist/web/assets/index-BwEPTTKp.js +90 -0
  74. package/dist/web/assets/index-noejUsK7.css +1 -0
  75. package/dist/web/assets/{index.es-DitvF-9H.js → index.es-DdpKlnGb.js} +1 -1
  76. package/dist/web/assets/useToolManager-tdxkKn3H.js +1 -0
  77. package/dist/web/assets/utils-CnsbSMo4.js +1 -0
  78. package/dist/web/index.html +2 -2
  79. package/package.json +7 -12
  80. package/src/templates/HEARTBEAT.md +5 -0
  81. package/dist/memory-hook-VUNWZ3NY.js +0 -19
  82. package/dist/web/assets/index-BfYCdwLI.js +0 -80
  83. package/dist/web/assets/index-DmlyQVhR.css +0 -1
  84. package/dist/{chunk-WFTC3JJW.js → chunk-3NO7QU7W.js} +1 -1
@@ -0,0 +1,273 @@
1
+ import {
2
+ hashText,
3
+ serializeEmbedding
4
+ } from "./chunk-Z5WY7BSB.js";
5
+ import {
6
+ KNOWLEDGE_CHUNK_SIZE
7
+ } from "./chunk-J4WDJ7XS.js";
8
+
9
+ // src/memory/agent/knowledge.ts
10
+ import { readFileSync, existsSync, readdirSync, statSync } from "fs";
11
+ import { join } from "path";
12
+ var _instance = null;
13
+ function setKnowledgeIndexer(indexer) {
14
+ _instance = indexer;
15
+ }
16
+ function getKnowledgeIndexer() {
17
+ return _instance;
18
+ }
19
+ var KnowledgeIndexer = class {
20
+ constructor(db, workspaceDir, embedder, vectorEnabled) {
21
+ this.db = db;
22
+ this.workspaceDir = workspaceDir;
23
+ this.embedder = embedder;
24
+ this.vectorEnabled = vectorEnabled;
25
+ }
26
+ getEmbedder() {
27
+ return this.embedder;
28
+ }
29
+ async indexAll(options) {
30
+ const files = this.listMemoryFiles();
31
+ let indexed = 0;
32
+ let skipped = 0;
33
+ for (const file of files) {
34
+ const wasIndexed = await this.indexFile(file, options?.force);
35
+ if (wasIndexed) {
36
+ indexed++;
37
+ } else {
38
+ skipped++;
39
+ }
40
+ }
41
+ return { indexed, skipped };
42
+ }
43
+ async indexFile(absPath, force) {
44
+ if (!existsSync(absPath) || !absPath.endsWith(".md")) {
45
+ return false;
46
+ }
47
+ const content = readFileSync(absPath, "utf-8");
48
+ const relPath = absPath.replace(this.workspaceDir + "/", "");
49
+ const fileHash = hashText(content);
50
+ if (!force) {
51
+ const existing = this.db.prepare(`SELECT hash FROM knowledge WHERE path = ? AND source = 'memory' LIMIT 1`).get(relPath);
52
+ if (existing?.hash === fileHash) {
53
+ return false;
54
+ }
55
+ }
56
+ const chunks = this.chunkMarkdown(content, relPath);
57
+ const texts = chunks.map((c) => c.text);
58
+ const embeddings = await this.embedder.embedBatch(texts);
59
+ const chunksToInsert = [];
60
+ const idsToSupersede = [];
61
+ if (this.vectorEnabled) {
62
+ const vecQuery = this.db.prepare(`
63
+ SELECT kv.id, kv.distance
64
+ FROM (
65
+ SELECT id, distance
66
+ FROM knowledge_vec
67
+ WHERE embedding MATCH ? AND k = 5
68
+ ) kv
69
+ JOIN knowledge k ON k.id = kv.id
70
+ WHERE k.path != ? AND (k.status = 'active' OR k.status IS NULL)
71
+ ORDER BY kv.distance
72
+ `);
73
+ for (let i = 0; i < chunks.length; i++) {
74
+ const chunk = chunks[i];
75
+ const embedding = embeddings[i] ?? [];
76
+ if (embedding.length === 0) {
77
+ chunksToInsert.push({ chunk, embedding });
78
+ continue;
79
+ }
80
+ let skip = false;
81
+ try {
82
+ const similar = vecQuery.all(serializeEmbedding(embedding), relPath);
83
+ const exactDup = similar.find((r) => r.distance < 0.1);
84
+ if (exactDup) {
85
+ skip = true;
86
+ } else {
87
+ const nearDup = similar.find((r) => r.distance < 0.15);
88
+ if (nearDup) {
89
+ idsToSupersede.push(nearDup.id);
90
+ }
91
+ }
92
+ } catch {
93
+ }
94
+ if (!skip) {
95
+ chunksToInsert.push({ chunk, embedding });
96
+ }
97
+ }
98
+ } else {
99
+ for (let i = 0; i < chunks.length; i++) {
100
+ chunksToInsert.push({ chunk: chunks[i], embedding: embeddings[i] ?? [] });
101
+ }
102
+ }
103
+ this.db.transaction(() => {
104
+ if (this.vectorEnabled) {
105
+ this.db.prepare(
106
+ `DELETE FROM knowledge_vec WHERE id IN (
107
+ SELECT id FROM knowledge WHERE path = ? AND source = 'memory'
108
+ )`
109
+ ).run(relPath);
110
+ }
111
+ this.db.prepare(`DELETE FROM knowledge WHERE path = ? AND source = 'memory'`).run(relPath);
112
+ if (idsToSupersede.length > 0) {
113
+ const placeholders = idsToSupersede.map(() => "?").join(", ");
114
+ this.db.prepare(
115
+ `UPDATE knowledge SET status = 'superseded', updated_at = unixepoch() WHERE id IN (${placeholders})`
116
+ ).run(...idsToSupersede);
117
+ }
118
+ const insert = this.db.prepare(`
119
+ INSERT INTO knowledge (id, source, path, text, embedding, start_line, end_line, hash, status, memory_type)
120
+ VALUES (?, 'memory', ?, ?, ?, ?, ?, ?, 'active', ?)
121
+ `);
122
+ const insertVec = this.vectorEnabled ? this.db.prepare(`INSERT INTO knowledge_vec (id, embedding) VALUES (?, ?)`) : null;
123
+ const memoryType = this.getMemoryType(relPath);
124
+ for (const { chunk, embedding } of chunksToInsert) {
125
+ insert.run(
126
+ chunk.id,
127
+ chunk.path,
128
+ chunk.text,
129
+ serializeEmbedding(embedding),
130
+ chunk.startLine,
131
+ chunk.endLine,
132
+ fileHash,
133
+ memoryType
134
+ );
135
+ if (insertVec && embedding.length > 0) {
136
+ insertVec.run(chunk.id, serializeEmbedding(embedding));
137
+ }
138
+ }
139
+ })();
140
+ return true;
141
+ }
142
+ async pruneOrphans() {
143
+ const paths = this.db.prepare(
144
+ `SELECT DISTINCT path FROM knowledge WHERE path IS NOT NULL AND source = 'memory'
145
+ AND (status = 'active' OR status IS NULL)`
146
+ ).all();
147
+ let markedInactive = 0;
148
+ const orphanedPaths = [];
149
+ for (const { path } of paths) {
150
+ const absPath = join(this.workspaceDir, path);
151
+ const archivedPath = join(
152
+ this.workspaceDir,
153
+ "memory",
154
+ "archived",
155
+ path.replace(/^memory\//, "")
156
+ );
157
+ if (!existsSync(absPath) && !existsSync(archivedPath)) {
158
+ orphanedPaths.push(path);
159
+ }
160
+ }
161
+ if (orphanedPaths.length > 0) {
162
+ const ph = orphanedPaths.map(() => "?").join(", ");
163
+ const result = this.db.prepare(
164
+ `UPDATE knowledge SET status = 'inactive', updated_at = unixepoch()
165
+ WHERE path IN (${ph}) AND source = 'memory'`
166
+ ).run(...orphanedPaths);
167
+ markedInactive = result.changes;
168
+ }
169
+ const staleIds = this.db.prepare(
170
+ `SELECT id FROM knowledge WHERE status = 'inactive' AND updated_at < unixepoch() - ?`
171
+ ).all(30 * 86400);
172
+ let deleted = 0;
173
+ if (staleIds.length > 0) {
174
+ const ids = staleIds.map((r) => r.id);
175
+ const placeholders = ids.map(() => "?").join(", ");
176
+ this.db.transaction(() => {
177
+ if (this.vectorEnabled) {
178
+ this.db.prepare(`DELETE FROM knowledge_vec WHERE id IN (${placeholders})`).run(...ids);
179
+ }
180
+ this.db.prepare(`DELETE FROM knowledge WHERE id IN (${placeholders})`).run(...ids);
181
+ })();
182
+ deleted = ids.length;
183
+ }
184
+ return { markedInactive, deleted };
185
+ }
186
+ listMemoryFiles() {
187
+ const files = [];
188
+ const memoryMd = join(this.workspaceDir, "MEMORY.md");
189
+ if (existsSync(memoryMd)) {
190
+ files.push(memoryMd);
191
+ }
192
+ const memoryDir = join(this.workspaceDir, "memory");
193
+ if (existsSync(memoryDir)) {
194
+ const entries = readdirSync(memoryDir);
195
+ for (const entry of entries) {
196
+ const absPath = join(memoryDir, entry);
197
+ if (statSync(absPath).isFile() && entry.endsWith(".md")) {
198
+ files.push(absPath);
199
+ }
200
+ }
201
+ }
202
+ return files;
203
+ }
204
+ getMemoryType(relPath) {
205
+ if (relPath === "MEMORY.md") return "procedural";
206
+ if (/^memory\/(\d{4}-\d{2}-\d{2}|consolidated-)/.test(relPath)) return "episodic";
207
+ return "semantic";
208
+ }
209
+ /**
210
+ * Chunk markdown content with structure awareness.
211
+ * Respects heading boundaries, code blocks, and list groups.
212
+ * Target: KNOWLEDGE_CHUNK_SIZE chars, hard max: 2x target.
213
+ */
214
+ chunkMarkdown(content, path) {
215
+ const lines = content.split("\n");
216
+ const chunks = [];
217
+ const targetSize = KNOWLEDGE_CHUNK_SIZE;
218
+ const hardMax = targetSize * 2;
219
+ let currentChunk = "";
220
+ let startLine = 1;
221
+ let currentLine = 1;
222
+ let inCodeBlock = false;
223
+ let overlapPrefix = "";
224
+ const flushChunk = () => {
225
+ const text = currentChunk.trim();
226
+ if (text.length > 0) {
227
+ chunks.push({
228
+ id: hashText(`${path}:${startLine}:${currentLine - 1}`),
229
+ source: "memory",
230
+ path,
231
+ text,
232
+ startLine,
233
+ endLine: currentLine - 1,
234
+ hash: hashText(text)
235
+ });
236
+ const nonEmpty = text.split("\n").filter((l) => l.trim());
237
+ overlapPrefix = nonEmpty.length > 0 ? nonEmpty.slice(-2).join("\n") + "\n" : "";
238
+ }
239
+ currentChunk = overlapPrefix;
240
+ startLine = currentLine;
241
+ };
242
+ for (const line of lines) {
243
+ if (line.trimStart().startsWith("```")) {
244
+ inCodeBlock = !inCodeBlock;
245
+ }
246
+ if (!inCodeBlock && currentChunk.length >= targetSize) {
247
+ const isHeading = /^#{1,6}\s/.test(line);
248
+ const isBlankLine = line.trim() === "";
249
+ const isHorizontalRule = /^(-{3,}|\*{3,}|_{3,})\s*$/.test(line.trim());
250
+ if (isHeading) {
251
+ flushChunk();
252
+ } else if ((isBlankLine || isHorizontalRule) && currentChunk.length >= targetSize) {
253
+ currentChunk += line + "\n";
254
+ currentLine++;
255
+ flushChunk();
256
+ continue;
257
+ } else if (currentChunk.length >= hardMax) {
258
+ flushChunk();
259
+ }
260
+ }
261
+ currentChunk += line + "\n";
262
+ currentLine++;
263
+ }
264
+ flushChunk();
265
+ return chunks;
266
+ }
267
+ };
268
+
269
+ export {
270
+ setKnowledgeIndexer,
271
+ getKnowledgeIndexer,
272
+ KnowledgeIndexer
273
+ };
@@ -0,0 +1,274 @@
1
+ import {
2
+ ALLOWED_EXTENSIONS,
3
+ WORKSPACE_PATHS,
4
+ WORKSPACE_ROOT
5
+ } from "./chunk-L653KKCR.js";
6
+ import {
7
+ createLogger
8
+ } from "./chunk-V3S3NXBQ.js";
9
+
10
+ // src/memory/daily-logs.ts
11
+ import { existsSync as existsSync2, mkdirSync, appendFileSync, readFileSync, readdirSync as readdirSync2, unlinkSync } from "fs";
12
+ import { join } from "path";
13
+
14
+ // src/workspace/validator.ts
15
+ import { existsSync, lstatSync, readdirSync } from "fs";
16
+ import { resolve, normalize, relative, extname, basename } from "path";
17
+ import { homedir } from "os";
18
+ var WorkspaceSecurityError = class extends Error {
19
+ constructor(message, attemptedPath) {
20
+ super(message);
21
+ this.attemptedPath = attemptedPath;
22
+ this.name = "WorkspaceSecurityError";
23
+ }
24
+ };
25
+ function decodeRecursive(str) {
26
+ let decoded = str;
27
+ let prev = "";
28
+ let iterations = 0;
29
+ const maxIterations = 10;
30
+ while (decoded !== prev && iterations < maxIterations) {
31
+ prev = decoded;
32
+ try {
33
+ decoded = decodeURIComponent(decoded);
34
+ } catch {
35
+ break;
36
+ }
37
+ iterations++;
38
+ }
39
+ return decoded;
40
+ }
41
+ function validatePath(inputPath, allowCreate = false) {
42
+ if (!inputPath || inputPath.trim() === "") {
43
+ throw new WorkspaceSecurityError("Path cannot be empty.", inputPath);
44
+ }
45
+ const trimmedPath = inputPath.trim().replace(/\\/g, "/");
46
+ const decodedPath = decodeRecursive(trimmedPath);
47
+ let absolutePath;
48
+ if (decodedPath.startsWith("/")) {
49
+ absolutePath = resolve(normalize(decodedPath));
50
+ } else if (decodedPath.startsWith("~/")) {
51
+ const expanded = decodedPath.replace(/^~(?=$|[\\/])/, homedir());
52
+ absolutePath = resolve(expanded);
53
+ } else {
54
+ absolutePath = resolve(WORKSPACE_ROOT, normalize(decodedPath));
55
+ }
56
+ const relativePath = relative(WORKSPACE_ROOT, absolutePath);
57
+ if (relativePath.startsWith("..") || relativePath.startsWith("/")) {
58
+ throw new WorkspaceSecurityError(
59
+ `Access denied: Path '${inputPath}' is outside the workspace. Only files in ~/.teleton/workspace/ are accessible.`,
60
+ inputPath
61
+ );
62
+ }
63
+ const exists = existsSync(absolutePath);
64
+ if (!exists && !allowCreate) {
65
+ throw new WorkspaceSecurityError(
66
+ `File not found: '${inputPath}' does not exist in workspace.`,
67
+ inputPath
68
+ );
69
+ }
70
+ if (exists) {
71
+ const stats = lstatSync(absolutePath);
72
+ if (stats.isSymbolicLink()) {
73
+ throw new WorkspaceSecurityError(
74
+ `Access denied: Symbolic links are not allowed for security reasons.`,
75
+ inputPath
76
+ );
77
+ }
78
+ }
79
+ return {
80
+ absolutePath,
81
+ relativePath,
82
+ exists,
83
+ isDirectory: exists ? lstatSync(absolutePath).isDirectory() : false,
84
+ extension: extname(absolutePath).toLowerCase(),
85
+ filename: basename(absolutePath)
86
+ };
87
+ }
88
+ function validateReadPath(inputPath) {
89
+ const validated = validatePath(inputPath, false);
90
+ if (validated.isDirectory) {
91
+ throw new WorkspaceSecurityError(`Cannot read directory as file: '${inputPath}'`, inputPath);
92
+ }
93
+ return validated;
94
+ }
95
+ var IMMUTABLE_FILES = ["SOUL.md", "STRATEGY.md", "SECURITY.md"];
96
+ function validateWritePath(inputPath, fileType) {
97
+ const validated = validatePath(inputPath, true);
98
+ if (IMMUTABLE_FILES.includes(validated.filename)) {
99
+ throw new WorkspaceSecurityError(
100
+ `Cannot write to ${validated.filename}. This file is configured by the owner. Use memory_write instead.`,
101
+ inputPath
102
+ );
103
+ }
104
+ if (fileType && ALLOWED_EXTENSIONS[fileType]) {
105
+ const allowedExts = ALLOWED_EXTENSIONS[fileType];
106
+ if (!allowedExts.includes(validated.extension)) {
107
+ throw new WorkspaceSecurityError(
108
+ `Invalid file type: '${validated.extension}' is not allowed for ${fileType}. Allowed: ${allowedExts.join(", ")}`,
109
+ inputPath
110
+ );
111
+ }
112
+ }
113
+ return validated;
114
+ }
115
+ function validateDirectory(inputPath) {
116
+ const validated = validatePath(inputPath, true);
117
+ if (validated.exists && !validated.isDirectory) {
118
+ throw new WorkspaceSecurityError(
119
+ `Path exists but is not a directory: '${inputPath}'`,
120
+ inputPath
121
+ );
122
+ }
123
+ return validated;
124
+ }
125
+
126
+ // src/memory/daily-logs.ts
127
+ var log = createLogger("Memory");
128
+ var MEMORY_DIR = WORKSPACE_PATHS.MEMORY_DIR;
129
+ function formatDate(date) {
130
+ const year = date.getFullYear();
131
+ const month = String(date.getMonth() + 1).padStart(2, "0");
132
+ const day = String(date.getDate()).padStart(2, "0");
133
+ return `${year}-${month}-${day}`;
134
+ }
135
+ function getDailyLogPath(date = /* @__PURE__ */ new Date()) {
136
+ return join(MEMORY_DIR, `${formatDate(date)}.md`);
137
+ }
138
+ function ensureMemoryDir() {
139
+ if (!existsSync2(MEMORY_DIR)) {
140
+ mkdirSync(MEMORY_DIR, { recursive: true });
141
+ }
142
+ }
143
+ function appendToDailyLog(content, date = /* @__PURE__ */ new Date()) {
144
+ try {
145
+ ensureMemoryDir();
146
+ const logPath = getDailyLogPath(date);
147
+ const timestamp = date.toLocaleTimeString("en-US", { hour12: false });
148
+ if (!existsSync2(logPath)) {
149
+ const header = `# Daily Log - ${formatDate(date)}
150
+
151
+ `;
152
+ appendFileSync(logPath, header, { encoding: "utf-8", mode: 384 });
153
+ }
154
+ const entry = `## ${timestamp}
155
+
156
+ ${content}
157
+
158
+ ---
159
+
160
+ `;
161
+ appendFileSync(logPath, entry, "utf-8");
162
+ log.info(`Daily log updated: ${logPath}`);
163
+ } catch (error) {
164
+ log.error({ err: error }, "Failed to write daily log");
165
+ }
166
+ }
167
+ function readDailyLog(date = /* @__PURE__ */ new Date()) {
168
+ try {
169
+ const logPath = getDailyLogPath(date);
170
+ if (!existsSync2(logPath)) return null;
171
+ return readFileSync(logPath, "utf-8");
172
+ } catch {
173
+ return null;
174
+ }
175
+ }
176
+ var DAILY_LOG_LINE_LIMIT = 100;
177
+ function truncateDailyLog(content) {
178
+ const lines = content.split("\n");
179
+ if (lines.length <= DAILY_LOG_LINE_LIMIT) return content;
180
+ const truncated = lines.slice(-DAILY_LOG_LINE_LIMIT).join("\n");
181
+ const dropped = lines.length - DAILY_LOG_LINE_LIMIT;
182
+ return `_[... ${dropped} earlier lines omitted]_
183
+
184
+ ${truncated}`;
185
+ }
186
+ function readRecentMemory() {
187
+ const today = /* @__PURE__ */ new Date();
188
+ const yesterday = new Date(today);
189
+ yesterday.setDate(yesterday.getDate() - 1);
190
+ const parts = [];
191
+ const yesterdayLog = readDailyLog(yesterday);
192
+ if (yesterdayLog) {
193
+ parts.push(`## Yesterday (${formatDate(yesterday)})
194
+
195
+ ${truncateDailyLog(yesterdayLog)}`);
196
+ }
197
+ const todayLog = readDailyLog(today);
198
+ if (todayLog) {
199
+ parts.push(`## Today (${formatDate(today)})
200
+
201
+ ${truncateDailyLog(todayLog)}`);
202
+ }
203
+ if (parts.length === 0) {
204
+ return null;
205
+ }
206
+ return `# Recent Memory
207
+
208
+ ${parts.join("\n\n---\n\n")}`;
209
+ }
210
+ function writeSessionEndSummary(summary, reason) {
211
+ const content = `### Session End (${reason})
212
+
213
+ ${summary}`;
214
+ appendToDailyLog(content);
215
+ }
216
+ function writeSummaryToDailyLog(summary) {
217
+ appendToDailyLog(`### Memory Flush (Pre-Compaction)
218
+
219
+ ${summary}`);
220
+ }
221
+ function cleanupOldDailyLogs(maxAgeDays = 60) {
222
+ if (!existsSync2(MEMORY_DIR)) return 0;
223
+ const cutoffMs = Date.now() - maxAgeDays * 864e5;
224
+ const datePattern = /^(\d{4}-\d{2}-\d{2})\.md$/;
225
+ let deleted = 0;
226
+ try {
227
+ const files = readdirSync2(MEMORY_DIR);
228
+ for (const file of files) {
229
+ const match = datePattern.exec(file);
230
+ if (!match) continue;
231
+ const fileDate = new Date(match[1]).getTime();
232
+ if (Number.isNaN(fileDate)) continue;
233
+ if (fileDate < cutoffMs) {
234
+ try {
235
+ unlinkSync(join(MEMORY_DIR, file));
236
+ deleted++;
237
+ } catch (innerError) {
238
+ log.warn({ err: innerError, file }, "Failed to delete old daily log");
239
+ }
240
+ }
241
+ }
242
+ } catch (error) {
243
+ log.error({ err: error }, "Failed to list memory directory for cleanup");
244
+ }
245
+ if (deleted > 0) {
246
+ log.info(`Cleaned up ${deleted} daily log file(s) older than ${maxAgeDays} days`);
247
+ }
248
+ return deleted;
249
+ }
250
+ function writeConversationMilestone(chatId, topic, details) {
251
+ const content = `### Conversation Milestone
252
+
253
+ **Chat**: ${chatId}
254
+ **Topic**: ${topic}
255
+
256
+ ${details}`;
257
+ appendToDailyLog(content);
258
+ }
259
+
260
+ export {
261
+ WorkspaceSecurityError,
262
+ validatePath,
263
+ validateReadPath,
264
+ validateWritePath,
265
+ validateDirectory,
266
+ getDailyLogPath,
267
+ appendToDailyLog,
268
+ readDailyLog,
269
+ readRecentMemory,
270
+ writeSessionEndSummary,
271
+ writeSummaryToDailyLog,
272
+ cleanupOldDailyLogs,
273
+ writeConversationMilestone
274
+ };
@@ -0,0 +1,139 @@
1
+ import {
2
+ TELETON_ROOT
3
+ } from "./chunk-L653KKCR.js";
4
+ import {
5
+ createLogger
6
+ } from "./chunk-V3S3NXBQ.js";
7
+
8
+ // src/utils/module-db.ts
9
+ import Database from "better-sqlite3";
10
+ import { existsSync, mkdirSync, chmodSync } from "fs";
11
+ import { dirname, join } from "path";
12
+ var log = createLogger("Utils");
13
+ var JOURNAL_SCHEMA = `
14
+ CREATE TABLE IF NOT EXISTS journal (
15
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
16
+ timestamp INTEGER NOT NULL DEFAULT (unixepoch()),
17
+ type TEXT NOT NULL CHECK(type IN ('trade', 'gift', 'middleman', 'kol')),
18
+ action TEXT NOT NULL,
19
+ asset_from TEXT,
20
+ asset_to TEXT,
21
+ amount_from REAL,
22
+ amount_to REAL,
23
+ price_ton REAL,
24
+ counterparty TEXT,
25
+ platform TEXT,
26
+ reasoning TEXT,
27
+ outcome TEXT CHECK(outcome IN ('pending', 'profit', 'loss', 'neutral', 'cancelled')),
28
+ pnl_ton REAL,
29
+ pnl_pct REAL,
30
+ tx_hash TEXT,
31
+ tool_used TEXT,
32
+ chat_id TEXT,
33
+ user_id INTEGER,
34
+ closed_at INTEGER,
35
+ created_at INTEGER NOT NULL DEFAULT (unixepoch())
36
+ );
37
+
38
+ CREATE INDEX IF NOT EXISTS idx_journal_type ON journal(type);
39
+ CREATE INDEX IF NOT EXISTS idx_journal_timestamp ON journal(timestamp DESC);
40
+ CREATE INDEX IF NOT EXISTS idx_journal_asset_from ON journal(asset_from);
41
+ CREATE INDEX IF NOT EXISTS idx_journal_outcome ON journal(outcome);
42
+ CREATE INDEX IF NOT EXISTS idx_journal_type_timestamp ON journal(type, timestamp DESC);
43
+ `;
44
+ var USED_TRANSACTIONS_SCHEMA = `
45
+ CREATE TABLE IF NOT EXISTS used_transactions (
46
+ tx_hash TEXT PRIMARY KEY,
47
+ user_id TEXT NOT NULL,
48
+ amount REAL NOT NULL,
49
+ game_type TEXT NOT NULL,
50
+ used_at INTEGER NOT NULL DEFAULT (unixepoch())
51
+ );
52
+
53
+ CREATE INDEX IF NOT EXISTS idx_used_tx_user ON used_transactions(user_id);
54
+ CREATE INDEX IF NOT EXISTS idx_used_tx_used_at ON used_transactions(used_at);
55
+ `;
56
+ function openModuleDb(path) {
57
+ const dir = dirname(path);
58
+ if (!existsSync(dir)) {
59
+ mkdirSync(dir, { recursive: true });
60
+ }
61
+ const db = new Database(path);
62
+ try {
63
+ chmodSync(path, 384);
64
+ } catch {
65
+ }
66
+ db.pragma("journal_mode = WAL");
67
+ return db;
68
+ }
69
+ function createDbWrapper(getDb, moduleName) {
70
+ return function withDb(executor) {
71
+ return (params, context) => {
72
+ const moduleDb = getDb();
73
+ if (!moduleDb) {
74
+ return Promise.resolve({
75
+ success: false,
76
+ error: `${moduleName} module not started`
77
+ });
78
+ }
79
+ return executor(params, { ...context, db: moduleDb });
80
+ };
81
+ };
82
+ }
83
+ var MAIN_DB_PATH = join(TELETON_ROOT, "memory.db");
84
+ function migrateFromMainDb(moduleDb, tables) {
85
+ let totalMigrated = 0;
86
+ for (const table of tables) {
87
+ if (!/^[a-z_]+$/.test(table)) {
88
+ throw new Error(`Invalid table name for migration: "${table}"`);
89
+ }
90
+ }
91
+ for (const table of tables) {
92
+ try {
93
+ const row = moduleDb.prepare(`SELECT COUNT(*) as c FROM ${table}`).get();
94
+ if (row.c > 0) return 0;
95
+ } catch {
96
+ continue;
97
+ }
98
+ }
99
+ if (!existsSync(MAIN_DB_PATH)) return 0;
100
+ try {
101
+ moduleDb.exec(`ATTACH DATABASE '${MAIN_DB_PATH}' AS main_db`);
102
+ for (const table of tables) {
103
+ try {
104
+ const exists = moduleDb.prepare(`SELECT name FROM main_db.sqlite_master WHERE type='table' AND name=?`).get(table);
105
+ if (!exists) continue;
106
+ const src = moduleDb.prepare(`SELECT COUNT(*) as c FROM main_db.${table}`).get();
107
+ if (src.c === 0) continue;
108
+ const dstCols = moduleDb.prepare(`PRAGMA table_info(${table})`).all().map((r) => r.name);
109
+ const srcCols = moduleDb.prepare(`PRAGMA main_db.table_info(${table})`).all().map((r) => r.name);
110
+ const shared = dstCols.filter((c) => srcCols.includes(c));
111
+ if (shared.length === 0) continue;
112
+ const cols = shared.join(", ");
113
+ moduleDb.exec(
114
+ `INSERT OR IGNORE INTO ${table} (${cols}) SELECT ${cols} FROM main_db.${table}`
115
+ );
116
+ totalMigrated += src.c;
117
+ log.info(`Migrated ${src.c} rows from memory.db \u2192 ${table}`);
118
+ } catch (innerError) {
119
+ log.warn({ err: innerError }, `Could not migrate table ${table}`);
120
+ }
121
+ }
122
+ moduleDb.exec(`DETACH DATABASE main_db`);
123
+ } catch (error) {
124
+ log.warn({ err: error }, `Migration from memory.db failed`);
125
+ try {
126
+ moduleDb.exec(`DETACH DATABASE main_db`);
127
+ } catch {
128
+ }
129
+ }
130
+ return totalMigrated;
131
+ }
132
+
133
+ export {
134
+ JOURNAL_SCHEMA,
135
+ USED_TRANSACTIONS_SCHEMA,
136
+ openModuleDb,
137
+ createDbWrapper,
138
+ migrateFromMainDb
139
+ };