skyloom 1.4.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 (225) hide show
  1. package/.github/workflows/ci.yml +36 -0
  2. package/CONVERSION_PLAN.md +191 -0
  3. package/README.md +67 -0
  4. package/dist/agents/dew.d.ts +15 -0
  5. package/dist/agents/dew.d.ts.map +1 -0
  6. package/dist/agents/dew.js +74 -0
  7. package/dist/agents/dew.js.map +1 -0
  8. package/dist/agents/fair.d.ts +15 -0
  9. package/dist/agents/fair.d.ts.map +1 -0
  10. package/dist/agents/fair.js +106 -0
  11. package/dist/agents/fair.js.map +1 -0
  12. package/dist/agents/fog.d.ts +15 -0
  13. package/dist/agents/fog.d.ts.map +1 -0
  14. package/dist/agents/fog.js +52 -0
  15. package/dist/agents/fog.js.map +1 -0
  16. package/dist/agents/frost.d.ts +15 -0
  17. package/dist/agents/frost.d.ts.map +1 -0
  18. package/dist/agents/frost.js +54 -0
  19. package/dist/agents/frost.js.map +1 -0
  20. package/dist/agents/rain.d.ts +15 -0
  21. package/dist/agents/rain.d.ts.map +1 -0
  22. package/dist/agents/rain.js +54 -0
  23. package/dist/agents/rain.js.map +1 -0
  24. package/dist/agents/snow.d.ts +27 -0
  25. package/dist/agents/snow.d.ts.map +1 -0
  26. package/dist/agents/snow.js +226 -0
  27. package/dist/agents/snow.js.map +1 -0
  28. package/dist/cli/main.d.ts +7 -0
  29. package/dist/cli/main.d.ts.map +1 -0
  30. package/dist/cli/main.js +402 -0
  31. package/dist/cli/main.js.map +1 -0
  32. package/dist/cli/mode.d.ts +17 -0
  33. package/dist/cli/mode.d.ts.map +1 -0
  34. package/dist/cli/mode.js +56 -0
  35. package/dist/cli/mode.js.map +1 -0
  36. package/dist/core/agent.d.ts +174 -0
  37. package/dist/core/agent.d.ts.map +1 -0
  38. package/dist/core/agent.js +1332 -0
  39. package/dist/core/agent.js.map +1 -0
  40. package/dist/core/agent_helpers.d.ts +51 -0
  41. package/dist/core/agent_helpers.d.ts.map +1 -0
  42. package/dist/core/agent_helpers.js +477 -0
  43. package/dist/core/agent_helpers.js.map +1 -0
  44. package/dist/core/bus.d.ts +99 -0
  45. package/dist/core/bus.d.ts.map +1 -0
  46. package/dist/core/bus.js +191 -0
  47. package/dist/core/bus.js.map +1 -0
  48. package/dist/core/cache.d.ts +63 -0
  49. package/dist/core/cache.d.ts.map +1 -0
  50. package/dist/core/cache.js +121 -0
  51. package/dist/core/cache.js.map +1 -0
  52. package/dist/core/checkpoint.d.ts +19 -0
  53. package/dist/core/checkpoint.d.ts.map +1 -0
  54. package/dist/core/checkpoint.js +120 -0
  55. package/dist/core/checkpoint.js.map +1 -0
  56. package/dist/core/circuit_breaker.d.ts +46 -0
  57. package/dist/core/circuit_breaker.d.ts.map +1 -0
  58. package/dist/core/circuit_breaker.js +99 -0
  59. package/dist/core/circuit_breaker.js.map +1 -0
  60. package/dist/core/config.d.ts +97 -0
  61. package/dist/core/config.d.ts.map +1 -0
  62. package/dist/core/config.js +281 -0
  63. package/dist/core/config.js.map +1 -0
  64. package/dist/core/constants.d.ts +78 -0
  65. package/dist/core/constants.d.ts.map +1 -0
  66. package/dist/core/constants.js +84 -0
  67. package/dist/core/constants.js.map +1 -0
  68. package/dist/core/factory.d.ts +63 -0
  69. package/dist/core/factory.d.ts.map +1 -0
  70. package/dist/core/factory.js +537 -0
  71. package/dist/core/factory.js.map +1 -0
  72. package/dist/core/icons.d.ts +28 -0
  73. package/dist/core/icons.d.ts.map +1 -0
  74. package/dist/core/icons.js +86 -0
  75. package/dist/core/icons.js.map +1 -0
  76. package/dist/core/index.d.ts +29 -0
  77. package/dist/core/index.d.ts.map +1 -0
  78. package/dist/core/index.js +54 -0
  79. package/dist/core/index.js.map +1 -0
  80. package/dist/core/llm.d.ts +121 -0
  81. package/dist/core/llm.d.ts.map +1 -0
  82. package/dist/core/llm.js +532 -0
  83. package/dist/core/llm.js.map +1 -0
  84. package/dist/core/logger.d.ts +57 -0
  85. package/dist/core/logger.d.ts.map +1 -0
  86. package/dist/core/logger.js +122 -0
  87. package/dist/core/logger.js.map +1 -0
  88. package/dist/core/mcp.d.ts +190 -0
  89. package/dist/core/mcp.d.ts.map +1 -0
  90. package/dist/core/mcp.js +822 -0
  91. package/dist/core/mcp.js.map +1 -0
  92. package/dist/core/mcp_server.d.ts +26 -0
  93. package/dist/core/mcp_server.d.ts.map +1 -0
  94. package/dist/core/mcp_server.js +211 -0
  95. package/dist/core/mcp_server.js.map +1 -0
  96. package/dist/core/memory.d.ts +190 -0
  97. package/dist/core/memory.d.ts.map +1 -0
  98. package/dist/core/memory.js +988 -0
  99. package/dist/core/memory.js.map +1 -0
  100. package/dist/core/middleware.d.ts +114 -0
  101. package/dist/core/middleware.d.ts.map +1 -0
  102. package/dist/core/middleware.js +248 -0
  103. package/dist/core/middleware.js.map +1 -0
  104. package/dist/core/pipelines.d.ts +87 -0
  105. package/dist/core/pipelines.d.ts.map +1 -0
  106. package/dist/core/pipelines.js +301 -0
  107. package/dist/core/pipelines.js.map +1 -0
  108. package/dist/core/profile.d.ts +23 -0
  109. package/dist/core/profile.d.ts.map +1 -0
  110. package/dist/core/profile.js +289 -0
  111. package/dist/core/profile.js.map +1 -0
  112. package/dist/core/router.d.ts +24 -0
  113. package/dist/core/router.d.ts.map +1 -0
  114. package/dist/core/router.js +111 -0
  115. package/dist/core/router.js.map +1 -0
  116. package/dist/core/schemas.d.ts +82 -0
  117. package/dist/core/schemas.d.ts.map +1 -0
  118. package/dist/core/schemas.js +200 -0
  119. package/dist/core/schemas.js.map +1 -0
  120. package/dist/core/semantic.d.ts +92 -0
  121. package/dist/core/semantic.d.ts.map +1 -0
  122. package/dist/core/semantic.js +175 -0
  123. package/dist/core/semantic.js.map +1 -0
  124. package/dist/core/skill.d.ts +68 -0
  125. package/dist/core/skill.d.ts.map +1 -0
  126. package/dist/core/skill.js +350 -0
  127. package/dist/core/skill.js.map +1 -0
  128. package/dist/core/tool.d.ts +99 -0
  129. package/dist/core/tool.d.ts.map +1 -0
  130. package/dist/core/tool.js +341 -0
  131. package/dist/core/tool.js.map +1 -0
  132. package/dist/core/tool_router.d.ts +29 -0
  133. package/dist/core/tool_router.d.ts.map +1 -0
  134. package/dist/core/tool_router.js +172 -0
  135. package/dist/core/tool_router.js.map +1 -0
  136. package/dist/core/workspace.d.ts +48 -0
  137. package/dist/core/workspace.d.ts.map +1 -0
  138. package/dist/core/workspace.js +179 -0
  139. package/dist/core/workspace.js.map +1 -0
  140. package/dist/plugins/loader.d.ts +17 -0
  141. package/dist/plugins/loader.d.ts.map +1 -0
  142. package/dist/plugins/loader.js +96 -0
  143. package/dist/plugins/loader.js.map +1 -0
  144. package/dist/skills/loader.d.ts +9 -0
  145. package/dist/skills/loader.d.ts.map +1 -0
  146. package/dist/skills/loader.js +78 -0
  147. package/dist/skills/loader.js.map +1 -0
  148. package/dist/tools/builtin.d.ts +10 -0
  149. package/dist/tools/builtin.d.ts.map +1 -0
  150. package/dist/tools/builtin.js +414 -0
  151. package/dist/tools/builtin.js.map +1 -0
  152. package/dist/tools/computer.d.ts +12 -0
  153. package/dist/tools/computer.d.ts.map +1 -0
  154. package/dist/tools/computer.js +326 -0
  155. package/dist/tools/computer.js.map +1 -0
  156. package/dist/tools/delegate.d.ts +10 -0
  157. package/dist/tools/delegate.d.ts.map +1 -0
  158. package/dist/tools/delegate.js +45 -0
  159. package/dist/tools/delegate.js.map +1 -0
  160. package/dist/web/server.d.ts +5 -0
  161. package/dist/web/server.d.ts.map +1 -0
  162. package/dist/web/server.js +647 -0
  163. package/dist/web/server.js.map +1 -0
  164. package/dist/web/tts.d.ts +33 -0
  165. package/dist/web/tts.d.ts.map +1 -0
  166. package/dist/web/tts.js +69 -0
  167. package/dist/web/tts.js.map +1 -0
  168. package/package.json +60 -0
  169. package/scripts/install.js +48 -0
  170. package/scripts/link.js +10 -0
  171. package/setup.bat +79 -0
  172. package/skill-test-ty2fOA/test.md +10 -0
  173. package/src/agents/dew.ts +70 -0
  174. package/src/agents/fair.ts +102 -0
  175. package/src/agents/fog.ts +48 -0
  176. package/src/agents/frost.ts +50 -0
  177. package/src/agents/rain.ts +50 -0
  178. package/src/agents/snow.ts +239 -0
  179. package/src/cli/main.ts +405 -0
  180. package/src/cli/mode.ts +58 -0
  181. package/src/core/agent.ts +1506 -0
  182. package/src/core/agent_helpers.ts +461 -0
  183. package/src/core/bus.ts +221 -0
  184. package/src/core/cache.ts +153 -0
  185. package/src/core/checkpoint.ts +94 -0
  186. package/src/core/circuit_breaker.ts +119 -0
  187. package/src/core/config.ts +341 -0
  188. package/src/core/constants.ts +95 -0
  189. package/src/core/factory.ts +627 -0
  190. package/src/core/icons.ts +53 -0
  191. package/src/core/index.ts +31 -0
  192. package/src/core/llm.ts +724 -0
  193. package/src/core/logger.ts +144 -0
  194. package/src/core/mcp.ts +953 -0
  195. package/src/core/mcp_server.ts +176 -0
  196. package/src/core/memory.ts +1169 -0
  197. package/src/core/middleware.ts +350 -0
  198. package/src/core/pipelines.ts +424 -0
  199. package/src/core/profile.ts +255 -0
  200. package/src/core/router.ts +124 -0
  201. package/src/core/schemas.ts +282 -0
  202. package/src/core/semantic.ts +211 -0
  203. package/src/core/skill.ts +342 -0
  204. package/src/core/tool.ts +427 -0
  205. package/src/core/tool_router.ts +193 -0
  206. package/src/core/workspace.ts +150 -0
  207. package/src/plugins/loader.ts +66 -0
  208. package/src/skills/loader.ts +46 -0
  209. package/src/sql.js.d.ts +29 -0
  210. package/src/tools/builtin.ts +382 -0
  211. package/src/tools/computer.ts +269 -0
  212. package/src/tools/delegate.ts +49 -0
  213. package/src/web/server.ts +634 -0
  214. package/src/web/tts.ts +93 -0
  215. package/tests/bus.test.ts +121 -0
  216. package/tests/icons.test.ts +45 -0
  217. package/tests/router.test.ts +86 -0
  218. package/tests/schemas.test.ts +51 -0
  219. package/tests/semantic.test.ts +83 -0
  220. package/tests/setup.ts +10 -0
  221. package/tests/skill.test.ts +172 -0
  222. package/tests/tool.test.ts +108 -0
  223. package/tests/tool_router.test.ts +71 -0
  224. package/tsconfig.json +37 -0
  225. package/vitest.config.ts +17 -0
