byterover-cli 2.0.0 → 2.1.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 (181) hide show
  1. package/README.md +6 -81
  2. package/dist/agent/core/domain/llm/index.d.ts +1 -1
  3. package/dist/agent/core/domain/llm/index.js +1 -1
  4. package/dist/agent/core/domain/llm/registry.d.ts +8 -0
  5. package/dist/agent/core/domain/llm/registry.js +34 -0
  6. package/dist/agent/core/domain/sandbox/types.d.ts +2 -0
  7. package/dist/agent/core/domain/tools/constants.d.ts +3 -0
  8. package/dist/agent/core/domain/tools/constants.js +3 -0
  9. package/dist/agent/core/interfaces/cipher-services.d.ts +2 -4
  10. package/dist/agent/core/interfaces/i-cipher-agent.d.ts +9 -1
  11. package/dist/agent/core/interfaces/i-sandbox-service.d.ts +8 -0
  12. package/dist/agent/core/interfaces/i-tool-provider.d.ts +10 -0
  13. package/dist/agent/core/interfaces/i-tool-scheduler.d.ts +9 -0
  14. package/dist/agent/infra/agent/agent-schemas.d.ts +0 -9
  15. package/dist/agent/infra/agent/agent-schemas.js +0 -3
  16. package/dist/agent/infra/agent/cipher-agent.d.ts +25 -1
  17. package/dist/agent/infra/agent/cipher-agent.js +138 -11
  18. package/dist/agent/infra/agent/provider-update-config.d.ts +0 -2
  19. package/dist/agent/infra/agent/service-initializer.d.ts +2 -6
  20. package/dist/agent/infra/agent/service-initializer.js +45 -38
  21. package/dist/agent/infra/blob/blob-storage-factory.d.ts +2 -2
  22. package/dist/agent/infra/blob/blob-storage-factory.js +4 -4
  23. package/dist/agent/infra/blob/file-blob-storage.d.ts +96 -0
  24. package/dist/agent/infra/blob/file-blob-storage.js +454 -0
  25. package/dist/agent/infra/blob/index.d.ts +2 -3
  26. package/dist/agent/infra/blob/index.js +4 -6
  27. package/dist/agent/infra/llm/agent-llm-service.d.ts +3 -0
  28. package/dist/agent/infra/llm/agent-llm-service.js +34 -52
  29. package/dist/agent/infra/llm/context/compression/compression-helpers.d.ts +35 -0
  30. package/dist/agent/infra/llm/context/compression/compression-helpers.js +124 -0
  31. package/dist/agent/infra/llm/context/compression/escalated-compression.d.ts +62 -0
  32. package/dist/agent/infra/llm/context/compression/escalated-compression.js +144 -0
  33. package/dist/agent/infra/llm/context/compression/index.d.ts +3 -0
  34. package/dist/agent/infra/llm/context/compression/index.js +3 -0
  35. package/dist/agent/infra/llm/context/compression/reactive-overflow.d.ts +0 -27
  36. package/dist/agent/infra/llm/context/compression/reactive-overflow.js +5 -122
  37. package/dist/agent/infra/llm/context/context-manager.d.ts +20 -1
  38. package/dist/agent/infra/llm/context/context-manager.js +37 -7
  39. package/dist/agent/infra/llm/providers/index.js +0 -2
  40. package/dist/agent/infra/llm/providers/types.d.ts +1 -5
  41. package/dist/agent/infra/map/agentic-map-service.d.ts +97 -0
  42. package/dist/agent/infra/map/agentic-map-service.js +309 -0
  43. package/dist/agent/infra/map/context-tree-store.d.ts +94 -0
  44. package/dist/agent/infra/map/context-tree-store.js +278 -0
  45. package/dist/agent/infra/map/index.d.ts +4 -0
  46. package/dist/agent/infra/map/index.js +4 -0
  47. package/dist/agent/infra/map/llm-map-memory.d.ts +59 -0
  48. package/dist/agent/infra/map/llm-map-memory.js +187 -0
  49. package/dist/agent/infra/map/llm-map-service.d.ts +36 -0
  50. package/dist/agent/infra/map/llm-map-service.js +118 -0
  51. package/dist/agent/infra/map/map-shared.d.ts +140 -0
  52. package/dist/agent/infra/map/map-shared.js +325 -0
  53. package/dist/agent/infra/map/worker-pool.d.ts +45 -0
  54. package/dist/agent/infra/map/worker-pool.js +73 -0
  55. package/dist/agent/infra/sandbox/curation-helpers.d.ts +62 -0
  56. package/dist/agent/infra/sandbox/curation-helpers.js +219 -0
  57. package/dist/agent/infra/sandbox/sandbox-service.d.ts +12 -0
  58. package/dist/agent/infra/sandbox/sandbox-service.js +39 -7
  59. package/dist/agent/infra/sandbox/tools-sdk.d.ts +48 -1
  60. package/dist/agent/infra/sandbox/tools-sdk.js +52 -1
  61. package/dist/agent/infra/session/session-manager.d.ts +8 -1
  62. package/dist/agent/infra/session/session-manager.js +24 -4
  63. package/dist/agent/infra/storage/file-key-storage.d.ts +142 -0
  64. package/dist/agent/infra/storage/file-key-storage.js +572 -0
  65. package/dist/agent/infra/storage/granular-history-storage.d.ts +1 -1
  66. package/dist/agent/infra/storage/granular-history-storage.js +1 -1
  67. package/dist/agent/infra/system-prompt/contributors/context-tree-structure-contributor.d.ts +4 -0
  68. package/dist/agent/infra/system-prompt/contributors/context-tree-structure-contributor.js +42 -14
  69. package/dist/agent/infra/system-prompt/contributors/map-selection-contributor.d.ts +16 -0
  70. package/dist/agent/infra/system-prompt/contributors/map-selection-contributor.js +47 -0
  71. package/dist/agent/infra/tools/core-tool-scheduler.js +3 -1
  72. package/dist/agent/infra/tools/implementations/agentic-map-tool.d.ts +35 -0
  73. package/dist/agent/infra/tools/implementations/agentic-map-tool.js +156 -0
  74. package/dist/agent/infra/tools/implementations/code-exec-tool.js +1 -0
  75. package/dist/agent/infra/tools/implementations/curate-tool.d.ts +9 -9
  76. package/dist/agent/infra/tools/implementations/expand-knowledge-tool.d.ts +18 -0
  77. package/dist/agent/infra/tools/implementations/expand-knowledge-tool.js +43 -0
  78. package/dist/agent/infra/tools/implementations/llm-map-tool.d.ts +24 -0
  79. package/dist/agent/infra/tools/implementations/llm-map-tool.js +87 -0
  80. package/dist/agent/infra/tools/implementations/memory-symbol-tree.d.ts +28 -1
  81. package/dist/agent/infra/tools/implementations/memory-symbol-tree.js +27 -3
  82. package/dist/agent/infra/tools/implementations/search-knowledge-service.d.ts +1 -0
  83. package/dist/agent/infra/tools/implementations/search-knowledge-service.js +83 -12
  84. package/dist/agent/infra/tools/implementations/search-knowledge-tool.js +2 -2
  85. package/dist/agent/infra/tools/tool-manager.js +6 -0
  86. package/dist/agent/infra/tools/tool-provider.d.ts +12 -0
  87. package/dist/agent/infra/tools/tool-provider.js +78 -0
  88. package/dist/agent/infra/tools/tool-registry.d.ts +14 -0
  89. package/dist/agent/infra/tools/tool-registry.js +32 -0
  90. package/dist/agent/resources/prompts/system-prompt.yml +48 -74
  91. package/dist/agent/resources/tools/expand_knowledge.txt +20 -0
  92. package/dist/oclif/commands/curate/index.js +1 -2
  93. package/dist/oclif/commands/main.js +1 -0
  94. package/dist/oclif/commands/providers/connect.d.ts +1 -3
  95. package/dist/oclif/commands/providers/connect.js +7 -29
  96. package/dist/oclif/commands/query.js +1 -2
  97. package/dist/server/constants.d.ts +7 -0
  98. package/dist/server/constants.js +8 -0
  99. package/dist/server/core/domain/entities/provider-registry.js +1 -15
  100. package/dist/server/core/domain/knowledge/memory-scoring.js +1 -1
  101. package/dist/server/core/domain/knowledge/summary-types.d.ts +126 -0
  102. package/dist/server/core/domain/knowledge/summary-types.js +7 -0
  103. package/dist/server/core/domain/transport/schemas.d.ts +0 -4
  104. package/dist/server/core/interfaces/context-tree/i-context-tree-archive-service.d.ts +30 -0
  105. package/dist/server/core/interfaces/context-tree/i-context-tree-archive-service.js +1 -0
  106. package/dist/server/core/interfaces/context-tree/i-context-tree-manifest-service.d.ts +30 -0
  107. package/dist/server/core/interfaces/context-tree/i-context-tree-manifest-service.js +1 -0
  108. package/dist/server/core/interfaces/context-tree/i-context-tree-summary-service.d.ts +29 -0
  109. package/dist/server/core/interfaces/context-tree/i-context-tree-summary-service.js +1 -0
  110. package/dist/server/infra/cogit/context-tree-to-push-context-mapper.js +10 -3
  111. package/dist/server/infra/connectors/skill/skill-connector.d.ts +4 -0
  112. package/dist/server/infra/connectors/skill/skill-connector.js +4 -0
  113. package/dist/server/infra/context-tree/children-hash.d.ts +20 -0
  114. package/dist/server/infra/context-tree/children-hash.js +22 -0
  115. package/dist/server/infra/context-tree/derived-artifact.d.ts +28 -0
  116. package/dist/server/infra/context-tree/derived-artifact.js +48 -0
  117. package/dist/server/infra/context-tree/file-context-tree-archive-service.d.ts +37 -0
  118. package/dist/server/infra/context-tree/file-context-tree-archive-service.js +219 -0
  119. package/dist/server/infra/context-tree/file-context-tree-manifest-service.d.ts +50 -0
  120. package/dist/server/infra/context-tree/file-context-tree-manifest-service.js +278 -0
  121. package/dist/server/infra/context-tree/file-context-tree-merger.js +4 -0
  122. package/dist/server/infra/context-tree/file-context-tree-snapshot-service.js +12 -4
  123. package/dist/server/infra/context-tree/file-context-tree-summary-service.d.ts +44 -0
  124. package/dist/server/infra/context-tree/file-context-tree-summary-service.js +313 -0
  125. package/dist/server/infra/context-tree/file-context-tree-writer-service.js +5 -0
  126. package/dist/server/infra/context-tree/prompts/summary-generation.d.ts +22 -0
  127. package/dist/server/infra/context-tree/prompts/summary-generation.js +45 -0
  128. package/dist/server/infra/context-tree/snapshot-diff.d.ts +19 -0
  129. package/dist/server/infra/context-tree/snapshot-diff.js +39 -0
  130. package/dist/server/infra/context-tree/summary-frontmatter.d.ts +24 -0
  131. package/dist/server/infra/context-tree/summary-frontmatter.js +111 -0
  132. package/dist/server/infra/daemon/agent-process.js +2 -14
  133. package/dist/server/infra/executor/curate-executor.d.ts +1 -0
  134. package/dist/server/infra/executor/curate-executor.js +82 -34
  135. package/dist/server/infra/executor/folder-pack-executor.js +1 -1
  136. package/dist/server/infra/executor/pre-compaction/compaction-escalation.d.ts +6 -0
  137. package/dist/server/infra/executor/pre-compaction/compaction-escalation.js +6 -0
  138. package/dist/server/infra/executor/pre-compaction/index.d.ts +3 -0
  139. package/dist/server/infra/executor/pre-compaction/index.js +1 -0
  140. package/dist/server/infra/executor/pre-compaction/pre-compaction-service.d.ts +59 -0
  141. package/dist/server/infra/executor/pre-compaction/pre-compaction-service.js +124 -0
  142. package/dist/server/infra/executor/pre-compaction/prompts.d.ts +24 -0
  143. package/dist/server/infra/executor/pre-compaction/prompts.js +47 -0
  144. package/dist/server/infra/executor/query-executor.d.ts +3 -0
  145. package/dist/server/infra/executor/query-executor.js +39 -4
  146. package/dist/server/infra/http/authenticated-http-client.js +4 -0
  147. package/dist/server/infra/http/provider-model-fetcher-registry.js +1 -5
  148. package/dist/server/infra/http/provider-model-fetchers.d.ts +0 -14
  149. package/dist/server/infra/http/provider-model-fetchers.js +0 -132
  150. package/dist/server/infra/provider/provider-config-resolver.js +0 -55
  151. package/dist/server/utils/curate-result-parser.d.ts +4 -4
  152. package/dist/shared/constants/curation.d.ts +6 -0
  153. package/dist/shared/constants/curation.js +6 -0
  154. package/dist/shared/utils/escalation-utils.d.ts +59 -0
  155. package/dist/shared/utils/escalation-utils.js +141 -0
  156. package/dist/tui/components/command-input.js +1 -1
  157. package/dist/tui/components/inline-prompts/inline-confirm.js +6 -1
  158. package/dist/tui/features/commands/definitions/exit.d.ts +2 -0
  159. package/dist/tui/features/commands/definitions/exit.js +9 -0
  160. package/dist/tui/features/commands/definitions/index.js +3 -0
  161. package/dist/tui/features/exit/components/exit-flow.d.ts +10 -0
  162. package/dist/tui/features/exit/components/exit-flow.js +19 -0
  163. package/dist/tui/features/provider/components/provider-flow.js +1 -21
  164. package/oclif.manifest.json +100 -109
  165. package/package.json +11 -4
  166. package/dist/agent/infra/blob/migrations.d.ts +0 -63
  167. package/dist/agent/infra/blob/migrations.js +0 -148
  168. package/dist/agent/infra/blob/sqlite-blob-storage.d.ts +0 -82
  169. package/dist/agent/infra/blob/sqlite-blob-storage.js +0 -307
  170. package/dist/agent/infra/llm/providers/google-vertex.d.ts +0 -15
  171. package/dist/agent/infra/llm/providers/google-vertex.js +0 -36
  172. package/dist/agent/infra/storage/blob-history-storage.d.ts +0 -81
  173. package/dist/agent/infra/storage/blob-history-storage.js +0 -193
  174. package/dist/agent/infra/storage/dual-format-history-storage.d.ts +0 -83
  175. package/dist/agent/infra/storage/dual-format-history-storage.js +0 -165
  176. package/dist/agent/infra/storage/sqlite-key-storage.d.ts +0 -113
  177. package/dist/agent/infra/storage/sqlite-key-storage.js +0 -438
  178. package/dist/server/infra/provider/vertex-ai-utils.d.ts +0 -10
  179. package/dist/server/infra/provider/vertex-ai-utils.js +0 -28
  180. package/dist/tui/features/provider/components/credential-path-dialog.d.ts +0 -30
  181. package/dist/tui/features/provider/components/credential-path-dialog.js +0 -85
