@looopy-ai/core 1.0.1

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 (132) hide show
  1. package/LICENSE +9 -0
  2. package/dist/core/agent.d.ts +53 -0
  3. package/dist/core/agent.js +416 -0
  4. package/dist/core/cleanup.d.ts +12 -0
  5. package/dist/core/cleanup.js +45 -0
  6. package/dist/core/index.d.ts +3 -0
  7. package/dist/core/index.js +3 -0
  8. package/dist/core/iteration.d.ts +5 -0
  9. package/dist/core/iteration.js +60 -0
  10. package/dist/core/logger.d.ts +11 -0
  11. package/dist/core/logger.js +31 -0
  12. package/dist/core/loop.d.ts +5 -0
  13. package/dist/core/loop.js +125 -0
  14. package/dist/core/tools.d.ts +4 -0
  15. package/dist/core/tools.js +78 -0
  16. package/dist/core/types.d.ts +30 -0
  17. package/dist/core/types.js +1 -0
  18. package/dist/events/index.d.ts +3 -0
  19. package/dist/events/index.js +2 -0
  20. package/dist/events/utils.d.ts +250 -0
  21. package/dist/events/utils.js +263 -0
  22. package/dist/index.d.ts +8 -0
  23. package/dist/index.js +8 -0
  24. package/dist/observability/index.d.ts +1 -0
  25. package/dist/observability/index.js +1 -0
  26. package/dist/observability/spans/agent-turn.d.ts +31 -0
  27. package/dist/observability/spans/agent-turn.js +94 -0
  28. package/dist/observability/spans/index.d.ts +5 -0
  29. package/dist/observability/spans/index.js +5 -0
  30. package/dist/observability/spans/iteration.d.ts +14 -0
  31. package/dist/observability/spans/iteration.js +41 -0
  32. package/dist/observability/spans/llm-call.d.ts +14 -0
  33. package/dist/observability/spans/llm-call.js +50 -0
  34. package/dist/observability/spans/loop.d.ts +14 -0
  35. package/dist/observability/spans/loop.js +40 -0
  36. package/dist/observability/spans/tool.d.ts +14 -0
  37. package/dist/observability/spans/tool.js +44 -0
  38. package/dist/observability/tracing.d.ts +58 -0
  39. package/dist/observability/tracing.js +203 -0
  40. package/dist/providers/chat-completions/aggregate.d.ts +4 -0
  41. package/dist/providers/chat-completions/aggregate.js +152 -0
  42. package/dist/providers/chat-completions/content.d.ts +25 -0
  43. package/dist/providers/chat-completions/content.js +229 -0
  44. package/dist/providers/chat-completions/index.d.ts +4 -0
  45. package/dist/providers/chat-completions/index.js +4 -0
  46. package/dist/providers/chat-completions/streaming.d.ts +12 -0
  47. package/dist/providers/chat-completions/streaming.js +3 -0
  48. package/dist/providers/chat-completions/types.d.ts +39 -0
  49. package/dist/providers/chat-completions/types.js +1 -0
  50. package/dist/providers/index.d.ts +2 -0
  51. package/dist/providers/index.js +1 -0
  52. package/dist/providers/litellm-provider.d.ts +43 -0
  53. package/dist/providers/litellm-provider.js +377 -0
  54. package/dist/server/event-buffer.d.ts +37 -0
  55. package/dist/server/event-buffer.js +116 -0
  56. package/dist/server/event-router.d.ts +31 -0
  57. package/dist/server/event-router.js +91 -0
  58. package/dist/server/index.d.ts +3 -0
  59. package/dist/server/index.js +3 -0
  60. package/dist/server/sse.d.ts +60 -0
  61. package/dist/server/sse.js +159 -0
  62. package/dist/stores/artifacts/artifact-scheduler.d.ts +50 -0
  63. package/dist/stores/artifacts/artifact-scheduler.js +86 -0
  64. package/dist/stores/artifacts/index.d.ts +3 -0
  65. package/dist/stores/artifacts/index.js +3 -0
  66. package/dist/stores/artifacts/internal-event-artifact-store.d.ts +54 -0
  67. package/dist/stores/artifacts/internal-event-artifact-store.js +126 -0
  68. package/dist/stores/artifacts/memory-artifact-store.d.ts +52 -0
  69. package/dist/stores/artifacts/memory-artifact-store.js +268 -0
  70. package/dist/stores/filesystem/filesystem-agent-store.d.ts +18 -0
  71. package/dist/stores/filesystem/filesystem-agent-store.js +61 -0
  72. package/dist/stores/filesystem/filesystem-artifact-store.d.ts +59 -0
  73. package/dist/stores/filesystem/filesystem-artifact-store.js +325 -0
  74. package/dist/stores/filesystem/filesystem-context-store.d.ts +37 -0
  75. package/dist/stores/filesystem/filesystem-context-store.js +245 -0
  76. package/dist/stores/filesystem/filesystem-message-store.d.ts +28 -0
  77. package/dist/stores/filesystem/filesystem-message-store.js +149 -0
  78. package/dist/stores/filesystem/filesystem-task-state-store.d.ts +27 -0
  79. package/dist/stores/filesystem/filesystem-task-state-store.js +220 -0
  80. package/dist/stores/filesystem/index.d.ts +10 -0
  81. package/dist/stores/filesystem/index.js +5 -0
  82. package/dist/stores/index.d.ts +5 -0
  83. package/dist/stores/index.js +5 -0
  84. package/dist/stores/memory/memory-state-store.d.ts +15 -0
  85. package/dist/stores/memory/memory-state-store.js +55 -0
  86. package/dist/stores/messages/hybrid-message-store.d.ts +29 -0
  87. package/dist/stores/messages/hybrid-message-store.js +72 -0
  88. package/dist/stores/messages/index.d.ts +4 -0
  89. package/dist/stores/messages/index.js +4 -0
  90. package/dist/stores/messages/interfaces.d.ts +42 -0
  91. package/dist/stores/messages/interfaces.js +18 -0
  92. package/dist/stores/messages/mem0-message-store.d.ts +34 -0
  93. package/dist/stores/messages/mem0-message-store.js +218 -0
  94. package/dist/stores/messages/memory-message-store.d.ts +27 -0
  95. package/dist/stores/messages/memory-message-store.js +183 -0
  96. package/dist/tools/artifact-tools.d.ts +4 -0
  97. package/dist/tools/artifact-tools.js +277 -0
  98. package/dist/tools/client-tool-provider.d.ts +25 -0
  99. package/dist/tools/client-tool-provider.js +139 -0
  100. package/dist/tools/index.d.ts +4 -0
  101. package/dist/tools/index.js +4 -0
  102. package/dist/tools/local-tools.d.ts +13 -0
  103. package/dist/tools/local-tools.js +70 -0
  104. package/dist/tools/mcp-client.d.ts +29 -0
  105. package/dist/tools/mcp-client.js +62 -0
  106. package/dist/tools/mcp-tool-provider.d.ts +22 -0
  107. package/dist/tools/mcp-tool-provider.js +86 -0
  108. package/dist/types/a2a.d.ts +36 -0
  109. package/dist/types/a2a.js +1 -0
  110. package/dist/types/agent.d.ts +14 -0
  111. package/dist/types/agent.js +1 -0
  112. package/dist/types/artifact.d.ts +126 -0
  113. package/dist/types/artifact.js +1 -0
  114. package/dist/types/context.d.ts +13 -0
  115. package/dist/types/context.js +1 -0
  116. package/dist/types/event.d.ts +360 -0
  117. package/dist/types/event.js +30 -0
  118. package/dist/types/index.d.ts +9 -0
  119. package/dist/types/index.js +9 -0
  120. package/dist/types/llm.d.ts +24 -0
  121. package/dist/types/llm.js +1 -0
  122. package/dist/types/message.d.ts +9 -0
  123. package/dist/types/message.js +1 -0
  124. package/dist/types/state.d.ts +86 -0
  125. package/dist/types/state.js +1 -0
  126. package/dist/types/tools.d.ts +57 -0
  127. package/dist/types/tools.js +53 -0
  128. package/dist/utils/error.d.ts +8 -0
  129. package/dist/utils/error.js +23 -0
  130. package/dist/utils/process-signals.d.ts +3 -0
  131. package/dist/utils/process-signals.js +67 -0
  132. package/package.json +54 -0