@@ -0,0 +1,988 @@
1
+ "use strict";
2
+ /**
3
+ * Memory system: short-term context, long-term persistence, working memory.
4
+ *
5
+ * Three-layer memory for each agent with SQLite persistence.
6
+ * - Short-term: conversation context (persisted to SQLite)
7
+ * - Working: in-memory task-scoped state
8
+ * - Long-term: persistent key-value storage with search
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ var __importDefault = (this && this.__importDefault) || function (mod) {
44
+ return (mod && mod.__esModule) ? mod : { "default": mod };
45
+ };
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.Memory = void 0;
48
+ exports.getShortTermLock = getShortTermLock;
49
+ const sql_js_1 = __importDefault(require("sql.js"));
50
+ const fs = __importStar(require("fs"));
51
+ const path = __importStar(require("path"));
52
+ const os = __importStar(require("os"));
53
+ const logger_1 = require("./logger");
54
+ const semantic_1 = require("./semantic");
55
+ const logger = (0, logger_1.getLogger)('memory');
56
+ // Token extraction patterns for fact-recall queries
57
+ const ASCII_TOKEN_RE = /[A-Za-z][A-Za-z0-9_+-]+/g;
58
+ const CJK_RUN_RE = /[一-鿿]+/g;
59
+ /**
60
+ * Simple inline Mutex implementation (replaces async-lock dependency).
61
+ */
62
+ class SimpleMutex {
63
+ constructor() {
64
+ this._locked = false;
65
+ this._queue = [];
66
+ }
67
+ async lock(fn) {
68
+ await new Promise((resolve) => {
69
+ if (!this._locked) {
70
+ this._locked = true;
71
+ resolve();
72
+ }
73
+ else {
74
+ this._queue.push(resolve);
75
+ }
76
+ });
77
+ try {
78
+ return await fn();
79
+ }
80
+ finally {
81
+ if (this._queue.length > 0) {
82
+ const next = this._queue.shift();
83
+ next();
84
+ }
85
+ else {
86
+ this._locked = false;
87
+ }
88
+ }
89
+ }
90
+ }
91
+ /**
92
+ * Three-layer memory system for agents.
93
+ */
94
+ class Memory {
95
+ constructor(config, agentName) {
96
+ this.shortTerm = [];
97
+ this.working = {};
98
+ this.db = null;
99
+ this.SQL = null;
100
+ this.loaded = false;
101
+ this.pendingPersists = new Set();
102
+ this.activeSession = null;
103
+ // short_term is mutated from both main chat loop and handlers
104
+ // All mutations go through a short critical section
105
+ this.shortTermLock = new SimpleMutex();
106
+ this.config = config;
107
+ this.agentName = agentName;
108
+ const base = expandUserPath(config.dbPath);
109
+ this.dbPath = path.join(path.dirname(base), `${agentName}.db`);
110
+ }
111
+ /**
112
+ * Initialize the database and load persistent data.
113
+ */
114
+ async initDb() {
115
+ if (this.db !== null) {
116
+ return;
117
+ }
118
+ // Initialize sql.js
119
+ this.SQL = await (0, sql_js_1.default)();
120
+ // Create directory if it doesn't exist
121
+ const dbDir = path.dirname(this.dbPath);
122
+ if (!fs.existsSync(dbDir)) {
123
+ fs.mkdirSync(dbDir, { recursive: true });
124
+ }
125
+ // Load existing database or create new
126
+ let dbBuffer = null;
127
+ if (fs.existsSync(this.dbPath)) {
128
+ try {
129
+ dbBuffer = fs.readFileSync(this.dbPath);
130
+ }
131
+ catch { /* read failed, start fresh */ }
132
+ }
133
+ this.db = new this.SQL.Database(dbBuffer || undefined);
134
+ this.db.run('PRAGMA journal_mode = MEMORY');
135
+ this.db.run('PRAGMA busy_timeout = 100');
136
+ // Create tables
137
+ this.db.run(`
138
+ CREATE TABLE IF NOT EXISTS memories (
139
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
140
+ agent TEXT NOT NULL,
141
+ key TEXT NOT NULL,
142
+ value TEXT NOT NULL,
143
+ category TEXT DEFAULT 'general',
144
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
145
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
146
+ )
147
+ `);
148
+ // Migrate existing DBs
149
+ try {
150
+ this.db.run("ALTER TABLE memories ADD COLUMN category TEXT DEFAULT 'general'");
151
+ }
152
+ catch {
153
+ // Column already exists
154
+ }
155
+ try {
156
+ this.db.run('ALTER TABLE memories ADD COLUMN updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP');
157
+ }
158
+ catch {
159
+ // Column already exists
160
+ }
161
+ // Ensure unique index
162
+ try {
163
+ this.db.run('DROP INDEX IF EXISTS idx_agent_key');
164
+ }
165
+ catch {
166
+ // Ignore
167
+ }
168
+ this.db.run('CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_key ON memories(agent, key)');
169
+ this.db.run('CREATE INDEX IF NOT EXISTS idx_agent_category ON memories(agent, category)');
170
+ this.db.run(`
171
+ CREATE TABLE IF NOT EXISTS messages (
172
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
173
+ agent TEXT NOT NULL,
174
+ role TEXT NOT NULL,
175
+ content TEXT NOT NULL,
176
+ name TEXT,
177
+ tool_call_id TEXT,
178
+ tool_calls TEXT,
179
+ reasoning_content TEXT,
180
+ session_id TEXT,
181
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
182
+ )
183
+ `);
184
+ this.db.run('CREATE INDEX IF NOT EXISTS idx_messages_agent ON messages(agent, created_at)');
185
+ try {
186
+ this.db.run('ALTER TABLE messages ADD COLUMN tool_calls TEXT');
187
+ }
188
+ catch {
189
+ // Column already exists
190
+ }
191
+ try {
192
+ this.db.run('ALTER TABLE messages ADD COLUMN reasoning_content TEXT');
193
+ }
194
+ catch {
195
+ // Column already exists
196
+ }
197
+ try {
198
+ this.db.run('ALTER TABLE messages ADD COLUMN session_id TEXT');
199
+ }
200
+ catch {
201
+ // Column already exists
202
+ }
203
+ this.db.run(`
204
+ CREATE TABLE IF NOT EXISTS sessions (
205
+ id TEXT PRIMARY KEY,
206
+ agent TEXT NOT NULL,
207
+ name TEXT,
208
+ preview TEXT DEFAULT '',
209
+ message_count INTEGER DEFAULT 0,
210
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
211
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
212
+ )
213
+ `);
214
+ this.db.run('CREATE INDEX IF NOT EXISTS idx_sessions_agent ON sessions(agent, updated_at DESC)');
215
+ this.db.run(`
216
+ CREATE TABLE IF NOT EXISTS working_data (
217
+ agent TEXT NOT NULL,
218
+ key TEXT NOT NULL,
219
+ value TEXT NOT NULL,
220
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
221
+ PRIMARY KEY (agent, key)
222
+ )
223
+ `);
224
+ this.loadShortTerm();
225
+ this.loadWorking();
226
+ }
227
+ /**
228
+ * Persist the database to disk.
229
+ */
230
+ persistDb() {
231
+ if (!this.db)
232
+ return;
233
+ try {
234
+ const data = this.db.export();
235
+ fs.writeFileSync(this.dbPath, Buffer.from(data));
236
+ }
237
+ catch (err) {
238
+ logger.warn('persist_db_failed', { path: this.dbPath, error: String(err) });
239
+ }
240
+ }
241
+ /**
242
+ * Execute a SELECT query and return array of row objects.
243
+ */
244
+ dbAll(sql, params) {
245
+ if (!this.db)
246
+ return [];
247
+ try {
248
+ const stmt = this.db.prepare(sql);
249
+ if (params)
250
+ stmt.bind(params);
251
+ const rows = [];
252
+ while (stmt.step()) {
253
+ rows.push(stmt.getAsObject());
254
+ }
255
+ stmt.free();
256
+ return rows;
257
+ }
258
+ catch {
259
+ return [];
260
+ }
261
+ }
262
+ /**
263
+ * Execute a SELECT query and return first row object.
264
+ */
265
+ dbGet(sql, params) {
266
+ if (!this.db)
267
+ return null;
268
+ try {
269
+ const stmt = this.db.prepare(sql);
270
+ if (params)
271
+ stmt.bind(params);
272
+ let row = null;
273
+ if (stmt.step()) {
274
+ row = stmt.getAsObject();
275
+ }
276
+ stmt.free();
277
+ return row;
278
+ }
279
+ catch {
280
+ return null;
281
+ }
282
+ }
283
+ /**
284
+ * Execute a statement and return this for chaining.
285
+ */
286
+ dbRun(sql, params) {
287
+ if (!this.db)
288
+ return;
289
+ try {
290
+ this.db.run(sql, params);
291
+ }
292
+ catch (err) {
293
+ logger.warn('db_run_failed', { sql: sql.slice(0, 80), error: String(err) });
294
+ }
295
+ }
296
+ /**
297
+ * Load short-term memory from database.
298
+ */
299
+ async loadShortTerm() {
300
+ if (!this.db || this.loaded) {
301
+ return;
302
+ }
303
+ let rows = [];
304
+ if (this.activeSession) {
305
+ rows = this.dbAll(`SELECT role, content, name, tool_call_id, tool_calls, reasoning_content, created_at
306
+ FROM messages
307
+ WHERE agent = ? AND session_id = ?
308
+ ORDER BY id DESC LIMIT ?`, [this.agentName, this.activeSession, this.config.shortTermLimit]);
309
+ }
310
+ else {
311
+ this.loaded = true;
312
+ this.pruneDanglingToolCalls();
313
+ return;
314
+ }
315
+ // Truncate at timestamp gap
316
+ const gapSeconds = parseFloat(process.env.WA_RESUME_GAP_SECONDS || '14400'); // 4h default
317
+ rows = Memory.truncateAtTimestampGap(rows, gapSeconds);
318
+ for (const row of rows.reverse()) {
319
+ let toolCalls = null;
320
+ if (row.tool_calls) {
321
+ try {
322
+ toolCalls = JSON.parse(row.tool_calls);
323
+ }
324
+ catch {
325
+ // Invalid JSON, skip
326
+ }
327
+ }
328
+ this.shortTerm.push({
329
+ role: row.role,
330
+ content: row.content,
331
+ name: row.name,
332
+ toolCallId: row.tool_call_id,
333
+ toolCalls,
334
+ reasoningContent: row.reasoning_content,
335
+ });
336
+ }
337
+ this.loaded = true;
338
+ this.pruneDanglingToolCalls();
339
+ }
340
+ /**
341
+ * Load working memory from database.
342
+ */
343
+ async loadWorking() {
344
+ if (!this.db) {
345
+ return;
346
+ }
347
+ const rows = this.dbAll('SELECT key, value FROM working_data WHERE agent = ?', [this.agentName]);
348
+ for (const row of rows) {
349
+ try {
350
+ this.working[row.key] = JSON.parse(row.value);
351
+ }
352
+ catch {
353
+ // Skip invalid JSON
354
+ }
355
+ }
356
+ }
357
+ /**
358
+ * Keep only the contiguous tail of rows where consecutive timestamps
359
+ * are within gap_seconds of each other.
360
+ */
361
+ static truncateAtTimestampGap(rows, gapSeconds) {
362
+ if (rows.length === 0 || gapSeconds <= 0) {
363
+ return rows;
364
+ }
365
+ const parseTs = (raw) => {
366
+ if (!raw)
367
+ return null;
368
+ if (raw instanceof Date)
369
+ return raw;
370
+ try {
371
+ return new Date(raw);
372
+ }
373
+ catch {
374
+ return null;
375
+ }
376
+ };
377
+ const keep = [rows[0]];
378
+ let prevTs = parseTs(rows[0].created_at);
379
+ for (let i = 1; i < rows.length; i++) {
380
+ const curTs = parseTs(rows[i].created_at);
381
+ if (prevTs && curTs) {
382
+ const delta = Math.abs((prevTs.getTime() - curTs.getTime()) / 1000);
383
+ if (delta > gapSeconds) {
384
+ break;
385
+ }
386
+ }
387
+ keep.push(rows[i]);
388
+ if (curTs) {
389
+ prevTs = curTs;
390
+ }
391
+ }
392
+ return keep;
393
+ }
394
+ /**
395
+ * Remove orphaned tool_calls/tool message pairs from short-term memory.
396
+ */
397
+ pruneDanglingToolCalls() {
398
+ if (this.shortTerm.length === 0) {
399
+ return;
400
+ }
401
+ const n = this.shortTerm.length;
402
+ const remove = new Array(n).fill(false);
403
+ // Pass 1: position-aware matching
404
+ const waiting = {};
405
+ for (let i = 0; i < this.shortTerm.length; i++) {
406
+ const msg = this.shortTerm[i];
407
+ if (msg.role === 'assistant' && msg.toolCalls) {
408
+ for (const tc of msg.toolCalls) {
409
+ const tid = tc.id;
410
+ if (tid) {
411
+ if (!waiting[tid])
412
+ waiting[tid] = [];
413
+ waiting[tid].push(i);
414
+ }
415
+ }
416
+ }
417
+ else if (msg.role === 'tool' && msg.toolCallId) {
418
+ const tid = msg.toolCallId;
419
+ if (waiting[tid] && waiting[tid].length > 0) {
420
+ waiting[tid].pop();
421
+ }
422
+ else {
423
+ remove[i] = true;
424
+ }
425
+ }
426
+ }
427
+ // Any assistant indices still in waiting stacks are orphaned
428
+ for (const indices of Object.values(waiting)) {
429
+ for (const i of indices) {
430
+ remove[i] = true;
431
+ }
432
+ }
433
+ if (!remove.some(r => r)) {
434
+ return;
435
+ }
436
+ const kept = this.shortTerm.filter((_, i) => !remove[i]);
437
+ // Pass 2: remove tool messages with no preceding assistant
438
+ const seenTcIds = new Set();
439
+ const sanitized = [];
440
+ for (const msg of kept) {
441
+ if (msg.role === 'assistant' && msg.toolCalls) {
442
+ for (const tc of msg.toolCalls) {
443
+ const tid = tc.id;
444
+ if (tid) {
445
+ seenTcIds.add(tid);
446
+ }
447
+ }
448
+ }
449
+ else if (msg.role === 'tool' && msg.toolCallId && !seenTcIds.has(msg.toolCallId)) {
450
+ continue;
451
+ }
452
+ sanitized.push(msg);
453
+ }
454
+ this.shortTerm = sanitized;
455
+ }
456
+ /**
457
+ * Public wrapper around pruneDanglingToolCalls.
458
+ */
459
+ pruneToolMessages() {
460
+ this.pruneDanglingToolCalls();
461
+ }
462
+ /**
463
+ * Flush pending operations and close database.
464
+ */
465
+ async close() {
466
+ if (this.db) {
467
+ await this.flushPending();
468
+ this.db.close();
469
+ }
470
+ }
471
+ /**
472
+ * Wait for all pending persist operations to complete.
473
+ */
474
+ async flushPending() {
475
+ if (this.pendingPersists.size > 0) {
476
+ const results = await Promise.allSettled(Array.from(this.pendingPersists));
477
+ for (const result of results) {
478
+ if (result.status === 'rejected') {
479
+ logger.warn('flush_persist_failed', { agent: this.agentName, error: String(result.reason) });
480
+ }
481
+ }
482
+ this.pendingPersists.clear();
483
+ }
484
+ }
485
+ // -- Short-term memory --
486
+ /**
487
+ * Add a message to short-term memory.
488
+ */
489
+ addMessage(role, content, kwargs) {
490
+ const msg = {
491
+ role,
492
+ content,
493
+ name: kwargs?.name,
494
+ toolCallId: kwargs?.toolCallId,
495
+ toolCalls: kwargs?.toolCalls,
496
+ reasoningContent: kwargs?.reasoningContent,
497
+ };
498
+ const ephemeral = kwargs?.ephemeral ?? false;
499
+ // Mutex-locked section
500
+ this.shortTermLock.lock(async () => {
501
+ this.shortTerm.push(msg);
502
+ if (this.shortTerm.length > this.config.shortTermLimit) {
503
+ const systemMsgs = this.shortTerm.filter(m => m.role === 'system');
504
+ const otherMsgs = this.shortTerm.filter(m => m.role !== 'system');
505
+ const keep = Math.max(0, this.config.shortTermLimit - systemMsgs.length);
506
+ this.shortTerm = systemMsgs.concat(keep > 0 ? otherMsgs.slice(-keep) : []);
507
+ this.pruneDanglingToolCalls();
508
+ }
509
+ });
510
+ // Persist message if not ephemeral
511
+ if (this.db && role !== 'system' && !ephemeral) {
512
+ const toolCallsJson = msg.toolCalls ? JSON.stringify(msg.toolCalls) : null;
513
+ const sessionId = this.activeSession;
514
+ const promise = this.persistMessage(role, content, msg.name || null, msg.toolCallId || null, toolCallsJson, msg.reasoningContent || null, sessionId);
515
+ this.pendingPersists.add(promise);
516
+ promise.then(() => {
517
+ this.pendingPersists.delete(promise);
518
+ }).catch(() => {
519
+ this.pendingPersists.delete(promise);
520
+ });
521
+ }
522
+ }
523
+ /**
524
+ * Persist a message to database.
525
+ */
526
+ async persistMessage(role, content, name, toolCallId, toolCalls = null, reasoningContent = null, sessionId = null) {
527
+ if (!this.db) {
528
+ return;
529
+ }
530
+ try {
531
+ this.dbRun(`INSERT INTO messages (agent, role, content, name, tool_call_id, tool_calls, reasoning_content, session_id)
532
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [this.agentName, role, content, name, toolCallId, toolCalls, reasoningContent, sessionId]);
533
+ if (sessionId) {
534
+ this.dbRun('UPDATE sessions SET message_count = message_count + 1, updated_at = CURRENT_TIMESTAMP WHERE id = ?', [sessionId]);
535
+ }
536
+ // Auto-prune old messages
537
+ const maxPersisted = this.config.maxPersistedMessages || 1000;
538
+ if (sessionId && maxPersisted > 0) {
539
+ const row = this.dbGet('SELECT COUNT(*) as count FROM messages WHERE agent = ? AND session_id = ? AND role != ?', [this.agentName, sessionId, 'system']);
540
+ if (row && row.count > maxPersisted) {
541
+ const excess = row.count - maxPersisted;
542
+ this.dbRun(`DELETE FROM messages WHERE id IN (
543
+ SELECT id FROM messages WHERE agent = ? AND session_id = ? AND role != ?
544
+ ORDER BY id ASC LIMIT ?
545
+ )`, [this.agentName, sessionId, 'system', excess]);
546
+ }
547
+ }
548
+ }
549
+ catch (err) {
550
+ logger.warn('persist_message_failed', { agent: this.agentName, error: String(err) });
551
+ }
552
+ }
553
+ /**
554
+ * Get messages as a list of dicts compatible with LLM API.
555
+ */
556
+ getMessages() {
557
+ this.pruneDanglingToolCalls();
558
+ const msgs = [];
559
+ for (const m of this.shortTerm) {
560
+ const d = { role: m.role, content: m.content };
561
+ if (m.name)
562
+ d.name = m.name;
563
+ if (m.toolCallId)
564
+ d.tool_call_id = m.toolCallId;
565
+ if (m.toolCalls)
566
+ d.tool_calls = m.toolCalls;
567
+ if (m.reasoningContent)
568
+ d.reasoning_content = m.reasoningContent;
569
+ msgs.push(d);
570
+ }
571
+ return msgs;
572
+ }
573
+ /**
574
+ * Return stats about current memory usage.
575
+ */
576
+ getContextWindowUsage() {
577
+ let totalChars = 0;
578
+ let cjk = 0;
579
+ for (const m of this.shortTerm) {
580
+ totalChars += m.content.length;
581
+ for (const c of m.content) {
582
+ const code = c.charCodeAt(0);
583
+ if ((code >= 0x4e00 && code <= 0x9fff) || (code >= 0x3000 && code <= 0x303f)) {
584
+ cjk++;
585
+ }
586
+ }
587
+ }
588
+ const other = totalChars - cjk;
589
+ return {
590
+ messageCount: this.shortTerm.length,
591
+ totalChars,
592
+ estimatedTokens: Math.max(1, cjk * 2 + Math.floor(other / 4)),
593
+ limit: this.config.shortTermLimit,
594
+ };
595
+ }
596
+ /**
597
+ * Clear in-memory short-term memory.
598
+ */
599
+ async clearShortTerm() {
600
+ const systemMsgs = this.shortTerm.filter(m => m.role === 'system');
601
+ this.shortTerm = systemMsgs;
602
+ if (!this.db) {
603
+ return;
604
+ }
605
+ if (this.activeSession !== null) {
606
+ this.dbRun('DELETE FROM messages WHERE agent = ? AND role != ? AND session_id = ?', [this.agentName, 'system', this.activeSession]);
607
+ this.dbRun('UPDATE sessions SET message_count = 0 WHERE id = ?', [this.activeSession]);
608
+ }
609
+ else {
610
+ this.dbRun('DELETE FROM messages WHERE agent = ? AND role != ? AND session_id IS NULL', [this.agentName, 'system']);
611
+ }
612
+ }
613
+ // -- Working memory --
614
+ /**
615
+ * Set a working memory value.
616
+ */
617
+ setWorking(key, value) {
618
+ this.working[key] = value;
619
+ this.schedulePersistWorking();
620
+ }
621
+ /**
622
+ * Get a working memory value.
623
+ */
624
+ getWorking(key, defaultValue = null) {
625
+ return this.working[key] ?? defaultValue;
626
+ }
627
+ /**
628
+ * Clear all working memory.
629
+ */
630
+ clearWorking() {
631
+ this.working = {};
632
+ this.schedulePersistWorking();
633
+ }
634
+ schedulePersistWorking() {
635
+ if (!this.db) {
636
+ return;
637
+ }
638
+ const promise = this.persistWorking();
639
+ this.pendingPersists.add(promise);
640
+ promise.then(() => {
641
+ this.pendingPersists.delete(promise);
642
+ }).catch(() => {
643
+ this.pendingPersists.delete(promise);
644
+ });
645
+ }
646
+ async persistWorking() {
647
+ if (!this.db) {
648
+ return;
649
+ }
650
+ try {
651
+ this.dbRun('DELETE FROM working_data WHERE agent = ?', [this.agentName]);
652
+ for (const [key, value] of Object.entries(this.working)) {
653
+ this.dbRun('INSERT INTO working_data (agent, key, value) VALUES (?, ?, ?)', [this.agentName, key, JSON.stringify(value)]);
654
+ }
655
+ }
656
+ catch (err) {
657
+ logger.warn('persist_working_failed', { agent: this.agentName, error: String(err) });
658
+ }
659
+ }
660
+ // -- Long-term memory --
661
+ /**
662
+ * Store a long-term memory fact.
663
+ */
664
+ async remember(key, value, category = 'general') {
665
+ if (!this.db) {
666
+ return;
667
+ }
668
+ this.dbRun(`INSERT INTO memories (agent, key, value, category) VALUES (?, ?, ?, ?)
669
+ ON CONFLICT(agent, key) DO UPDATE SET value = excluded.value, category = excluded.category, updated_at = CURRENT_TIMESTAMP`, [this.agentName, key, JSON.stringify(value), category]);
670
+ }
671
+ /**
672
+ * Recall long-term memories.
673
+ */
674
+ async recall(key, category, limit = 20) {
675
+ if (!this.db) {
676
+ return [];
677
+ }
678
+ let query = 'SELECT key, value, category FROM memories WHERE agent = ?';
679
+ const params = [this.agentName];
680
+ if (key) {
681
+ query += ' AND key LIKE ?';
682
+ params.push(`%${key}%`);
683
+ }
684
+ if (category) {
685
+ query += ' AND category = ?';
686
+ params.push(category);
687
+ }
688
+ query += ' ORDER BY updated_at DESC LIMIT ?';
689
+ params.push(limit);
690
+ const rows = this.dbAll(query, params);
691
+ return rows.map((r) => ({
692
+ key: r.key,
693
+ value: JSON.parse(r.value),
694
+ category: r.category,
695
+ }));
696
+ }
697
+ /**
698
+ * Forget a memory.
699
+ */
700
+ async forget(key) {
701
+ if (!this.db) {
702
+ return;
703
+ }
704
+ this.dbRun('DELETE FROM memories WHERE agent = ? AND key = ?', [this.agentName, key]);
705
+ }
706
+ /**
707
+ * Tokenize query for recall.
708
+ */
709
+ static tokenizeForRecall(query) {
710
+ const out = new Set();
711
+ const text = query || '';
712
+ // ASCII tokens first
713
+ let match;
714
+ while ((match = ASCII_TOKEN_RE.exec(text)) !== null) {
715
+ out.add(match[0]);
716
+ }
717
+ // CJK tokens (n-grams)
718
+ for (const run of text.match(CJK_RUN_RE) || []) {
719
+ for (const size of [3, 2]) {
720
+ if (run.length < size)
721
+ continue;
722
+ for (let i = 0; i <= run.length - size; i++) {
723
+ out.add(run.slice(i, i + size));
724
+ }
725
+ }
726
+ }
727
+ return Array.from(out);
728
+ }
729
+ /**
730
+ * Recall for injection into prompts.
731
+ */
732
+ async recallForInjection(query, limit = 3) {
733
+ if (!this.db || !query) {
734
+ return [];
735
+ }
736
+ const tokens = Memory.tokenizeForRecall(query).slice(0, 24);
737
+ const likeHits = [];
738
+ // Pass 1: LIKE token scan
739
+ if (tokens.length > 0) {
740
+ const likeClauses = tokens.map(() => 'key LIKE ? OR value LIKE ?').join(' OR ');
741
+ const params = [this.agentName];
742
+ for (const tok of tokens) {
743
+ const pattern = `%${tok}%`;
744
+ params.push(pattern, pattern);
745
+ }
746
+ params.push(limit * 2);
747
+ const sql = `SELECT key, value, category, updated_at FROM memories
748
+ WHERE agent = ? AND (${likeClauses})
749
+ ORDER BY updated_at DESC LIMIT ?`;
750
+ const rows = this.dbAll(sql, params);
751
+ for (const r of rows) {
752
+ try {
753
+ const val = JSON.parse(r.value);
754
+ likeHits.push({
755
+ key: r.key,
756
+ value: val,
757
+ category: r.category,
758
+ updatedAt: r.updated_at,
759
+ });
760
+ }
761
+ catch {
762
+ likeHits.push({
763
+ key: r.key,
764
+ value: r.value,
765
+ category: r.category,
766
+ updatedAt: r.updated_at,
767
+ });
768
+ }
769
+ }
770
+ }
771
+ // Pass 2: Semantic scoring (best-effort)
772
+ const semanticHits = [];
773
+ try {
774
+ const seenKeys = new Set(likeHits.map(h => h.key));
775
+ const rows = this.dbAll('SELECT key, value, category, updated_at FROM memories WHERE agent = ? ORDER BY updated_at DESC LIMIT 200', [this.agentName]);
776
+ const candidates = [];
777
+ for (const r of rows) {
778
+ if (seenKeys.has(r.key))
779
+ continue;
780
+ try {
781
+ const val = JSON.parse(r.value);
782
+ candidates.push({
783
+ key: r.key,
784
+ value: val,
785
+ category: r.category,
786
+ updatedAt: r.updated_at,
787
+ });
788
+ }
789
+ catch {
790
+ candidates.push({
791
+ key: r.key,
792
+ value: r.value,
793
+ category: r.category,
794
+ updatedAt: r.updated_at,
795
+ });
796
+ }
797
+ }
798
+ const scorer = (0, semantic_1.getScorer)();
799
+ if (scorer) {
800
+ const ranked = scorer.rank(query, candidates, 'value', limit, 0.03);
801
+ for (const [_score, c] of ranked) {
802
+ semanticHits.push(c);
803
+ }
804
+ }
805
+ }
806
+ catch (err) {
807
+ // Semantic pass is best-effort
808
+ logger.debug('recall_semantic_failed', { error: String(err) });
809
+ }
810
+ // Merge: LIKE first, then semantic
811
+ const seen = new Set();
812
+ const merged = [];
813
+ for (const src of [likeHits, semanticHits]) {
814
+ for (const item of src) {
815
+ const k = item.key;
816
+ if (seen.has(k))
817
+ continue;
818
+ seen.add(k);
819
+ merged.push({ key: k, value: item.value, category: item.category });
820
+ if (merged.length >= limit) {
821
+ return merged;
822
+ }
823
+ }
824
+ }
825
+ return merged;
826
+ }
827
+ /**
828
+ * Format facts as a markdown block for prompt injection.
829
+ */
830
+ static formatFactsBlock(facts) {
831
+ if (!facts || facts.length === 0) {
832
+ return '';
833
+ }
834
+ const lines = ['## 相关记忆'];
835
+ for (const f of facts) {
836
+ let v = f.value;
837
+ if (typeof v !== 'string') {
838
+ try {
839
+ v = JSON.stringify(v);
840
+ }
841
+ catch {
842
+ v = String(v);
843
+ }
844
+ }
845
+ lines.push(`- **${f.key}**: ${v}`);
846
+ }
847
+ return lines.join('\n');
848
+ }
849
+ // -- Session management --
850
+ /**
851
+ * Get active session ID.
852
+ */
853
+ getActiveSession() {
854
+ return this.activeSession;
855
+ }
856
+ /**
857
+ * Create a new session.
858
+ */
859
+ async createSession(name) {
860
+ const sessionId = Math.random().toString(36).slice(2, 14);
861
+ const preview = name || '';
862
+ if (this.db) {
863
+ this.dbRun('INSERT INTO sessions (id, agent, name, preview) VALUES (?, ?, ?, ?)', [sessionId, this.agentName, name, preview]);
864
+ }
865
+ this.activeSession = sessionId;
866
+ this.shortTerm = this.shortTerm.filter(m => m.role === 'system');
867
+ this.loaded = true;
868
+ return sessionId;
869
+ }
870
+ /**
871
+ * List all sessions.
872
+ */
873
+ async listSessions() {
874
+ if (!this.db) {
875
+ return [];
876
+ }
877
+ const rows = this.dbAll('SELECT id, agent, name, preview, message_count, created_at, updated_at FROM sessions WHERE agent = ? ORDER BY updated_at DESC LIMIT 50', [this.agentName]);
878
+ return rows.map((r) => ({
879
+ id: r.id,
880
+ agent: r.agent,
881
+ name: r.name,
882
+ preview: r.preview,
883
+ messageCount: r.message_count,
884
+ createdAt: r.created_at,
885
+ updatedAt: r.updated_at,
886
+ }));
887
+ }
888
+ /**
889
+ * Resume the latest session.
890
+ */
891
+ async resumeLatestSession() {
892
+ if (!this.db || this.activeSession) {
893
+ return this.activeSession;
894
+ }
895
+ const row = this.dbGet('SELECT id FROM sessions WHERE agent = ? ORDER BY updated_at DESC LIMIT 1', [this.agentName]);
896
+ if (!row) {
897
+ return null;
898
+ }
899
+ this.activeSession = row.id;
900
+ this.loaded = false;
901
+ this.shortTerm = this.shortTerm.filter(m => m.role === 'system');
902
+ await this.loadShortTerm();
903
+ return this.activeSession;
904
+ }
905
+ /**
906
+ * Load a specific session.
907
+ */
908
+ async loadSession(sessionId) {
909
+ if (!this.db) {
910
+ return false;
911
+ }
912
+ const row = this.dbGet('SELECT id FROM sessions WHERE id = ? AND agent = ?', [sessionId, this.agentName]);
913
+ if (!row) {
914
+ return false;
915
+ }
916
+ this.activeSession = sessionId;
917
+ this.shortTerm = this.shortTerm.filter(m => m.role === 'system');
918
+ this.loaded = false;
919
+ await this.loadShortTerm();
920
+ return true;
921
+ }
922
+ /**
923
+ * Delete a session.
924
+ */
925
+ async deleteSession(sessionId) {
926
+ if (!this.db) {
927
+ return false;
928
+ }
929
+ const row = this.dbGet('SELECT id FROM sessions WHERE id = ? AND agent = ?', [sessionId, this.agentName]);
930
+ if (!row) {
931
+ return false;
932
+ }
933
+ this.dbRun('DELETE FROM messages WHERE agent = ? AND session_id = ?', [this.agentName, sessionId]);
934
+ this.dbRun('DELETE FROM sessions WHERE id = ? AND agent = ?', [sessionId, this.agentName]);
935
+ if (this.activeSession === sessionId) {
936
+ this.activeSession = null;
937
+ this.shortTerm = this.shortTerm.filter(m => m.role === 'system');
938
+ }
939
+ return true;
940
+ }
941
+ /**
942
+ * Update session preview.
943
+ */
944
+ async updateSessionPreview() {
945
+ if (!this.db || !this.activeSession) {
946
+ return;
947
+ }
948
+ const row = this.dbGet('SELECT content FROM messages WHERE agent = ? AND session_id = ? AND role = ? ORDER BY id ASC LIMIT 1', [this.agentName, this.activeSession, 'user']);
949
+ if (row) {
950
+ const preview = row.content.slice(0, 80);
951
+ this.dbRun('UPDATE sessions SET preview = ? WHERE id = ?', [preview, this.activeSession]);
952
+ }
953
+ }
954
+ /**
955
+ * Get memory statistics.
956
+ */
957
+ async getMemoryStats() {
958
+ if (!this.db) {
959
+ return { total: 0, categories: {} };
960
+ }
961
+ const rows = this.dbAll('SELECT category, COUNT(*) as count FROM memories WHERE agent = ? GROUP BY category', [this.agentName]);
962
+ const categories = {};
963
+ for (const row of rows) {
964
+ categories[row.category] = row.count;
965
+ }
966
+ return {
967
+ total: Object.values(categories).reduce((a, b) => a + b, 0),
968
+ categories,
969
+ };
970
+ }
971
+ }
972
+ exports.Memory = Memory;
973
+ /**
974
+ * Helper for expanding home directory paths.
975
+ */
976
+ function expandUserPath(filePath) {
977
+ if (filePath.startsWith('~')) {
978
+ return path.join(os.homedir(), filePath.slice(1));
979
+ }
980
+ return filePath;
981
+ }
982
+ /**
983
+ * ShortTermLock helper - exposes the lock for use by agent.ts
984
+ */
985
+ function getShortTermLock(memory) {
986
+ return memory.shortTermLock;
987
+ }
988
+ //# sourceMappingURL=memory.js.map