@@ -0,0 +1,454 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import * as fs from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { BlobError } from '../../core/domain/errors/blob-error.js';
5
+ /**
6
+ * File-based blob storage implementation.
7
+ *
8
+ * Stores blobs as individual files on the filesystem:
9
+ * {storageDir}/blobs/{key}/content.bin — binary content
10
+ * {storageDir}/blobs/{key}/metadata.json — JSON metadata
11
+ *
12
+ * Features:
13
+ * - One directory per blob (O(1) read/write/delete)
14
+ * - Atomic writes via write-to-temp + rename
15
+ * - Size limit enforcement (per-blob and total)
16
+ * - In-memory mode for fast unit tests
17
+ * - Prefix-based listing via readdir
18
+ */
19
+ export class FileBlobStorage {
20
+ baseDir;
21
+ initialized = false;
22
+ inMemory;
23
+ maxBlobSize;
24
+ maxTotalSize;
25
+ memoryStore = null;
26
+ storageDir;
27
+ constructor(config) {
28
+ this.inMemory = config?.inMemory ?? false;
29
+ this.storageDir = config?.storageDir ?? '';
30
+ if (!this.inMemory && !this.storageDir) {
31
+ throw new Error('FileBlobStorage: storageDir is required when inMemory is false');
32
+ }
33
+ this.baseDir = join(this.storageDir, 'blobs');
34
+ this.maxBlobSize = config?.maxBlobSize ?? 100 * 1024 * 1024; // 100MB default
35
+ this.maxTotalSize = config?.maxTotalSize ?? 1024 * 1024 * 1024; // 1GB default
36
+ }
37
+ /**
38
+ * Clear all blobs from storage.
39
+ */
40
+ async clear() {
41
+ this.ensureInitialized();
42
+ try {
43
+ if (this.inMemory) {
44
+ this.memoryStore.clear();
45
+ return;
46
+ }
47
+ // Read all entries and remove each directory
48
+ let entries;
49
+ try {
50
+ entries = await fs.readdir(this.baseDir, { withFileTypes: true });
51
+ }
52
+ catch {
53
+ return; // Nothing to clear
54
+ }
55
+ for (const entry of entries) {
56
+ if (entry.isDirectory()) {
57
+ const dirPath = join(this.baseDir, entry.name);
58
+ // eslint-disable-next-line no-await-in-loop
59
+ await fs.rm(dirPath, { force: true, recursive: true });
60
+ }
61
+ }
62
+ }
63
+ catch (error) {
64
+ if (error instanceof BlobError) {
65
+ throw error;
66
+ }
67
+ throw BlobError.deleteError(`Failed to clear storage: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
68
+ }
69
+ }
70
+ /**
71
+ * Close the storage. Releases in-memory data.
72
+ */
73
+ close() {
74
+ if (this.inMemory) {
75
+ this.memoryStore = null;
76
+ }
77
+ this.initialized = false;
78
+ }
79
+ /**
80
+ * Delete a blob by its key.
81
+ */
82
+ async delete(key) {
83
+ this.ensureInitialized();
84
+ this.validateKey(key);
85
+ if (this.inMemory) {
86
+ if (!this.memoryStore.has(key)) {
87
+ throw BlobError.notFound(key);
88
+ }
89
+ this.memoryStore.delete(key);
90
+ return;
91
+ }
92
+ const blobDir = join(this.baseDir, key);
93
+ try {
94
+ await fs.access(blobDir);
95
+ }
96
+ catch {
97
+ throw BlobError.notFound(key);
98
+ }
99
+ try {
100
+ await fs.rm(blobDir, { force: true, recursive: true });
101
+ }
102
+ catch (error) {
103
+ throw BlobError.deleteError(`Failed to delete blob ${key}: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
104
+ }
105
+ }
106
+ /**
107
+ * Check if a blob exists.
108
+ */
109
+ async exists(key) {
110
+ this.ensureInitialized();
111
+ this.validateKey(key);
112
+ if (this.inMemory) {
113
+ return this.memoryStore.has(key);
114
+ }
115
+ try {
116
+ await fs.access(join(this.baseDir, key, 'metadata.json'));
117
+ return true;
118
+ }
119
+ catch {
120
+ return false;
121
+ }
122
+ }
123
+ /**
124
+ * Get metadata for a blob without retrieving its content.
125
+ */
126
+ async getMetadata(key) {
127
+ this.ensureInitialized();
128
+ this.validateKey(key);
129
+ try {
130
+ if (this.inMemory) {
131
+ const entry = this.memoryStore.get(key);
132
+ return entry ? this.toPublicMetadata(entry.metadata) : undefined;
133
+ }
134
+ return this.readMetadataFromDisk(key);
135
+ }
136
+ catch (error) {
137
+ if (error instanceof BlobError) {
138
+ throw error;
139
+ }
140
+ throw BlobError.retrievalError(`Failed to read metadata for blob ${key}: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
141
+ }
142
+ }
143
+ /**
144
+ * Get storage statistics.
145
+ */
146
+ async getStats() {
147
+ this.ensureInitialized();
148
+ try {
149
+ if (this.inMemory) {
150
+ let totalSize = 0;
151
+ for (const entry of this.memoryStore.values()) {
152
+ totalSize += entry.metadata.size;
153
+ }
154
+ return {
155
+ lastUpdated: Date.now(),
156
+ totalBlobs: this.memoryStore.size,
157
+ totalSize,
158
+ };
159
+ }
160
+ return this.getStatsFromDisk();
161
+ }
162
+ catch (error) {
163
+ if (error instanceof BlobError) {
164
+ throw error;
165
+ }
166
+ throw BlobError.retrievalError(`Failed to get storage stats: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
167
+ }
168
+ }
169
+ /**
170
+ * Initialize the storage backend.
171
+ */
172
+ async initialize() {
173
+ if (this.initialized) {
174
+ return;
175
+ }
176
+ try {
177
+ if (this.inMemory) {
178
+ this.memoryStore = new Map();
179
+ }
180
+ else {
181
+ await fs.mkdir(this.baseDir, { recursive: true });
182
+ }
183
+ this.initialized = true;
184
+ }
185
+ catch (error) {
186
+ throw BlobError.initializationError(`Failed to initialize file blob storage: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
187
+ }
188
+ }
189
+ /**
190
+ * List all blob keys, optionally filtered by prefix.
191
+ */
192
+ async list(prefix) {
193
+ this.ensureInitialized();
194
+ try {
195
+ if (this.inMemory) {
196
+ let keys = [...this.memoryStore.keys()];
197
+ if (prefix) {
198
+ keys = keys.filter((k) => k.startsWith(prefix));
199
+ }
200
+ keys.sort();
201
+ return keys;
202
+ }
203
+ return this.listFromDisk(prefix);
204
+ }
205
+ catch (error) {
206
+ if (error instanceof BlobError) {
207
+ throw error;
208
+ }
209
+ throw BlobError.retrievalError(`Failed to list blobs: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
210
+ }
211
+ }
212
+ /**
213
+ * Retrieve a blob by its key.
214
+ */
215
+ async retrieve(key) {
216
+ this.ensureInitialized();
217
+ this.validateKey(key);
218
+ try {
219
+ if (this.inMemory) {
220
+ const entry = this.memoryStore.get(key);
221
+ if (!entry) {
222
+ return undefined;
223
+ }
224
+ return {
225
+ content: entry.content,
226
+ key,
227
+ metadata: this.toPublicMetadata(entry.metadata),
228
+ };
229
+ }
230
+ return this.retrieveFromDisk(key);
231
+ }
232
+ catch (error) {
233
+ if (error instanceof BlobError) {
234
+ throw error;
235
+ }
236
+ throw BlobError.retrievalError(`Failed to retrieve blob ${key}: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
237
+ }
238
+ }
239
+ /**
240
+ * Store a blob with optional metadata.
241
+ */
242
+ async store(key, content, metadata) {
243
+ this.ensureInitialized();
244
+ this.validateKey(key);
245
+ const buffer = Buffer.isBuffer(content) ? content : Buffer.from(content);
246
+ // Check individual blob size
247
+ if (buffer.length > this.maxBlobSize) {
248
+ throw BlobError.tooLarge(buffer.length, this.maxBlobSize);
249
+ }
250
+ // Check total storage size
251
+ const stats = await this.getStats();
252
+ const existing = await this.retrieve(key);
253
+ const existingSize = existing?.metadata.size ?? 0;
254
+ const newTotalSize = stats.totalSize - existingSize + buffer.length;
255
+ if (newTotalSize > this.maxTotalSize) {
256
+ throw BlobError.totalSizeExceeded(stats.totalSize - existingSize, buffer.length, this.maxTotalSize);
257
+ }
258
+ const now = Date.now();
259
+ const fullMetadata = {
260
+ createdAt: existing?.metadata.createdAt ?? now,
261
+ size: buffer.length,
262
+ updatedAt: now,
263
+ ...metadata,
264
+ };
265
+ const storedMeta = {
266
+ createdAt: fullMetadata.createdAt,
267
+ size: buffer.length,
268
+ updatedAt: fullMetadata.updatedAt,
269
+ };
270
+ if (fullMetadata.contentType) {
271
+ storedMeta.contentType = fullMetadata.contentType;
272
+ }
273
+ if (fullMetadata.originalName) {
274
+ storedMeta.originalName = fullMetadata.originalName;
275
+ }
276
+ if (fullMetadata.tags) {
277
+ storedMeta.tags = fullMetadata.tags;
278
+ }
279
+ try {
280
+ if (this.inMemory) {
281
+ this.memoryStore.set(key, { content: buffer, metadata: storedMeta });
282
+ }
283
+ else {
284
+ await this.writeToDisk(key, buffer, storedMeta);
285
+ }
286
+ return {
287
+ content: buffer,
288
+ key,
289
+ metadata: fullMetadata,
290
+ };
291
+ }
292
+ catch (error) {
293
+ if (error instanceof BlobError) {
294
+ throw error;
295
+ }
296
+ throw BlobError.storageError(`Failed to store blob ${key}: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
297
+ }
298
+ }
299
+ // ---------------------------------------------------------------------------
300
+ // Private helpers
301
+ // ---------------------------------------------------------------------------
302
+ ensureInitialized() {
303
+ if (!this.initialized) {
304
+ throw BlobError.notInitialized();
305
+ }
306
+ }
307
+ /**
308
+ * Get stats by scanning the blobs directory.
309
+ */
310
+ async getStatsFromDisk() {
311
+ let totalBlobs = 0;
312
+ let totalSize = 0;
313
+ let entries;
314
+ try {
315
+ entries = await fs.readdir(this.baseDir, { withFileTypes: true });
316
+ }
317
+ catch {
318
+ return { lastUpdated: Date.now(), totalBlobs: 0, totalSize: 0 };
319
+ }
320
+ for (const entry of entries) {
321
+ if (entry.isDirectory()) {
322
+ try {
323
+ const metaPath = join(this.baseDir, entry.name, 'metadata.json');
324
+ // eslint-disable-next-line no-await-in-loop
325
+ const metaContent = await fs.readFile(metaPath, 'utf8');
326
+ const meta = JSON.parse(metaContent);
327
+ totalBlobs++;
328
+ totalSize += meta.size;
329
+ }
330
+ catch {
331
+ // Skip corrupt entries
332
+ }
333
+ }
334
+ }
335
+ return { lastUpdated: Date.now(), totalBlobs, totalSize };
336
+ }
337
+ /**
338
+ * List keys from disk, optionally filtered by prefix.
339
+ */
340
+ async listFromDisk(prefix) {
341
+ let entries;
342
+ try {
343
+ entries = await fs.readdir(this.baseDir, { withFileTypes: true });
344
+ }
345
+ catch {
346
+ return [];
347
+ }
348
+ let keys = entries
349
+ .filter((e) => e.isDirectory())
350
+ .map((e) => e.name);
351
+ if (prefix) {
352
+ keys = keys.filter((k) => k.startsWith(prefix));
353
+ }
354
+ keys.sort();
355
+ return keys;
356
+ }
357
+ /**
358
+ * Read metadata from disk for a given key.
359
+ */
360
+ async readMetadataFromDisk(key) {
361
+ const metaPath = join(this.baseDir, key, 'metadata.json');
362
+ try {
363
+ const content = await fs.readFile(metaPath, 'utf8');
364
+ const stored = JSON.parse(content);
365
+ return this.toPublicMetadata(stored);
366
+ }
367
+ catch (error) {
368
+ if (error.code === 'ENOENT') {
369
+ return undefined;
370
+ }
371
+ throw error;
372
+ }
373
+ }
374
+ /**
375
+ * Retrieve a blob from disk.
376
+ */
377
+ async retrieveFromDisk(key) {
378
+ const blobDir = join(this.baseDir, key);
379
+ const contentPath = join(blobDir, 'content.bin');
380
+ const metaPath = join(blobDir, 'metadata.json');
381
+ try {
382
+ const [contentBuf, metaContent] = await Promise.all([
383
+ fs.readFile(contentPath),
384
+ fs.readFile(metaPath, 'utf8'),
385
+ ]);
386
+ const stored = JSON.parse(metaContent);
387
+ return {
388
+ content: contentBuf,
389
+ key,
390
+ metadata: this.toPublicMetadata(stored),
391
+ };
392
+ }
393
+ catch (error) {
394
+ if (error.code === 'ENOENT') {
395
+ return undefined;
396
+ }
397
+ throw error;
398
+ }
399
+ }
400
+ /**
401
+ * Convert StoredMetadata to public BlobMetadata.
402
+ */
403
+ toPublicMetadata(stored) {
404
+ const metadata = {
405
+ createdAt: stored.createdAt,
406
+ size: stored.size,
407
+ updatedAt: stored.updatedAt,
408
+ };
409
+ if (stored.contentType) {
410
+ metadata.contentType = stored.contentType;
411
+ }
412
+ if (stored.originalName) {
413
+ metadata.originalName = stored.originalName;
414
+ }
415
+ if (stored.tags) {
416
+ metadata.tags = stored.tags;
417
+ }
418
+ return metadata;
419
+ }
420
+ /**
421
+ * Validate blob key.
422
+ * Keys must be alphanumeric with hyphens and underscores only.
423
+ */
424
+ validateKey(key) {
425
+ if (!key || typeof key !== 'string') {
426
+ throw BlobError.invalidKey(String(key), 'Key must be a non-empty string');
427
+ }
428
+ if (key.length === 0) {
429
+ throw BlobError.invalidKey(key, 'Key cannot be empty');
430
+ }
431
+ const validKeyRegex = /^[a-zA-Z0-9_-]+$/;
432
+ if (!validKeyRegex.test(key)) {
433
+ throw BlobError.invalidKey(key, 'Key must contain only alphanumeric characters, hyphens, and underscores');
434
+ }
435
+ }
436
+ /**
437
+ * Write blob content and metadata to disk atomically.
438
+ */
439
+ async writeToDisk(key, content, metadata) {
440
+ const blobDir = join(this.baseDir, key);
441
+ await fs.mkdir(blobDir, { recursive: true });
442
+ const contentPath = join(blobDir, 'content.bin');
443
+ const metaPath = join(blobDir, 'metadata.json');
444
+ const tmpSuffix = `.tmp.${randomUUID()}`;
445
+ // Write content atomically
446
+ const tmpContentPath = `${contentPath}${tmpSuffix}`;
447
+ await fs.writeFile(tmpContentPath, content);
448
+ await fs.rename(tmpContentPath, contentPath);
449
+ // Write metadata atomically
450
+ const tmpMetaPath = `${metaPath}${tmpSuffix}`;
451
+ await fs.writeFile(tmpMetaPath, JSON.stringify(metadata, null, 2), 'utf8');
452
+ await fs.rename(tmpMetaPath, metaPath);
453
+ }
454
+ }
@@ -1,10 +1,9 @@
1
1
  /**
2
2
  * Blob storage module
3
- * Provides persistent storage for binary/large data blobs using SQLite
3
+ * Provides persistent storage for binary/large data blobs
4
4
  */
5
5
  export type { BlobMetadata, BlobStats, BlobStorageConfig, StoredBlob } from '../../core/domain/blob/types.js';
6
6
  export { BlobError, BlobErrorCode } from '../../core/domain/errors/blob-error.js';
7
7
  export type { IBlobStorage } from '../../core/interfaces/i-blob-storage.js';
8
8
  export { createBlobStorage } from './blob-storage-factory.js';
9
- export * as BlobMigrations from './migrations.js';
10
- export { SqliteBlobStorage } from './sqlite-blob-storage.js';
9
+ export { FileBlobStorage } from './file-blob-storage.js';
@@ -1,12 +1,10 @@
1
1
  /**
2
2
  * Blob storage module
3
- * Provides persistent storage for binary/large data blobs using SQLite
3
+ * Provides persistent storage for binary/large data blobs
4
4
  */
5
5
  // Re-export errors from core
6
6
  export { BlobError, BlobErrorCode } from '../../core/domain/errors/blob-error.js';
7
- // Factory (always returns SQLite implementation)
7
+ // Factory (returns file-based implementation)
8
8
  export { createBlobStorage } from './blob-storage-factory.js';
9
- // Migration system
10
- export * as BlobMigrations from './migrations.js';
11
- // SQLite storage implementation
12
- export { SqliteBlobStorage } from './sqlite-blob-storage.js';
9
+ // File-based storage implementation
10
+ export { FileBlobStorage } from './file-blob-storage.js';
@@ -8,6 +8,7 @@ import type { MemoryManager } from '../memory/memory-manager.js';
8
8
  import type { SystemPromptManager } from '../system-prompt/system-prompt-manager.js';
9
9
  import type { ToolManager } from '../tools/tool-manager.js';
10
10
  import type { CompactionService } from './context/compaction/compaction-service.js';
11
+ import type { ICompressionStrategy } from './context/compression/types.js';
11
12
  import { type IContentGenerator } from '../../core/interfaces/i-content-generator.js';
12
13
  import { SessionEventBus } from '../events/event-emitter.js';
13
14
  import { ContextManager, type FileData, type ImageData } from './context/context-manager.js';
@@ -122,6 +123,8 @@ export declare class AgentLLMService implements ILLMService {
122
123
  */
123
124
  constructor(sessionId: string, generator: IContentGenerator, config: AgentLLMServiceConfig, options: {
124
125
  compactionService?: CompactionService;
126
+ /** Optional compression strategies for context overflow management */
127
+ compressionStrategies?: ICompressionStrategy[];
125
128
  historyStorage?: IHistoryStorage;
126
129
  logger?: ILogger;
127
130
  memoryManager?: MemoryManager;
@@ -2,7 +2,7 @@ import { getErrorMessage } from '../../../server/utils/error-helpers.js';
2
2
  import { AgentStateMachine } from '../../core/domain/agent/agent-state-machine.js';
3
3
  import { AgentState, TerminationReason } from '../../core/domain/agent/agent-state.js';
4
4
  import { LlmGenerationError, LlmMaxIterationsError, LlmResponseParsingError } from '../../core/domain/errors/llm-error.js';
5
- import { getEffectiveMaxInputTokens, getMaxInputTokensForModel, getProviderFromModel, isValidProviderModel, safeParseLLMConfig, } from '../../core/domain/llm/index.js';
5
+ import { getEffectiveMaxInputTokens, getMaxInputTokensForModel, isValidProviderModel, resolveRegistryProvider, safeParseLLMConfig, } from '../../core/domain/llm/index.js';
6
6
  import { StreamChunkType, } from '../../core/interfaces/i-content-generator.js';
7
7
  import { NoOpLogger } from '../../core/interfaces/i-logger.js';
8
8
  import { EnvironmentContextBuilder } from '../environment/environment-context-builder.js';
@@ -141,6 +141,7 @@ export class AgentLLMService {
141
141
  }
142
142
  // Initialize context manager with optional history storage
143
143
  this.contextManager = new ContextManager({
144
+ compressionStrategies: options.compressionStrategies,
144
145
  formatter: this.formatter,
145
146
  historyStorage: options.historyStorage,
146
147
  maxInputTokens: this.config.maxInputTokens,
@@ -558,33 +559,7 @@ export class AgentLLMService {
558
559
  * @returns Provider type ('claude', 'gemini', or 'openai')
559
560
  */
560
561
  detectProviderType(model, explicitProvider) {
561
- // 1. Explicit provider mapping takes priority
562
- if (explicitProvider) {
563
- if (explicitProvider === 'anthropic')
564
- return 'claude';
565
- if (explicitProvider === 'google' || explicitProvider === 'google-vertex')
566
- return 'gemini';
567
- if (['groq', 'mistral', 'openai', 'openai-compatible', 'openrouter', 'xai'].includes(explicitProvider))
568
- return 'openai';
569
- }
570
- // 2. Use registry to detect provider from model name
571
- const registryProvider = getProviderFromModel(model);
572
- if (registryProvider === 'claude')
573
- return 'claude';
574
- if (registryProvider === 'gemini')
575
- return 'gemini';
576
- if (registryProvider === 'openai')
577
- return 'openai';
578
- // 3. Fallback to string prefix matching for unknown models
579
- const lowerModel = model.toLowerCase();
580
- if (lowerModel.startsWith('claude'))
581
- return 'claude';
582
- if (lowerModel.startsWith('gpt') ||
583
- lowerModel.startsWith('o1') ||
584
- lowerModel.startsWith('o3') ||
585
- lowerModel.startsWith('o4'))
586
- return 'openai';
587
- return 'gemini';
562
+ return resolveRegistryProvider(model, explicitProvider);
588
563
  }
589
564
  /**
590
565
  * Determine which reflection prompt to add based on hierarchical priority.
@@ -728,31 +703,38 @@ export class AgentLLMService {
728
703
  const maxMessageTokens = this.config.maxInputTokens - systemPromptTokens;
729
704
  // Target utilization to leave headroom for response
730
705
  const targetMessageTokens = Math.floor(maxMessageTokens * TARGET_MESSAGE_TOKEN_UTILIZATION);
731
- // Smart pruning: try clearing old tool outputs before destructive compression
732
- this.contextManager.markToolOutputsCompacted(2);
733
- // Get token counts (recalculate if pruning changed content)
706
+ // Count current token usage
734
707
  const currentMessages = this.contextManager.getMessages();
735
- const effectiveTokenCounts = currentMessages.map((msg) => this.generator.estimateTokensSync(typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)));
736
- // Destructive compression as fallback if still over limit
737
- this.contextManager.compressMessage(targetMessageTokens, effectiveTokenCounts);
738
- // Emergency guard: if still over 90% after normal compression, force aggressive compaction
739
- // This is critical for curate/query commands where there's only 1 user turn,
740
- // making the protectedTurns=2 in markToolOutputsCompacted() ineffective.
741
- if (executionContext?.commandType === 'curate' || executionContext?.commandType === 'query') {
742
- const postCompressionMessages = this.contextManager.getMessages();
743
- const postCompressionTokens = postCompressionMessages.reduce((sum, msg) => sum + this.generator.estimateTokensSync(typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)), 0);
744
- const totalWithSystem = postCompressionTokens + systemPromptTokens;
745
- if (totalWithSystem > this.config.maxInputTokens * 0.9) {
746
- // Aggressive: compact ALL tool outputs (protect 0 turns instead of 2)
747
- this.contextManager.markToolOutputsCompacted(0);
748
- // Recalculate and re-compress
749
- const aggressiveMessages = this.contextManager.getMessages();
750
- const aggressiveTokens = aggressiveMessages.map((msg) => this.generator.estimateTokensSync(typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)));
751
- this.contextManager.compressMessage(targetMessageTokens, aggressiveTokens);
752
- this.sessionEventBus.emit('llmservice:warning', {
753
- message: `Emergency context compression triggered (${Math.round((totalWithSystem / this.config.maxInputTokens) * 100)}% utilization)`,
754
- taskId,
755
- });
708
+ const currentTokens = currentMessages.reduce((sum, msg) => sum + this.generator.estimateTokensSync(typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)), 0);
709
+ // Zero-cost continuity: skip all compression work if under threshold.
710
+ // Below 70% utilization, the agent pays zero overhead for context management.
711
+ if (currentTokens > targetMessageTokens) {
712
+ // Step 1: Non-destructive pruning clear old tool outputs first
713
+ this.contextManager.markToolOutputsCompacted(2);
714
+ // Step 2: Recount after pruning
715
+ const afterPruningMessages = this.contextManager.getMessages();
716
+ const afterPruningTokens = afterPruningMessages.reduce((sum, msg) => sum + this.generator.estimateTokensSync(typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)), 0);
717
+ // Step 3: If still over, run escalated compression (L1→L2→L3) via strategy chain
718
+ if (afterPruningTokens > targetMessageTokens) {
719
+ await this.contextManager.compressAndReplace(systemPromptTokens, targetMessageTokens);
720
+ }
721
+ // Step 4: Emergency guard for curate/query commands.
722
+ // Critical because curate/query have only 1 user turn, making
723
+ // protectedTurns=2 in markToolOutputsCompacted() ineffective.
724
+ if (executionContext?.commandType === 'curate' || executionContext?.commandType === 'query') {
725
+ const postCompressionMessages = this.contextManager.getMessages();
726
+ const postCompressionTokens = postCompressionMessages.reduce((sum, msg) => sum + this.generator.estimateTokensSync(typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)), 0);
727
+ const totalWithSystem = postCompressionTokens + systemPromptTokens;
728
+ if (totalWithSystem > this.config.maxInputTokens * 0.9) {
729
+ // Aggressive: compact ALL tool outputs (protect 0 turns instead of 2)
730
+ this.contextManager.markToolOutputsCompacted(0);
731
+ // Re-run escalated compression with aggressively pruned context
732
+ await this.contextManager.compressAndReplace(systemPromptTokens, targetMessageTokens);
733
+ this.sessionEventBus.emit('llmservice:warning', {
734
+ message: `Emergency context compression triggered (${Math.round((totalWithSystem / this.config.maxInputTokens) * 100)}% utilization)`,
735
+ taskId,
736
+ });
737
+ }
756
738
  }
757
739
  }
758
740
  // Build generation request
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Shared compression helper functions.
3
+ *
4
+ * Extracted from ReactiveOverflowStrategy to enable reuse
5
+ * by EscalatedCompressionStrategy and other compression implementations.
6
+ */
7
+ import type { ITokenizer } from '../../../../core/interfaces/i-tokenizer.js';
8
+ import type { InternalMessage } from '../../../../core/interfaces/message-types.js';
9
+ /**
10
+ * Count tokens in message history.
11
+ */
12
+ export declare function countHistoryTokens(history: InternalMessage[], tokenizer: ITokenizer): number;
13
+ /**
14
+ * Count tokens in a single message.
15
+ */
16
+ export declare function countMessageTokens(message: InternalMessage, tokenizer: ITokenizer): number;
17
+ /**
18
+ * Extract text content from a message.
19
+ */
20
+ export declare function extractTextContent(message: InternalMessage): string;
21
+ /**
22
+ * Find turn boundaries in message history.
23
+ *
24
+ * A turn boundary is the index where a user message starts.
25
+ * Returns indices of all user messages.
26
+ */
27
+ export declare function findTurnBoundaries(messages: InternalMessage[]): number[];
28
+ /**
29
+ * Format messages for the summary prompt.
30
+ */
31
+ export declare function formatMessagesForSummary(messages: InternalMessage[]): string;
32
+ /**
33
+ * Format role for display.
34
+ */
35
+ export declare function formatRole(role: string): string;