@@ -0,0 +1,325 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { appendFile, mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ export class FileSystemArtifactStore {
5
+ basePath;
6
+ agentId;
7
+ constructor(config) {
8
+ this.basePath = config.basePath || './_agent_store';
9
+ this.agentId = config.agentId;
10
+ }
11
+ async createFileArtifact(params) {
12
+ const artifactId = params.artifactId || randomUUID();
13
+ const artifactDir = this.getArtifactDir(params.contextId, artifactId);
14
+ const existing = await this.getArtifact(params.contextId, artifactId);
15
+ if (existing && !params.override) {
16
+ throw new Error(`Artifact already exists: ${artifactId}. ` +
17
+ `Use override: true to replace it, or use a different artifactId.`);
18
+ }
19
+ if (existing && params.override) {
20
+ try {
21
+ await rm(artifactDir, { recursive: true, force: true });
22
+ }
23
+ catch {
24
+ }
25
+ }
26
+ await mkdir(artifactDir, { recursive: true });
27
+ const contentPath = join(artifactDir, 'content.txt');
28
+ await writeFile(contentPath, '', 'utf-8');
29
+ const now = new Date().toISOString();
30
+ const artifact = {
31
+ type: 'file',
32
+ artifactId,
33
+ taskId: params.taskId,
34
+ contextId: params.contextId,
35
+ name: params.name,
36
+ description: params.description,
37
+ mimeType: params.mimeType,
38
+ encoding: params.encoding || 'utf-8',
39
+ chunks: [],
40
+ totalChunks: 0,
41
+ totalSize: 0,
42
+ status: 'building',
43
+ version: existing && params.override ? existing.version + 1 : 1,
44
+ operations: [
45
+ {
46
+ operationId: `op-${Date.now()}`,
47
+ type: params.override ? 'reset' : 'create',
48
+ timestamp: now,
49
+ },
50
+ ],
51
+ createdAt: existing ? existing.createdAt : now,
52
+ updatedAt: now,
53
+ };
54
+ await this.saveMetadata(artifactDir, artifact);
55
+ return artifactId;
56
+ }
57
+ async appendFileChunk(contextId, artifactId, chunk, options) {
58
+ const artifact = await this.getArtifact(contextId, artifactId);
59
+ if (!artifact || artifact.type !== 'file') {
60
+ throw new Error(`File artifact not found: ${artifactId} in context ${contextId}`);
61
+ }
62
+ const artifactDir = this.getArtifactDir(contextId, artifactId);
63
+ const contentPath = join(artifactDir, 'content.txt');
64
+ const chunkIndex = artifact.chunks.length;
65
+ await appendFile(contentPath, chunk, 'utf-8');
66
+ const now = new Date().toISOString();
67
+ const chunkSize = Buffer.byteLength(chunk, 'utf-8');
68
+ artifact.chunks.push({
69
+ index: chunkIndex,
70
+ data: chunk,
71
+ size: chunkSize,
72
+ timestamp: now,
73
+ });
74
+ artifact.totalChunks++;
75
+ artifact.totalSize += chunkSize;
76
+ artifact.updatedAt = now;
77
+ if (options?.isLastChunk) {
78
+ artifact.status = 'complete';
79
+ artifact.completedAt = now;
80
+ }
81
+ artifact.operations.push({
82
+ operationId: `op-${Date.now()}`,
83
+ type: 'append',
84
+ timestamp: now,
85
+ });
86
+ await this.saveMetadata(artifactDir, artifact);
87
+ }
88
+ async getFileContent(contextId, artifactId) {
89
+ const artifact = await this.getArtifact(contextId, artifactId);
90
+ if (!artifact || artifact.type !== 'file') {
91
+ throw new Error(`File artifact not found: ${artifactId} in context ${contextId}`);
92
+ }
93
+ const artifactDir = this.getArtifactDir(contextId, artifactId);
94
+ const contentPath = join(artifactDir, 'content.txt');
95
+ try {
96
+ return await readFile(contentPath, 'utf-8');
97
+ }
98
+ catch (error) {
99
+ if (artifact.chunks.length > 0) {
100
+ return artifact.chunks.map((c) => c.data).join('');
101
+ }
102
+ throw error;
103
+ }
104
+ }
105
+ async createDataArtifact(params) {
106
+ const artifactId = params.artifactId || randomUUID();
107
+ const artifactDir = this.getArtifactDir(params.contextId, artifactId);
108
+ const existing = await this.getArtifact(params.contextId, artifactId);
109
+ if (existing && !params.override) {
110
+ throw new Error(`Artifact already exists: ${artifactId}. ` +
111
+ `Use override: true to replace it, or use a different artifactId.`);
112
+ }
113
+ if (existing && params.override) {
114
+ try {
115
+ await rm(artifactDir, { recursive: true, force: true });
116
+ }
117
+ catch {
118
+ }
119
+ }
120
+ await mkdir(artifactDir, { recursive: true });
121
+ const now = new Date().toISOString();
122
+ const artifact = {
123
+ type: 'data',
124
+ artifactId,
125
+ taskId: params.taskId,
126
+ contextId: params.contextId,
127
+ name: params.name,
128
+ description: params.description,
129
+ data: {},
130
+ status: 'building',
131
+ version: existing && params.override ? existing.version + 1 : 1,
132
+ operations: [
133
+ {
134
+ operationId: `op-${Date.now()}`,
135
+ type: params.override ? 'reset' : 'create',
136
+ timestamp: now,
137
+ },
138
+ ],
139
+ createdAt: existing ? existing.createdAt : now,
140
+ updatedAt: now,
141
+ };
142
+ await this.saveMetadata(artifactDir, artifact);
143
+ await writeFile(join(artifactDir, 'data.json'), '{}', 'utf-8');
144
+ return artifactId;
145
+ }
146
+ async writeData(contextId, artifactId, data) {
147
+ const artifact = await this.getArtifact(contextId, artifactId);
148
+ if (!artifact || artifact.type !== 'data') {
149
+ throw new Error(`Data artifact not found: ${artifactId} in context ${contextId}`);
150
+ }
151
+ const artifactDir = this.getArtifactDir(contextId, artifactId);
152
+ const now = new Date().toISOString();
153
+ await writeFile(join(artifactDir, 'data.json'), JSON.stringify(data, null, 2), 'utf-8');
154
+ artifact.data = data;
155
+ artifact.status = 'complete';
156
+ artifact.updatedAt = now;
157
+ artifact.completedAt = now;
158
+ artifact.operations.push({
159
+ operationId: `op-${Date.now()}`,
160
+ type: 'replace',
161
+ timestamp: now,
162
+ });
163
+ await this.saveMetadata(artifactDir, artifact);
164
+ }
165
+ async getDataContent(contextId, artifactId) {
166
+ const artifact = await this.getArtifact(contextId, artifactId);
167
+ if (!artifact || artifact.type !== 'data') {
168
+ throw new Error(`Data artifact not found: ${artifactId} in context ${contextId}`);
169
+ }
170
+ const artifactDir = this.getArtifactDir(contextId, artifactId);
171
+ try {
172
+ const dataPath = join(artifactDir, 'data.json');
173
+ const content = await readFile(dataPath, 'utf-8');
174
+ return JSON.parse(content);
175
+ }
176
+ catch {
177
+ return artifact.data;
178
+ }
179
+ }
180
+ async createDatasetArtifact(params) {
181
+ const artifactId = params.artifactId || randomUUID();
182
+ const artifactDir = this.getArtifactDir(params.contextId, artifactId);
183
+ const existing = await this.getArtifact(params.contextId, artifactId);
184
+ if (existing && !params.override) {
185
+ throw new Error(`Artifact already exists: ${artifactId}. ` +
186
+ `Use override: true to replace it, or use a different artifactId.`);
187
+ }
188
+ if (existing && params.override) {
189
+ try {
190
+ await rm(artifactDir, { recursive: true, force: true });
191
+ }
192
+ catch {
193
+ }
194
+ }
195
+ await mkdir(artifactDir, { recursive: true });
196
+ const now = new Date().toISOString();
197
+ const artifact = {
198
+ type: 'dataset',
199
+ artifactId,
200
+ taskId: params.taskId,
201
+ contextId: params.contextId,
202
+ name: params.name,
203
+ description: params.description,
204
+ schema: params.schema,
205
+ rows: [],
206
+ totalChunks: 0,
207
+ totalSize: 0,
208
+ status: 'building',
209
+ version: existing && params.override ? existing.version + 1 : 1,
210
+ operations: [
211
+ {
212
+ operationId: `op-${Date.now()}`,
213
+ type: params.override ? 'reset' : 'create',
214
+ timestamp: now,
215
+ },
216
+ ],
217
+ createdAt: existing ? existing.createdAt : now,
218
+ updatedAt: now,
219
+ };
220
+ await this.saveMetadata(artifactDir, artifact);
221
+ await writeFile(join(artifactDir, 'rows.jsonl'), '', 'utf-8');
222
+ return artifactId;
223
+ }
224
+ async appendDatasetBatch(contextId, artifactId, rows, options) {
225
+ const artifact = await this.getArtifact(contextId, artifactId);
226
+ if (!artifact || artifact.type !== 'dataset') {
227
+ throw new Error(`Dataset artifact not found: ${artifactId} in context ${contextId}`);
228
+ }
229
+ const artifactDir = this.getArtifactDir(contextId, artifactId);
230
+ const now = new Date().toISOString();
231
+ const rowsPath = join(artifactDir, 'rows.jsonl');
232
+ const lines = `${rows.map((row) => JSON.stringify(row)).join('\n')}\n`;
233
+ await appendFile(rowsPath, lines, 'utf-8');
234
+ artifact.rows.push(...rows);
235
+ artifact.totalChunks++;
236
+ artifact.totalSize += rows.length;
237
+ artifact.updatedAt = now;
238
+ if (options?.isLastBatch) {
239
+ artifact.status = 'complete';
240
+ artifact.completedAt = now;
241
+ }
242
+ artifact.operations.push({
243
+ operationId: `op-${Date.now()}`,
244
+ type: 'append',
245
+ timestamp: now,
246
+ });
247
+ await this.saveMetadata(artifactDir, artifact);
248
+ }
249
+ async getDatasetRows(contextId, artifactId) {
250
+ const artifact = await this.getArtifact(contextId, artifactId);
251
+ if (!artifact || artifact.type !== 'dataset') {
252
+ throw new Error(`Dataset artifact not found: ${artifactId} in context ${contextId}`);
253
+ }
254
+ const artifactDir = this.getArtifactDir(contextId, artifactId);
255
+ try {
256
+ const rowsPath = join(artifactDir, 'rows.jsonl');
257
+ const content = await readFile(rowsPath, 'utf-8');
258
+ const lines = content
259
+ .trim()
260
+ .split('\n')
261
+ .filter((line) => line.length > 0);
262
+ return lines.map((line) => JSON.parse(line));
263
+ }
264
+ catch {
265
+ return artifact.rows;
266
+ }
267
+ }
268
+ async getArtifact(contextId, artifactId) {
269
+ const artifactDir = this.getArtifactDir(contextId, artifactId);
270
+ return this.loadMetadata(artifactDir);
271
+ }
272
+ async deleteArtifact(contextId, artifactId) {
273
+ const artifact = await this.getArtifact(contextId, artifactId);
274
+ if (!artifact) {
275
+ throw new Error(`Artifact not found: ${artifactId} in context ${contextId}`);
276
+ }
277
+ const artifactDir = this.getArtifactDir(contextId, artifactId);
278
+ await rm(artifactDir, { recursive: true, force: true });
279
+ }
280
+ async listArtifacts(contextId, taskId) {
281
+ const results = [];
282
+ const contextDir = join(this.basePath, `agent=${this.agentId}`, `context=${contextId}`, 'artifacts');
283
+ try {
284
+ const entries = await readdir(contextDir);
285
+ for (const artifactId of entries) {
286
+ const artifact = await this.getArtifact(contextId, artifactId);
287
+ if (!artifact)
288
+ continue;
289
+ if (taskId && artifact.taskId !== taskId)
290
+ continue;
291
+ results.push(artifactId);
292
+ }
293
+ }
294
+ catch {
295
+ return [];
296
+ }
297
+ return results;
298
+ }
299
+ async queryArtifacts(query) {
300
+ if (!query.contextId) {
301
+ throw new Error('contextId is required for queryArtifacts');
302
+ }
303
+ return this.listArtifacts(query.contextId, query.taskId);
304
+ }
305
+ async getTaskArtifacts(_taskId) {
306
+ throw new Error('getTaskArtifacts not supported for filesystem store. Use listArtifacts with contextId instead.');
307
+ }
308
+ getArtifactDir(contextId, artifactId) {
309
+ return join(this.basePath, `agent=${this.agentId}`, `context=${contextId}`, 'artifacts', artifactId);
310
+ }
311
+ async saveMetadata(artifactDir, artifact) {
312
+ const metadataPath = join(artifactDir, 'metadata.json');
313
+ await writeFile(metadataPath, JSON.stringify(artifact, null, 2), 'utf-8');
314
+ }
315
+ async loadMetadata(artifactDir) {
316
+ try {
317
+ const metadataPath = join(artifactDir, 'metadata.json');
318
+ const content = await readFile(metadataPath, 'utf-8');
319
+ return JSON.parse(content);
320
+ }
321
+ catch {
322
+ return null;
323
+ }
324
+ }
325
+ }
@@ -0,0 +1,37 @@
1
+ import type { ContextState, ContextStore } from '../../types/state';
2
+ export interface FileSystemContextStoreConfig {
3
+ basePath?: string;
4
+ defaultLockTTL?: number;
5
+ }
6
+ export declare class FileSystemContextStore implements ContextStore {
7
+ private basePath;
8
+ private defaultLockTTL;
9
+ constructor(config?: FileSystemContextStoreConfig);
10
+ save(state: ContextState): Promise<void>;
11
+ load(contextId: string): Promise<ContextState | null>;
12
+ exists(contextId: string): Promise<boolean>;
13
+ delete(contextId: string): Promise<void>;
14
+ list(filter?: {
15
+ agentId?: string;
16
+ ownerId?: string;
17
+ status?: ContextState['status'];
18
+ tags?: string[];
19
+ createdAfter?: Date;
20
+ createdBefore?: Date;
21
+ updatedAfter?: Date;
22
+ limit?: number;
23
+ offset?: number;
24
+ }): Promise<ContextState[]>;
25
+ search(query: string, filter?: {
26
+ agentId?: string;
27
+ ownerId?: string;
28
+ }): Promise<ContextState[]>;
29
+ acquireLock(contextId: string, lockOwnerId: string, ttlSeconds?: number): Promise<boolean>;
30
+ releaseLock(contextId: string, lockOwnerId: string): Promise<void>;
31
+ refreshLock(contextId: string, lockOwnerId: string, ttlSeconds?: number): Promise<boolean>;
32
+ isLocked(contextId: string): Promise<boolean>;
33
+ update(contextId: string, updates: Partial<Omit<ContextState, 'contextId' | 'agentId' | 'createdAt'>>): Promise<void>;
34
+ private getContextDir;
35
+ private findContextDirs;
36
+ private getAllAgentDirs;
37
+ }
@@ -0,0 +1,245 @@
1
+ import { mkdir, readdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ export class FileSystemContextStore {
4
+ basePath;
5
+ defaultLockTTL;
6
+ constructor(config = {}) {
7
+ this.basePath = config.basePath || './_agent_store';
8
+ this.defaultLockTTL = config.defaultLockTTL || 5 * 60;
9
+ }
10
+ async save(state) {
11
+ const contextDir = this.getContextDir(state.agentId, state.contextId);
12
+ const filePath = join(contextDir, 'context.json');
13
+ await mkdir(contextDir, { recursive: true });
14
+ const now = new Date().toISOString();
15
+ const stateWithTimestamps = {
16
+ ...state,
17
+ updatedAt: now,
18
+ lastActivityAt: now,
19
+ };
20
+ await writeFile(filePath, JSON.stringify(stateWithTimestamps, null, 2), 'utf-8');
21
+ }
22
+ async load(contextId) {
23
+ try {
24
+ const contextDirs = await this.findContextDirs(contextId);
25
+ if (contextDirs.length === 0) {
26
+ return null;
27
+ }
28
+ const filePath = join(contextDirs[0], 'context.json');
29
+ const content = await readFile(filePath, 'utf-8');
30
+ const state = JSON.parse(content);
31
+ if (state.lockExpiresAt && new Date(state.lockExpiresAt) < new Date()) {
32
+ state.lockedBy = undefined;
33
+ state.lockedAt = undefined;
34
+ state.lockExpiresAt = undefined;
35
+ await this.save(state);
36
+ }
37
+ return state;
38
+ }
39
+ catch (error) {
40
+ if (error.code === 'ENOENT') {
41
+ return null;
42
+ }
43
+ throw error;
44
+ }
45
+ }
46
+ async exists(contextId) {
47
+ const contextDirs = await this.findContextDirs(contextId);
48
+ return contextDirs.length > 0;
49
+ }
50
+ async delete(contextId) {
51
+ const contextDirs = await this.findContextDirs(contextId);
52
+ for (const contextDir of contextDirs) {
53
+ await rm(contextDir, { recursive: true, force: true });
54
+ }
55
+ }
56
+ async list(filter) {
57
+ const allContexts = [];
58
+ const agentDirs = filter?.agentId
59
+ ? [join(this.basePath, `agent=${filter.agentId}`)]
60
+ : await this.getAllAgentDirs();
61
+ for (const agentDir of agentDirs) {
62
+ try {
63
+ const entries = await readdir(agentDir, { withFileTypes: true });
64
+ for (const entry of entries) {
65
+ if (entry.isDirectory() && entry.name.startsWith('context=')) {
66
+ const contextDir = join(agentDir, entry.name);
67
+ const filePath = join(contextDir, 'context.json');
68
+ try {
69
+ const content = await readFile(filePath, 'utf-8');
70
+ const state = JSON.parse(content);
71
+ allContexts.push(state);
72
+ }
73
+ catch (error) {
74
+ if (error.code !== 'ENOENT') {
75
+ throw error;
76
+ }
77
+ }
78
+ }
79
+ }
80
+ }
81
+ catch (error) {
82
+ if (error.code !== 'ENOENT') {
83
+ throw error;
84
+ }
85
+ }
86
+ }
87
+ let filtered = allContexts;
88
+ if (filter?.ownerId) {
89
+ filtered = filtered.filter((c) => c.ownerId === filter.ownerId);
90
+ }
91
+ if (filter?.status) {
92
+ filtered = filtered.filter((c) => c.status === filter.status);
93
+ }
94
+ if (filter?.tags && filter.tags.length > 0) {
95
+ filtered = filtered.filter((c) => filter.tags?.some((tag) => c.tags?.includes(tag)));
96
+ }
97
+ if (filter?.createdAfter) {
98
+ filtered = filtered.filter((c) => new Date(c.createdAt) >= filter.createdAfter);
99
+ }
100
+ if (filter?.createdBefore) {
101
+ filtered = filtered.filter((c) => new Date(c.createdAt) <= filter.createdBefore);
102
+ }
103
+ if (filter?.updatedAfter) {
104
+ filtered = filtered.filter((c) => new Date(c.updatedAt) >= filter.updatedAfter);
105
+ }
106
+ filtered.sort((a, b) => new Date(b.lastActivityAt).getTime() - new Date(a.lastActivityAt).getTime());
107
+ const offset = filter?.offset || 0;
108
+ const limit = filter?.limit || filtered.length;
109
+ return filtered.slice(offset, offset + limit);
110
+ }
111
+ async search(query, filter) {
112
+ const allContexts = await this.list(filter);
113
+ const queryLower = query.toLowerCase();
114
+ return allContexts.filter((context) => {
115
+ const titleMatch = context.title?.toLowerCase().includes(queryLower);
116
+ const descriptionMatch = context.description?.toLowerCase().includes(queryLower);
117
+ const tagMatch = context.tags?.some((tag) => tag.toLowerCase().includes(queryLower));
118
+ return titleMatch || descriptionMatch || tagMatch;
119
+ });
120
+ }
121
+ async acquireLock(contextId, lockOwnerId, ttlSeconds) {
122
+ const state = await this.load(contextId);
123
+ if (!state) {
124
+ throw new Error(`Context not found: ${contextId}`);
125
+ }
126
+ if (state.lockedBy && state.lockedBy !== lockOwnerId) {
127
+ if (state.lockExpiresAt && new Date(state.lockExpiresAt) > new Date()) {
128
+ return false;
129
+ }
130
+ }
131
+ const now = new Date();
132
+ const ttl = ttlSeconds || this.defaultLockTTL;
133
+ const expiresAt = new Date(now.getTime() + ttl * 1000);
134
+ state.lockedBy = lockOwnerId;
135
+ state.lockedAt = now.toISOString();
136
+ state.lockExpiresAt = expiresAt.toISOString();
137
+ state.status = 'locked';
138
+ await this.save(state);
139
+ const contextDir = this.getContextDir(state.agentId, contextId);
140
+ const lockPath = join(contextDir, 'context.lock');
141
+ const lockFile = {
142
+ lockOwnerId,
143
+ lockedAt: state.lockedAt,
144
+ expiresAt: state.lockExpiresAt,
145
+ };
146
+ await writeFile(lockPath, JSON.stringify(lockFile, null, 2), 'utf-8');
147
+ return true;
148
+ }
149
+ async releaseLock(contextId, lockOwnerId) {
150
+ const state = await this.load(contextId);
151
+ if (!state) {
152
+ throw new Error(`Context not found: ${contextId}`);
153
+ }
154
+ if (state.lockedBy !== lockOwnerId) {
155
+ throw new Error(`Lock is owned by ${state.lockedBy}, cannot release`);
156
+ }
157
+ state.lockedBy = undefined;
158
+ state.lockedAt = undefined;
159
+ state.lockExpiresAt = undefined;
160
+ state.status = 'active';
161
+ await this.save(state);
162
+ const contextDir = this.getContextDir(state.agentId, contextId);
163
+ const lockPath = join(contextDir, 'context.lock');
164
+ await rm(lockPath, { force: true });
165
+ }
166
+ async refreshLock(contextId, lockOwnerId, ttlSeconds) {
167
+ const state = await this.load(contextId);
168
+ if (!state) {
169
+ throw new Error(`Context not found: ${contextId}`);
170
+ }
171
+ if (state.lockedBy !== lockOwnerId) {
172
+ return false;
173
+ }
174
+ const ttl = ttlSeconds || this.defaultLockTTL;
175
+ const expiresAt = new Date(Date.now() + ttl * 1000);
176
+ state.lockExpiresAt = expiresAt.toISOString();
177
+ await this.save(state);
178
+ const contextDir = this.getContextDir(state.agentId, contextId);
179
+ const lockPath = join(contextDir, 'context.lock');
180
+ const lockFile = {
181
+ lockOwnerId,
182
+ lockedAt: state.lockedAt,
183
+ expiresAt: state.lockExpiresAt,
184
+ };
185
+ await writeFile(lockPath, JSON.stringify(lockFile, null, 2), 'utf-8');
186
+ return true;
187
+ }
188
+ async isLocked(contextId) {
189
+ const state = await this.load(contextId);
190
+ if (!state || !state.lockedBy || !state.lockExpiresAt) {
191
+ return false;
192
+ }
193
+ return new Date(state.lockExpiresAt) > new Date();
194
+ }
195
+ async update(contextId, updates) {
196
+ const state = await this.load(contextId);
197
+ if (!state) {
198
+ throw new Error(`Context not found: ${contextId}`);
199
+ }
200
+ const updatedState = {
201
+ ...state,
202
+ ...updates,
203
+ contextId: state.contextId,
204
+ agentId: state.agentId,
205
+ createdAt: state.createdAt,
206
+ updatedAt: new Date().toISOString(),
207
+ };
208
+ await this.save(updatedState);
209
+ }
210
+ getContextDir(agentId, contextId) {
211
+ return join(this.basePath, `agent=${agentId}`, `context=${contextId}`);
212
+ }
213
+ async findContextDirs(contextId) {
214
+ const matches = [];
215
+ try {
216
+ const agentDirs = await this.getAllAgentDirs();
217
+ for (const agentDir of agentDirs) {
218
+ const contextDir = join(agentDir, `context=${contextId}`);
219
+ try {
220
+ await stat(contextDir);
221
+ matches.push(contextDir);
222
+ }
223
+ catch {
224
+ }
225
+ }
226
+ }
227
+ catch {
228
+ }
229
+ return matches;
230
+ }
231
+ async getAllAgentDirs() {
232
+ try {
233
+ const entries = await readdir(this.basePath, { withFileTypes: true });
234
+ return entries
235
+ .filter((entry) => entry.isDirectory() && entry.name.startsWith('agent='))
236
+ .map((entry) => join(this.basePath, entry.name));
237
+ }
238
+ catch (error) {
239
+ if (error.code === 'ENOENT') {
240
+ return [];
241
+ }
242
+ throw error;
243
+ }
244
+ }
245
+ }
@@ -0,0 +1,28 @@
1
+ import type { Message } from '../../types/message';
2
+ import type { CompactionOptions, CompactionResult, MessageStore } from '../messages/interfaces';
3
+ export interface FileSystemMessageStoreConfig {
4
+ basePath?: string;
5
+ agentId: string;
6
+ }
7
+ export declare class FileSystemMessageStore implements MessageStore {
8
+ private basePath;
9
+ private agentId;
10
+ private messageIndex;
11
+ constructor(config: FileSystemMessageStoreConfig);
12
+ append(contextId: string, messages: Message[]): Promise<void>;
13
+ getRecent(contextId: string, options?: {
14
+ maxMessages?: number;
15
+ maxTokens?: number;
16
+ }): Promise<Message[]>;
17
+ getAll(contextId: string): Promise<Message[]>;
18
+ getCount(contextId: string): Promise<number>;
19
+ getRange(contextId: string, startIndex: number, endIndex: number): Promise<Message[]>;
20
+ compact(contextId: string, options?: CompactionOptions): Promise<CompactionResult>;
21
+ clear(contextId: string): Promise<void>;
22
+ private getMessagesDir;
23
+ private getMessageFilename;
24
+ private loadMessages;
25
+ private toMessage;
26
+ private estimateTokens;
27
+ private sanitizeName;
28
+ }