@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 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
  [![npm version](https://img.shields.io/npm/v/@defai.digital/automatosx.svg)](https://www.npmjs.com/package/@defai.digital/automatosx)
8
8
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
9
9
  [![TypeScript](https://img.shields.io/badge/TypeScript-100%25-blue.svg)](https://www.typescriptlang.org/)
10
- [![Tests](https://img.shields.io/badge/tests-1,149%20passing-brightgreen.svg)](#)
10
+ [![Tests](https://img.shields.io/badge/tests-1,207%20passing-brightgreen.svg)](#)
11
11
 
12
- **Status**: โœ… Production Ready ยท v5.0.8 ยท October 2025
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.8** (October 2025): Critical Fixes - Timeout & Memory
70
- - **CRITICAL FIX**: Multi-stage agents now respect `--timeout` flag
71
- - **CRITICAL FIX**: Memory system enforces `maxEntries` and `autoCleanup` limits
72
- - **Timeout support**: AbortSignal properly passed to all stage executors
73
- - **Memory limits**: Automatic cleanup prevents database growth issues
74
- - **100% backward compatible**: Drop-in replacement for v5.0.7
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
- const currentCount = this.getCount();
3250
- if (currentCount >= this.config.maxEntries) {
3251
- const entriesToRemove = Math.min(100, Math.floor(this.config.maxEntries * 0.1));
3252
- await this.cleanupOldest(entriesToRemove);
3253
- logger.warn("Memory limit approaching, auto-cleanup triggered", {
3254
- currentCount,
3255
- maxEntries: this.config.maxEntries,
3256
- removed: entriesToRemove
3257
- });
3258
- const newCount = this.getCount();
3259
- if (newCount >= this.config.maxEntries) {
3260
- throw new MemoryError(
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 result = this.db.prepare(`
3276
- INSERT INTO memory_entries (content, metadata, created_at, access_count)
3277
- VALUES (?, ?, ?, 0)
3278
- `).run(content, JSON.stringify(metadata), now);
3279
- const id = Number(result.lastInsertRowid);
3280
- logger.debug("Memory entry added", {
3281
- id,
3282
- contentLength: content.length,
3283
- searchMethod: "FTS5"
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(Date.now(), ...ids);
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
- this.db.prepare("DELETE FROM memory_entries WHERE id = ?").run(id);
3487
- logger.debug("Memory entry deleted", { id });
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
- try {
3608
- const result = this.db.prepare("SELECT COUNT(*) as count FROM memory_entries").get();
3609
- return result?.count ?? 0;
3610
- } catch (error) {
3611
- logger.error("Failed to get entry count", { error: error.message });
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.db.prepare(`
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 created_at ASC
3828
+ ORDER BY access_count ASC, last_accessed_at ASC
3629
3829
  LIMIT ?
3630
3830
  )
3631
3831
  `).run(count);
3632
- logger.info("Cleaned up oldest entries", { count });
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 oldest entries", {
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 result = this.db.prepare(`
3648
- DELETE FROM memory_entries
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", { deleted, olderThanDays: days });
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
- sqliteVec.load(this.db);
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
- resolvedAgentName = await profileLoader.resolveAgentName(argv2.agent);
10086
- if (argv2.verbose) {
10087
- if (resolvedAgentName !== argv2.agent) {
10088
- console.log(chalk12.gray(`Resolved agent: ${argv2.agent} \u2192 ${resolvedAgentName}`));
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")