@defai.digital/automatosx 5.0.9 โ 5.0.10
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.
- package/CHANGELOG.md +96 -0
- package/README.md +15 -9
- package/dist/index.js +333 -56
- package/dist/index.js.map +1 -1
- package/dist/version.json +3 -3
- package/package.json +6 -6
- package/version.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,102 @@ All notable changes to AutomatosX will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [5.0.10] - 2025-10-10
|
|
9
|
+
|
|
10
|
+
### ๐ฏ Smart Cleanup & UX Improvements
|
|
11
|
+
|
|
12
|
+
#### Added
|
|
13
|
+
|
|
14
|
+
- **Smart Memory Cleanup (Phase 2)**: Intelligent threshold-based cleanup replacing unpredictable random triggers
|
|
15
|
+
- **Threshold Triggering**: Cleanup triggers at 90% capacity (default), cleans to 70% target
|
|
16
|
+
- **Three Cleanup Strategies**:
|
|
17
|
+
- `oldest`: Time-based cleanup (FIFO, default)
|
|
18
|
+
- `least_accessed`: Access-based cleanup (LRU/LFU, preserves hot data)
|
|
19
|
+
- `hybrid`: Balanced strategy (considers both age and access)
|
|
20
|
+
- **Configurable Thresholds**:
|
|
21
|
+
- `triggerThreshold`: When to start cleanup (default 0.9 = 90%)
|
|
22
|
+
- `targetThreshold`: Cleanup target (default 0.7 = 70%)
|
|
23
|
+
- `minCleanupCount`: Minimum entries to remove (default 10)
|
|
24
|
+
- `maxCleanupCount`: Maximum entries to remove (default 1000)
|
|
25
|
+
- **Smart Validation**: Comprehensive configuration validation with clear error messages
|
|
26
|
+
- **Backward Compatible**: Old `autoCleanup` and `cleanupDays` configs automatically mapped
|
|
27
|
+
|
|
28
|
+
#### Fixed
|
|
29
|
+
|
|
30
|
+
- **Memory Cleanup Bug Fixes (Phase 2.1)**: Ultra-deep review found and fixed 5 bugs
|
|
31
|
+
1. **Negative Cleanup Handling**: Fixed cleanup when entry count below target (prevents accidental deletion)
|
|
32
|
+
2. **Return Value Consistency**: All cleanup methods now return actual deleted count (not requested count)
|
|
33
|
+
3. **Async Operations**: Fixed missing `await` in fallback scenarios (eliminates race conditions)
|
|
34
|
+
4. **Configuration Validation**: Added validation for `maxCleanupCount` and `retentionDays` (prevents invalid configs)
|
|
35
|
+
5. **Type Design**: Unified all cleanup methods to `async Promise<number>` (consistent interface)
|
|
36
|
+
|
|
37
|
+
- **Agent Not Found UX**: Restored friendly agent suggestions in `ax run` command
|
|
38
|
+
- Shows "Did you mean..." list with similar agents (Levenshtein distance โค 3)
|
|
39
|
+
- Displays displayName, actual name, and role for each suggestion
|
|
40
|
+
- Falls back to "Run 'ax agent list'" if no close matches
|
|
41
|
+
- Prevents regression from early agent name resolution
|
|
42
|
+
|
|
43
|
+
#### Changed
|
|
44
|
+
|
|
45
|
+
- **Memory Cleanup Behavior**:
|
|
46
|
+
- **Before**: Random 10% chance on each add โ unpredictable timing
|
|
47
|
+
- **After**: Deterministic threshold-based โ 100% predictable when cleanup occurs
|
|
48
|
+
- **Result**: Users can trust cleanup timing and configure to their needs
|
|
49
|
+
|
|
50
|
+
- **Memory Manager Methods** (Phase 2.1 refactoring):
|
|
51
|
+
- `cleanupOldest()`: Now returns `Promise<number>` (actual deleted count)
|
|
52
|
+
- `cleanupLeastAccessed()`: Now async `Promise<number>` (supports proper fallback)
|
|
53
|
+
- `cleanupHybrid()`: Now async `Promise<number>` (consistent interface)
|
|
54
|
+
- `calculateCleanupCount()`: Added negative value check (safety improvement)
|
|
55
|
+
- `validateCleanupConfig()`: Enhanced with additional validations
|
|
56
|
+
|
|
57
|
+
#### Performance
|
|
58
|
+
|
|
59
|
+
- **Cleanup Efficiency**:
|
|
60
|
+
- 100% predictable cleanup timing (vs random)
|
|
61
|
+
- Configurable cleanup bounds prevent excessive operations
|
|
62
|
+
- Smart strategies preserve hot data when needed
|
|
63
|
+
- All cleanup methods properly await async operations
|
|
64
|
+
|
|
65
|
+
### Documentation
|
|
66
|
+
|
|
67
|
+
- Created comprehensive Phase 2 implementation plan (16 pages)
|
|
68
|
+
- Created detailed bug analysis report (Phase 2.1, 57 pages)
|
|
69
|
+
- Created bug fixes completion report
|
|
70
|
+
- All documentation in `tmp/` folder (development artifacts)
|
|
71
|
+
|
|
72
|
+
### Tests
|
|
73
|
+
|
|
74
|
+
- All 1,207 tests passing (5 skipped)
|
|
75
|
+
- Memory manager tests: 25/25 passing
|
|
76
|
+
- Phase 1 tests: 13/13 passing
|
|
77
|
+
- Integration tests: 106/106 passing
|
|
78
|
+
- Zero regressions, fully backward compatible
|
|
79
|
+
|
|
80
|
+
### Migration Notes
|
|
81
|
+
|
|
82
|
+
**No migration required** - v5.0.10 is 100% backward compatible with v5.0.9 and earlier versions.
|
|
83
|
+
|
|
84
|
+
**Optional**: To use new smart cleanup features, add to your config:
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"memory": {
|
|
88
|
+
"cleanup": {
|
|
89
|
+
"enabled": true,
|
|
90
|
+
"strategy": "hybrid",
|
|
91
|
+
"triggerThreshold": 0.9,
|
|
92
|
+
"targetThreshold": 0.7,
|
|
93
|
+
"minCleanupCount": 10,
|
|
94
|
+
"maxCleanupCount": 1000
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Old configs using `autoCleanup` and `cleanupDays` continue to work unchanged.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
8
104
|
## [5.0.9] - 2025-10-10
|
|
9
105
|
|
|
10
106
|
### Added
|
package/README.md
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
[](https://www.npmjs.com/package/@defai.digital/automatosx)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](https://www.typescriptlang.org/)
|
|
10
|
-
[](#)
|
|
11
11
|
|
|
12
|
-
**Status**: โ
Production Ready ยท v5.0.
|
|
12
|
+
**Status**: โ
Production Ready ยท v5.0.10 ยท October 2025
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
@@ -66,13 +66,19 @@ Day 3: /ax run steve "security audit" โ Steve has full context from Day 1-2
|
|
|
66
66
|
|
|
67
67
|
## ๐ What's New
|
|
68
68
|
|
|
69
|
-
**v5.0.
|
|
70
|
-
- **
|
|
71
|
-
- **
|
|
72
|
-
- **
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
|
|
69
|
+
**v5.0.10** (October 2025): Smart Cleanup & UX Improvements
|
|
70
|
+
- **NEW**: Smart memory cleanup with threshold-based triggering (replaces random cleanup)
|
|
71
|
+
- **NEW**: Three cleanup strategies: `oldest`, `least_accessed`, `hybrid`
|
|
72
|
+
- **FIX**: Memory cleanup bugs (5 bugs fixed in Phase 2.1)
|
|
73
|
+
- Fixed negative cleanup count handling
|
|
74
|
+
- Fixed async operation await issues
|
|
75
|
+
- Unified cleanup method signatures
|
|
76
|
+
- Enhanced configuration validation
|
|
77
|
+
- **FIX**: Restored agent suggestion list in `ax run` command
|
|
78
|
+
- **IMPROVED**: Predictable cleanup behavior (90% trigger โ 70% target)
|
|
79
|
+
- **100% backward compatible**: All existing configurations work unchanged
|
|
80
|
+
|
|
81
|
+
**v5.0.8**: Critical Fixes - Timeout & Memory
|
|
76
82
|
**v5.0.6**: File Operation Tools Enabled
|
|
77
83
|
**v5.0.5**: Provider Parameters & Version Management
|
|
78
84
|
**v5.0.4**: Memory saving now works automatically
|
package/dist/index.js
CHANGED
|
@@ -3148,6 +3148,12 @@ var MemoryManager = class _MemoryManager {
|
|
|
3148
3148
|
initialized = false;
|
|
3149
3149
|
useFTS = true;
|
|
3150
3150
|
// Use FTS5 by default
|
|
3151
|
+
// Phase 1: Performance optimization
|
|
3152
|
+
entryCount = 0;
|
|
3153
|
+
// Internal counter to avoid repeated COUNT(*)
|
|
3154
|
+
statements = {};
|
|
3155
|
+
// Phase 2: Smart cleanup configuration
|
|
3156
|
+
cleanupConfig;
|
|
3151
3157
|
constructor(config) {
|
|
3152
3158
|
this.config = {
|
|
3153
3159
|
dbPath: config.dbPath,
|
|
@@ -3158,6 +3164,19 @@ var MemoryManager = class _MemoryManager {
|
|
|
3158
3164
|
embeddingProvider: config.embeddingProvider
|
|
3159
3165
|
};
|
|
3160
3166
|
this.embeddingProvider = config.embeddingProvider;
|
|
3167
|
+
const cleanupCfg = config.cleanup || {};
|
|
3168
|
+
const enabled = cleanupCfg.enabled ?? config.autoCleanup ?? true;
|
|
3169
|
+
const retentionDays = cleanupCfg.retentionDays ?? config.cleanupDays ?? 30;
|
|
3170
|
+
this.cleanupConfig = {
|
|
3171
|
+
enabled,
|
|
3172
|
+
strategy: cleanupCfg.strategy ?? "oldest",
|
|
3173
|
+
triggerThreshold: cleanupCfg.triggerThreshold ?? 0.9,
|
|
3174
|
+
targetThreshold: cleanupCfg.targetThreshold ?? 0.7,
|
|
3175
|
+
minCleanupCount: cleanupCfg.minCleanupCount ?? 10,
|
|
3176
|
+
maxCleanupCount: cleanupCfg.maxCleanupCount ?? 1e3,
|
|
3177
|
+
retentionDays
|
|
3178
|
+
};
|
|
3179
|
+
this.validateCleanupConfig();
|
|
3161
3180
|
const dir = dirname4(this.config.dbPath);
|
|
3162
3181
|
if (!existsSync3(dir)) {
|
|
3163
3182
|
mkdirSync(dir, { recursive: true });
|
|
@@ -3223,11 +3242,37 @@ var MemoryManager = class _MemoryManager {
|
|
|
3223
3242
|
WHERE rowid = old.id;
|
|
3224
3243
|
END;
|
|
3225
3244
|
`);
|
|
3245
|
+
this.statements.countAll = this.db.prepare("SELECT COUNT(*) as count FROM memory_entries");
|
|
3246
|
+
this.statements.insert = this.db.prepare(`
|
|
3247
|
+
INSERT INTO memory_entries (content, metadata, created_at, last_accessed_at, access_count)
|
|
3248
|
+
VALUES (?, ?, ?, ?, 0)
|
|
3249
|
+
`);
|
|
3250
|
+
this.statements.deleteById = this.db.prepare("DELETE FROM memory_entries WHERE id = ?");
|
|
3251
|
+
this.statements.deleteOldest = this.db.prepare(`
|
|
3252
|
+
DELETE FROM memory_entries
|
|
3253
|
+
WHERE id IN (
|
|
3254
|
+
SELECT id FROM memory_entries
|
|
3255
|
+
ORDER BY created_at ASC
|
|
3256
|
+
LIMIT ?
|
|
3257
|
+
)
|
|
3258
|
+
`);
|
|
3259
|
+
this.statements.deleteBeforeCutoff = this.db.prepare(`
|
|
3260
|
+
DELETE FROM memory_entries
|
|
3261
|
+
WHERE created_at < ?
|
|
3262
|
+
`);
|
|
3263
|
+
this.statements.updateAccessCount = this.db.prepare(`
|
|
3264
|
+
UPDATE memory_entries
|
|
3265
|
+
SET access_count = access_count + 1, last_accessed_at = ?
|
|
3266
|
+
WHERE id = ?
|
|
3267
|
+
`);
|
|
3268
|
+
const countResult = this.statements.countAll.get();
|
|
3269
|
+
this.entryCount = countResult.count;
|
|
3226
3270
|
this.initialized = true;
|
|
3227
3271
|
logger.info("MemoryManager initialized successfully", {
|
|
3228
3272
|
dbPath: this.config.dbPath,
|
|
3229
3273
|
searchMethod: "FTS5",
|
|
3230
|
-
hasEmbeddingProvider: !!this.embeddingProvider
|
|
3274
|
+
hasEmbeddingProvider: !!this.embeddingProvider,
|
|
3275
|
+
entryCount: this.entryCount
|
|
3231
3276
|
});
|
|
3232
3277
|
} catch (error) {
|
|
3233
3278
|
logger.error("Failed to initialize MemoryManager", { error: error.message });
|
|
@@ -3241,47 +3286,60 @@ var MemoryManager = class _MemoryManager {
|
|
|
3241
3286
|
* Add a new memory entry
|
|
3242
3287
|
*
|
|
3243
3288
|
* v4.11.0: Embedding is now optional (only needed for Plus version)
|
|
3289
|
+
* v5.0.9: Phase 1 - Transaction atomicity with prepared statements
|
|
3244
3290
|
*/
|
|
3245
3291
|
async add(content, embedding, metadata) {
|
|
3246
3292
|
if (!this.initialized) {
|
|
3247
3293
|
throw new MemoryError("Memory manager not initialized", "DATABASE_ERROR");
|
|
3248
3294
|
}
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
`Memory limit reached (${this.config.maxEntries} entries). Run 'ax memory clear' or increase maxEntries in config.`,
|
|
3262
|
-
"MEMORY_LIMIT"
|
|
3263
|
-
);
|
|
3264
|
-
}
|
|
3265
|
-
}
|
|
3266
|
-
if (this.config.autoCleanup && Math.random() < 0.1) {
|
|
3267
|
-
this.cleanup().catch((error) => {
|
|
3268
|
-
logger.debug("Background cleanup failed", {
|
|
3295
|
+
if (this.shouldTriggerCleanup()) {
|
|
3296
|
+
try {
|
|
3297
|
+
const removed = await this.executeSmartCleanup();
|
|
3298
|
+
logger.info("Smart cleanup triggered", {
|
|
3299
|
+
removed,
|
|
3300
|
+
currentCount: this.entryCount,
|
|
3301
|
+
usage: (this.entryCount / this.config.maxEntries * 100).toFixed(1) + "%",
|
|
3302
|
+
threshold: (this.cleanupConfig.triggerThreshold * 100).toFixed(0) + "%",
|
|
3303
|
+
strategy: this.cleanupConfig.strategy
|
|
3304
|
+
});
|
|
3305
|
+
} catch (error) {
|
|
3306
|
+
logger.warn("Smart cleanup failed", {
|
|
3269
3307
|
error: error.message
|
|
3270
3308
|
});
|
|
3271
|
-
}
|
|
3309
|
+
}
|
|
3272
3310
|
}
|
|
3273
3311
|
try {
|
|
3274
3312
|
const now = Date.now();
|
|
3275
|
-
const
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3313
|
+
const metadataStr = JSON.stringify(metadata);
|
|
3314
|
+
const insertTxn = this.db.transaction(() => {
|
|
3315
|
+
let deletedCount2 = 0;
|
|
3316
|
+
if (this.entryCount >= this.config.maxEntries) {
|
|
3317
|
+
const entriesToRemove = Math.min(100, Math.floor(this.config.maxEntries * 0.1));
|
|
3318
|
+
const deleteInfo = this.statements.deleteOldest.run(entriesToRemove);
|
|
3319
|
+
deletedCount2 = deleteInfo.changes;
|
|
3320
|
+
logger.warn("Memory limit approaching, auto-cleanup triggered", {
|
|
3321
|
+
currentCount: this.entryCount,
|
|
3322
|
+
maxEntries: this.config.maxEntries,
|
|
3323
|
+
removed: deletedCount2
|
|
3324
|
+
});
|
|
3325
|
+
if (this.entryCount - deletedCount2 >= this.config.maxEntries) {
|
|
3326
|
+
throw new MemoryError(
|
|
3327
|
+
`Memory limit reached (${this.config.maxEntries} entries). Run 'ax memory clear' or increase maxEntries in config.`,
|
|
3328
|
+
"MEMORY_LIMIT"
|
|
3329
|
+
);
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
const insertResult = this.statements.insert.run(content, metadataStr, now, now);
|
|
3333
|
+
logger.debug("Memory entry added", {
|
|
3334
|
+
id: insertResult.lastInsertRowid,
|
|
3335
|
+
contentLength: content.length,
|
|
3336
|
+
searchMethod: "FTS5",
|
|
3337
|
+
deletedCount: deletedCount2
|
|
3338
|
+
});
|
|
3339
|
+
return { id: Number(insertResult.lastInsertRowid), deletedCount: deletedCount2 };
|
|
3284
3340
|
});
|
|
3341
|
+
const { id, deletedCount } = insertTxn();
|
|
3342
|
+
this.entryCount = this.entryCount - deletedCount + 1;
|
|
3285
3343
|
return {
|
|
3286
3344
|
id,
|
|
3287
3345
|
content,
|
|
@@ -3379,13 +3437,14 @@ var MemoryManager = class _MemoryManager {
|
|
|
3379
3437
|
const finalParams = [ftsQuery, ...params, limit];
|
|
3380
3438
|
const results = this.db.prepare(sql).all(...finalParams);
|
|
3381
3439
|
if (this.config.trackAccess && results.length > 0) {
|
|
3440
|
+
const now = Date.now();
|
|
3382
3441
|
const ids = results.map((r) => r.id);
|
|
3383
3442
|
const placeholders = ids.map(() => "?").join(",");
|
|
3384
3443
|
this.db.prepare(`
|
|
3385
3444
|
UPDATE memory_entries
|
|
3386
3445
|
SET last_accessed_at = ?, access_count = access_count + 1
|
|
3387
3446
|
WHERE id IN (${placeholders})
|
|
3388
|
-
`).run(
|
|
3447
|
+
`).run(now, ...ids);
|
|
3389
3448
|
}
|
|
3390
3449
|
return results.map((row) => {
|
|
3391
3450
|
const similarity = Math.max(0, Math.min(1, 1 + row.relevance / 10));
|
|
@@ -3483,8 +3542,11 @@ var MemoryManager = class _MemoryManager {
|
|
|
3483
3542
|
if (!existing) {
|
|
3484
3543
|
throw new MemoryError(`Memory entry not found: ${id}`, "ENTRY_NOT_FOUND");
|
|
3485
3544
|
}
|
|
3486
|
-
|
|
3487
|
-
|
|
3545
|
+
const deleteInfo = this.statements.deleteById.run(id);
|
|
3546
|
+
if (deleteInfo.changes > 0) {
|
|
3547
|
+
this.entryCount -= deleteInfo.changes;
|
|
3548
|
+
}
|
|
3549
|
+
logger.debug("Memory entry deleted", { id, newCount: this.entryCount });
|
|
3488
3550
|
} catch (error) {
|
|
3489
3551
|
if (error instanceof MemoryError) throw error;
|
|
3490
3552
|
throw new MemoryError(
|
|
@@ -3560,6 +3622,7 @@ var MemoryManager = class _MemoryManager {
|
|
|
3560
3622
|
}
|
|
3561
3623
|
try {
|
|
3562
3624
|
this.db.prepare("DELETE FROM memory_entries").run();
|
|
3625
|
+
this.entryCount = 0;
|
|
3563
3626
|
this.db.prepare("VACUUM").run();
|
|
3564
3627
|
logger.info("All memory entries cleared");
|
|
3565
3628
|
} catch (error) {
|
|
@@ -3600,41 +3663,227 @@ var MemoryManager = class _MemoryManager {
|
|
|
3600
3663
|
* Get total count of memory entries
|
|
3601
3664
|
* v5.0.8: Added for maxEntries enforcement
|
|
3602
3665
|
*/
|
|
3666
|
+
/**
|
|
3667
|
+
* Get current entry count
|
|
3668
|
+
* Phase 1: Use internal counter instead of COUNT(*) for better performance
|
|
3669
|
+
*/
|
|
3603
3670
|
getCount() {
|
|
3604
3671
|
if (!this.initialized) {
|
|
3605
3672
|
return 0;
|
|
3606
3673
|
}
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3674
|
+
return this.entryCount;
|
|
3675
|
+
}
|
|
3676
|
+
/**
|
|
3677
|
+
* Phase 2: Validate cleanup configuration
|
|
3678
|
+
* v5.0.10 Phase 2.1: Added validation for maxCleanupCount and retentionDays
|
|
3679
|
+
* @throws {MemoryError} if configuration is invalid
|
|
3680
|
+
*/
|
|
3681
|
+
validateCleanupConfig() {
|
|
3682
|
+
const cfg = this.cleanupConfig;
|
|
3683
|
+
if (cfg.triggerThreshold < 0.5 || cfg.triggerThreshold > 1) {
|
|
3684
|
+
throw new MemoryError(
|
|
3685
|
+
"cleanup.triggerThreshold must be between 0.5 and 1.0",
|
|
3686
|
+
"CONFIG_ERROR"
|
|
3687
|
+
);
|
|
3688
|
+
}
|
|
3689
|
+
if (cfg.targetThreshold < 0.1 || cfg.targetThreshold > 0.9) {
|
|
3690
|
+
throw new MemoryError(
|
|
3691
|
+
"cleanup.targetThreshold must be between 0.1 and 0.9",
|
|
3692
|
+
"CONFIG_ERROR"
|
|
3693
|
+
);
|
|
3694
|
+
}
|
|
3695
|
+
if (cfg.targetThreshold >= cfg.triggerThreshold) {
|
|
3696
|
+
throw new MemoryError(
|
|
3697
|
+
"cleanup.targetThreshold must be less than triggerThreshold",
|
|
3698
|
+
"CONFIG_ERROR"
|
|
3699
|
+
);
|
|
3700
|
+
}
|
|
3701
|
+
if (cfg.minCleanupCount < 1) {
|
|
3702
|
+
throw new MemoryError(
|
|
3703
|
+
"cleanup.minCleanupCount must be at least 1",
|
|
3704
|
+
"CONFIG_ERROR"
|
|
3705
|
+
);
|
|
3706
|
+
}
|
|
3707
|
+
if (cfg.maxCleanupCount < 1) {
|
|
3708
|
+
throw new MemoryError(
|
|
3709
|
+
"cleanup.maxCleanupCount must be at least 1",
|
|
3710
|
+
"CONFIG_ERROR"
|
|
3711
|
+
);
|
|
3712
|
+
}
|
|
3713
|
+
if (cfg.maxCleanupCount < cfg.minCleanupCount) {
|
|
3714
|
+
throw new MemoryError(
|
|
3715
|
+
"cleanup.maxCleanupCount must be >= minCleanupCount",
|
|
3716
|
+
"CONFIG_ERROR"
|
|
3717
|
+
);
|
|
3718
|
+
}
|
|
3719
|
+
if (cfg.retentionDays < 1) {
|
|
3720
|
+
throw new MemoryError(
|
|
3721
|
+
"cleanup.retentionDays must be at least 1",
|
|
3722
|
+
"CONFIG_ERROR"
|
|
3723
|
+
);
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3726
|
+
/**
|
|
3727
|
+
* Phase 2: Check if cleanup should be triggered based on usage threshold
|
|
3728
|
+
*/
|
|
3729
|
+
shouldTriggerCleanup() {
|
|
3730
|
+
if (!this.cleanupConfig.enabled) {
|
|
3731
|
+
return false;
|
|
3732
|
+
}
|
|
3733
|
+
const currentUsage = this.entryCount / this.config.maxEntries;
|
|
3734
|
+
return currentUsage >= this.cleanupConfig.triggerThreshold;
|
|
3735
|
+
}
|
|
3736
|
+
/**
|
|
3737
|
+
* Phase 2: Calculate how many entries to remove to reach target threshold
|
|
3738
|
+
*/
|
|
3739
|
+
calculateCleanupCount() {
|
|
3740
|
+
const targetCount = Math.floor(
|
|
3741
|
+
this.config.maxEntries * this.cleanupConfig.targetThreshold
|
|
3742
|
+
);
|
|
3743
|
+
const toRemove = this.entryCount - targetCount;
|
|
3744
|
+
if (toRemove <= 0) {
|
|
3612
3745
|
return 0;
|
|
3613
3746
|
}
|
|
3747
|
+
return Math.max(
|
|
3748
|
+
this.cleanupConfig.minCleanupCount,
|
|
3749
|
+
Math.min(this.cleanupConfig.maxCleanupCount, toRemove)
|
|
3750
|
+
);
|
|
3751
|
+
}
|
|
3752
|
+
/**
|
|
3753
|
+
* Phase 2: Execute cleanup with configured strategy
|
|
3754
|
+
* @returns Number of entries removed
|
|
3755
|
+
*/
|
|
3756
|
+
async executeSmartCleanup() {
|
|
3757
|
+
const count = this.calculateCleanupCount();
|
|
3758
|
+
logger.debug("Executing smart cleanup", {
|
|
3759
|
+
strategy: this.cleanupConfig.strategy,
|
|
3760
|
+
count,
|
|
3761
|
+
currentCount: this.entryCount,
|
|
3762
|
+
threshold: this.cleanupConfig.triggerThreshold
|
|
3763
|
+
});
|
|
3764
|
+
switch (this.cleanupConfig.strategy) {
|
|
3765
|
+
case "oldest":
|
|
3766
|
+
return await this.cleanupOldest(count);
|
|
3767
|
+
case "least_accessed":
|
|
3768
|
+
return await this.cleanupLeastAccessed(count);
|
|
3769
|
+
case "hybrid":
|
|
3770
|
+
return await this.cleanupHybrid(count);
|
|
3771
|
+
default:
|
|
3772
|
+
throw new MemoryError(
|
|
3773
|
+
`Unknown cleanup strategy: ${this.cleanupConfig.strategy}`,
|
|
3774
|
+
"CONFIG_ERROR"
|
|
3775
|
+
);
|
|
3776
|
+
}
|
|
3614
3777
|
}
|
|
3615
3778
|
/**
|
|
3616
3779
|
* Remove oldest entries
|
|
3617
3780
|
* v5.0.8: Added for automatic cleanup when approaching maxEntries
|
|
3781
|
+
* v5.0.10 Phase 2: Enhanced logging with strategy info
|
|
3782
|
+
* v5.0.10 Phase 2.1: Now returns actual deleted count
|
|
3618
3783
|
*/
|
|
3619
3784
|
async cleanupOldest(count) {
|
|
3620
3785
|
if (!this.initialized || count <= 0) {
|
|
3621
|
-
return;
|
|
3786
|
+
return 0;
|
|
3622
3787
|
}
|
|
3623
3788
|
try {
|
|
3624
|
-
this.
|
|
3789
|
+
const deleteInfo = this.statements.deleteOldest.run(count);
|
|
3790
|
+
if (deleteInfo.changes > 0) {
|
|
3791
|
+
this.entryCount -= deleteInfo.changes;
|
|
3792
|
+
}
|
|
3793
|
+
logger.info("Cleaned up oldest entries", {
|
|
3794
|
+
requested: count,
|
|
3795
|
+
deleted: deleteInfo.changes,
|
|
3796
|
+
newCount: this.entryCount,
|
|
3797
|
+
strategy: "oldest"
|
|
3798
|
+
// Phase 2: Add strategy info
|
|
3799
|
+
});
|
|
3800
|
+
return deleteInfo.changes;
|
|
3801
|
+
} catch (error) {
|
|
3802
|
+
logger.error("Failed to cleanup oldest entries", {
|
|
3803
|
+
count,
|
|
3804
|
+
error: error.message
|
|
3805
|
+
});
|
|
3806
|
+
return 0;
|
|
3807
|
+
}
|
|
3808
|
+
}
|
|
3809
|
+
/**
|
|
3810
|
+
* Phase 2: Remove least accessed entries
|
|
3811
|
+
* v5.0.10 Phase 2.1: Now async to support fallback to cleanupOldest
|
|
3812
|
+
* @param count Number of entries to remove
|
|
3813
|
+
* @returns Number of entries actually removed
|
|
3814
|
+
*/
|
|
3815
|
+
async cleanupLeastAccessed(count) {
|
|
3816
|
+
if (!this.initialized || count <= 0) {
|
|
3817
|
+
return 0;
|
|
3818
|
+
}
|
|
3819
|
+
if (!this.config.trackAccess) {
|
|
3820
|
+
logger.warn("least_accessed strategy requires trackAccess=true, falling back to oldest");
|
|
3821
|
+
return await this.cleanupOldest(count);
|
|
3822
|
+
}
|
|
3823
|
+
try {
|
|
3824
|
+
const deleteInfo = this.db.prepare(`
|
|
3625
3825
|
DELETE FROM memory_entries
|
|
3626
3826
|
WHERE id IN (
|
|
3627
3827
|
SELECT id FROM memory_entries
|
|
3628
|
-
ORDER BY
|
|
3828
|
+
ORDER BY access_count ASC, last_accessed_at ASC
|
|
3629
3829
|
LIMIT ?
|
|
3630
3830
|
)
|
|
3631
3831
|
`).run(count);
|
|
3632
|
-
|
|
3832
|
+
if (deleteInfo.changes > 0) {
|
|
3833
|
+
this.entryCount -= deleteInfo.changes;
|
|
3834
|
+
}
|
|
3835
|
+
logger.info("Cleaned up least accessed entries", {
|
|
3836
|
+
requested: count,
|
|
3837
|
+
deleted: deleteInfo.changes,
|
|
3838
|
+
newCount: this.entryCount,
|
|
3839
|
+
strategy: "least_accessed"
|
|
3840
|
+
});
|
|
3841
|
+
return deleteInfo.changes;
|
|
3633
3842
|
} catch (error) {
|
|
3634
|
-
logger.error("Failed to cleanup
|
|
3843
|
+
logger.error("Failed to cleanup least accessed entries", {
|
|
3635
3844
|
count,
|
|
3636
3845
|
error: error.message
|
|
3637
3846
|
});
|
|
3847
|
+
return 0;
|
|
3848
|
+
}
|
|
3849
|
+
}
|
|
3850
|
+
/**
|
|
3851
|
+
* Phase 2: Remove entries using hybrid strategy (access count + age)
|
|
3852
|
+
* v5.0.10 Phase 2.1: Now async for consistency with other cleanup methods
|
|
3853
|
+
* @param count Number of entries to remove
|
|
3854
|
+
* @returns Number of entries actually removed
|
|
3855
|
+
*/
|
|
3856
|
+
async cleanupHybrid(count) {
|
|
3857
|
+
if (!this.initialized || count <= 0) {
|
|
3858
|
+
return 0;
|
|
3859
|
+
}
|
|
3860
|
+
try {
|
|
3861
|
+
const deleteInfo = this.db.prepare(`
|
|
3862
|
+
DELETE FROM memory_entries
|
|
3863
|
+
WHERE id IN (
|
|
3864
|
+
SELECT id FROM memory_entries
|
|
3865
|
+
ORDER BY
|
|
3866
|
+
access_count ASC,
|
|
3867
|
+
created_at ASC
|
|
3868
|
+
LIMIT ?
|
|
3869
|
+
)
|
|
3870
|
+
`).run(count);
|
|
3871
|
+
if (deleteInfo.changes > 0) {
|
|
3872
|
+
this.entryCount -= deleteInfo.changes;
|
|
3873
|
+
}
|
|
3874
|
+
logger.info("Cleaned up entries using hybrid strategy", {
|
|
3875
|
+
requested: count,
|
|
3876
|
+
deleted: deleteInfo.changes,
|
|
3877
|
+
newCount: this.entryCount,
|
|
3878
|
+
strategy: "hybrid"
|
|
3879
|
+
});
|
|
3880
|
+
return deleteInfo.changes;
|
|
3881
|
+
} catch (error) {
|
|
3882
|
+
logger.error("Failed to cleanup with hybrid strategy", {
|
|
3883
|
+
count,
|
|
3884
|
+
error: error.message
|
|
3885
|
+
});
|
|
3886
|
+
return 0;
|
|
3638
3887
|
}
|
|
3639
3888
|
}
|
|
3640
3889
|
async cleanup(olderThanDays) {
|
|
@@ -3644,14 +3893,16 @@ var MemoryManager = class _MemoryManager {
|
|
|
3644
3893
|
const days = olderThanDays || this.config.cleanupDays;
|
|
3645
3894
|
const cutoffTime = Date.now() - days * 24 * 60 * 60 * 1e3;
|
|
3646
3895
|
try {
|
|
3647
|
-
const
|
|
3648
|
-
|
|
3649
|
-
WHERE created_at < ?
|
|
3650
|
-
`).run(cutoffTime);
|
|
3651
|
-
const deleted = result.changes;
|
|
3896
|
+
const deleteInfo = this.statements.deleteBeforeCutoff.run(cutoffTime);
|
|
3897
|
+
const deleted = deleteInfo.changes;
|
|
3652
3898
|
if (deleted > 0) {
|
|
3899
|
+
this.entryCount -= deleted;
|
|
3653
3900
|
this.db.prepare("VACUUM").run();
|
|
3654
|
-
logger.info("Cleanup completed", {
|
|
3901
|
+
logger.info("Cleanup completed", {
|
|
3902
|
+
deleted,
|
|
3903
|
+
olderThanDays: days,
|
|
3904
|
+
newCount: this.entryCount
|
|
3905
|
+
});
|
|
3655
3906
|
}
|
|
3656
3907
|
return deleted;
|
|
3657
3908
|
} catch (error) {
|
|
@@ -3708,6 +3959,9 @@ var MemoryManager = class _MemoryManager {
|
|
|
3708
3959
|
);
|
|
3709
3960
|
}
|
|
3710
3961
|
this.db.close();
|
|
3962
|
+
this.initialized = false;
|
|
3963
|
+
this.entryCount = 0;
|
|
3964
|
+
this.statements = {};
|
|
3711
3965
|
const srcDb = new Database(srcPath, { readonly: true });
|
|
3712
3966
|
const destDb = new Database(this.config.dbPath);
|
|
3713
3967
|
await srcDb.backup(this.config.dbPath);
|
|
@@ -3715,8 +3969,7 @@ var MemoryManager = class _MemoryManager {
|
|
|
3715
3969
|
destDb.close();
|
|
3716
3970
|
this.db = new Database(this.config.dbPath);
|
|
3717
3971
|
this.db.pragma("journal_mode = WAL");
|
|
3718
|
-
|
|
3719
|
-
this.initialized = true;
|
|
3972
|
+
await this.initialize();
|
|
3720
3973
|
logger.info("Database restored successfully", { srcPath });
|
|
3721
3974
|
} catch (error) {
|
|
3722
3975
|
throw new MemoryError(
|
|
@@ -10082,11 +10335,35 @@ var runCommand = {
|
|
|
10082
10335
|
// fallbackProfilesDir (uses default)
|
|
10083
10336
|
teamManager
|
|
10084
10337
|
);
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
if (
|
|
10088
|
-
|
|
10338
|
+
try {
|
|
10339
|
+
resolvedAgentName = await profileLoader.resolveAgentName(argv2.agent);
|
|
10340
|
+
if (argv2.verbose) {
|
|
10341
|
+
if (resolvedAgentName !== argv2.agent) {
|
|
10342
|
+
console.log(chalk12.gray(`Resolved agent: ${argv2.agent} \u2192 ${resolvedAgentName}`));
|
|
10343
|
+
}
|
|
10344
|
+
}
|
|
10345
|
+
} catch (error) {
|
|
10346
|
+
console.error(chalk12.red.bold(`
|
|
10347
|
+
\u274C Agent not found: ${argv2.agent}
|
|
10348
|
+
`));
|
|
10349
|
+
try {
|
|
10350
|
+
const suggestions = await profileLoader.findSimilarAgents(argv2.agent, 3);
|
|
10351
|
+
const closeSuggestions = suggestions.filter((s) => s.distance <= 3);
|
|
10352
|
+
if (closeSuggestions.length > 0) {
|
|
10353
|
+
console.log(chalk12.yellow("\u{1F4A1} Did you mean:\n"));
|
|
10354
|
+
closeSuggestions.forEach((s, i) => {
|
|
10355
|
+
const displayInfo = s.displayName ? `${s.displayName} (${s.name})` : s.name;
|
|
10356
|
+
const roleInfo = s.role ? ` - ${s.role}` : "";
|
|
10357
|
+
console.log(chalk12.cyan(` ${i + 1}. ${displayInfo}${roleInfo}`));
|
|
10358
|
+
});
|
|
10359
|
+
console.log();
|
|
10360
|
+
} else {
|
|
10361
|
+
console.log(chalk12.gray('Run "ax agent list" to see available agents.\n'));
|
|
10362
|
+
}
|
|
10363
|
+
} catch {
|
|
10364
|
+
console.log(chalk12.gray('Run "ax agent list" to see available agents.\n'));
|
|
10089
10365
|
}
|
|
10366
|
+
process.exit(1);
|
|
10090
10367
|
}
|
|
10091
10368
|
const abilitiesManager = new AbilitiesManager(
|
|
10092
10369
|
join8(projectDir, ".automatosx", "abilities")
|