claude-cognitive 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/README.md +57 -35
  2. package/dist/agents/context.d.ts.map +1 -1
  3. package/dist/agents/context.js +7 -7
  4. package/dist/agents/context.js.map +1 -1
  5. package/dist/cli/commands/feedback-stats.d.ts +10 -0
  6. package/dist/cli/commands/feedback-stats.d.ts.map +1 -0
  7. package/dist/cli/commands/feedback-stats.js +70 -0
  8. package/dist/cli/commands/feedback-stats.js.map +1 -0
  9. package/dist/cli/commands/feedback-sync.d.ts +10 -0
  10. package/dist/cli/commands/feedback-sync.d.ts.map +1 -0
  11. package/dist/cli/commands/feedback-sync.js +117 -0
  12. package/dist/cli/commands/feedback-sync.js.map +1 -0
  13. package/dist/cli/commands/index.d.ts +2 -0
  14. package/dist/cli/commands/index.d.ts.map +1 -1
  15. package/dist/cli/commands/index.js +2 -0
  16. package/dist/cli/commands/index.js.map +1 -1
  17. package/dist/cli/commands/install.d.ts.map +1 -1
  18. package/dist/cli/commands/install.js +130 -4
  19. package/dist/cli/commands/install.js.map +1 -1
  20. package/dist/cli/commands/semantic.d.ts +4 -2
  21. package/dist/cli/commands/semantic.d.ts.map +1 -1
  22. package/dist/cli/commands/semantic.js +8 -57
  23. package/dist/cli/commands/semantic.js.map +1 -1
  24. package/dist/cli/commands/status.d.ts.map +1 -1
  25. package/dist/cli/commands/status.js +0 -2
  26. package/dist/cli/commands/status.js.map +1 -1
  27. package/dist/cli/commands/sync.d.ts +4 -2
  28. package/dist/cli/commands/sync.d.ts.map +1 -1
  29. package/dist/cli/commands/sync.js +9 -250
  30. package/dist/cli/commands/sync.js.map +1 -1
  31. package/dist/cli/commands/update.d.ts.map +1 -1
  32. package/dist/cli/commands/update.js +106 -1
  33. package/dist/cli/commands/update.js.map +1 -1
  34. package/dist/cli/index.js +3 -1
  35. package/dist/cli/index.js.map +1 -1
  36. package/dist/cli/utils/output.d.ts +0 -2
  37. package/dist/cli/utils/output.d.ts.map +1 -1
  38. package/dist/cli/utils/output.js +0 -2
  39. package/dist/cli/utils/output.js.map +1 -1
  40. package/dist/client.d.ts +121 -22
  41. package/dist/client.d.ts.map +1 -1
  42. package/dist/client.js +209 -36
  43. package/dist/client.js.map +1 -1
  44. package/dist/events.d.ts +30 -1
  45. package/dist/events.d.ts.map +1 -1
  46. package/dist/events.js.map +1 -1
  47. package/dist/feedback/constants.d.ts +72 -0
  48. package/dist/feedback/constants.d.ts.map +1 -0
  49. package/dist/feedback/constants.js +145 -0
  50. package/dist/feedback/constants.js.map +1 -0
  51. package/dist/feedback/detector.d.ts +75 -0
  52. package/dist/feedback/detector.d.ts.map +1 -0
  53. package/dist/feedback/detector.js +393 -0
  54. package/dist/feedback/detector.js.map +1 -0
  55. package/dist/feedback/index.d.ts +85 -0
  56. package/dist/feedback/index.d.ts.map +1 -0
  57. package/dist/feedback/index.js +194 -0
  58. package/dist/feedback/index.js.map +1 -0
  59. package/dist/feedback/offline-queue.d.ts +110 -0
  60. package/dist/feedback/offline-queue.d.ts.map +1 -0
  61. package/dist/feedback/offline-queue.js +188 -0
  62. package/dist/feedback/offline-queue.js.map +1 -0
  63. package/dist/feedback/scorer.d.ts +81 -0
  64. package/dist/feedback/scorer.d.ts.map +1 -0
  65. package/dist/feedback/scorer.js +194 -0
  66. package/dist/feedback/scorer.js.map +1 -0
  67. package/dist/feedback/similarity.d.ts +19 -0
  68. package/dist/feedback/similarity.d.ts.map +1 -0
  69. package/dist/feedback/similarity.js +49 -0
  70. package/dist/feedback/similarity.js.map +1 -0
  71. package/dist/feedback/tracker.d.ts +104 -0
  72. package/dist/feedback/tracker.d.ts.map +1 -0
  73. package/dist/feedback/tracker.js +324 -0
  74. package/dist/feedback/tracker.js.map +1 -0
  75. package/dist/hooks/process-session.d.ts.map +1 -1
  76. package/dist/hooks/process-session.js +17 -8
  77. package/dist/hooks/process-session.js.map +1 -1
  78. package/dist/index.d.ts +9 -0
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +15 -0
  81. package/dist/index.js.map +1 -1
  82. package/dist/learn/index.d.ts.map +1 -1
  83. package/dist/learn/index.js +6 -2
  84. package/dist/learn/index.js.map +1 -1
  85. package/dist/mcp/handlers.d.ts +9 -1
  86. package/dist/mcp/handlers.d.ts.map +1 -1
  87. package/dist/mcp/handlers.js +66 -0
  88. package/dist/mcp/handlers.js.map +1 -1
  89. package/dist/mcp/server.d.ts.map +1 -1
  90. package/dist/mcp/server.js +42 -2
  91. package/dist/mcp/server.js.map +1 -1
  92. package/dist/mcp/tools.d.ts +77 -0
  93. package/dist/mcp/tools.d.ts.map +1 -1
  94. package/dist/mcp/tools.js +30 -0
  95. package/dist/mcp/tools.js.map +1 -1
  96. package/dist/mcp/types.d.ts +16 -0
  97. package/dist/mcp/types.d.ts.map +1 -1
  98. package/dist/mind.d.ts +47 -28
  99. package/dist/mind.d.ts.map +1 -1
  100. package/dist/mind.js +351 -104
  101. package/dist/mind.js.map +1 -1
  102. package/dist/offline.d.ts +111 -0
  103. package/dist/offline.d.ts.map +1 -0
  104. package/dist/offline.js +198 -0
  105. package/dist/offline.js.map +1 -0
  106. package/dist/types.d.ts +171 -1
  107. package/dist/types.d.ts.map +1 -1
  108. package/package.json +1 -1
package/dist/mind.js CHANGED
@@ -9,8 +9,9 @@ import { HindsightClient } from "./client.js";
9
9
  import { loadConfig } from "./config.js";
10
10
  import { HindsightError } from "./errors.js";
11
11
  import { TypedEventEmitter } from "./events.js";
12
- import { PromotionManager, DEFAULT_PROMOTION_THRESHOLD } from "./promotion.js";
13
- import { SemanticMemory } from "./semantic.js";
12
+ import { createFeedbackService } from "./feedback/index.js";
13
+ import { OfflineFeedbackQueue } from "./feedback/offline-queue.js";
14
+ import { OfflineMemoryStore } from "./offline.js";
14
15
  // ============================================
15
16
  // Default Values
16
17
  // ============================================
@@ -63,10 +64,14 @@ export class Mind extends TypedEventEmitter {
63
64
  bankId = "";
64
65
  disposition = DEFAULT_DISPOSITION;
65
66
  background;
66
- /** Path to semantic memory file. Used in Phase 3. */
67
- semanticPath = ".claude/memory.md";
68
67
  // Client (nullable for graceful degradation)
69
68
  client = null;
69
+ // Offline memory store (always available)
70
+ offlineStore = null;
71
+ // Feedback service (optional, enabled via config)
72
+ feedbackService = null;
73
+ // Offline feedback queue (for degraded mode)
74
+ feedbackQueue = null;
70
75
  // State
71
76
  initialized = false;
72
77
  initializing = false;
@@ -74,11 +79,12 @@ export class Mind extends TypedEventEmitter {
74
79
  sessionActive = false;
75
80
  /** Session start time. Used for duration tracking. */
76
81
  sessionStartTime = null;
82
+ /** Current session ID for feedback tracking */
83
+ sessionId = null;
77
84
  // Agent templates (loaded in init())
78
85
  customAgents = [];
79
- // Semantic memory (loaded in init())
80
- semantic = null;
81
- promotionManager = null;
86
+ // Context settings
87
+ recentMemoryLimit = 3;
82
88
  /**
83
89
  * Create a new Mind instance.
84
90
  *
@@ -138,9 +144,6 @@ export class Mind extends TypedEventEmitter {
138
144
  if (this.pendingOptions.background !== undefined) {
139
145
  overrides.background = this.pendingOptions.background;
140
146
  }
141
- if (this.pendingOptions.semanticPath !== undefined) {
142
- overrides.semantic = { path: this.pendingOptions.semanticPath };
143
- }
144
147
  // Load config with pending options as overrides
145
148
  const config = await loadConfig(this.projectPath, overrides);
146
149
  // Resolve configuration
@@ -149,7 +152,7 @@ export class Mind extends TypedEventEmitter {
149
152
  if (config.background) {
150
153
  this.background = config.background;
151
154
  }
152
- this.semanticPath = config.semantic?.path ?? ".claude/memory.md";
155
+ this.recentMemoryLimit = config.context?.recentMemoryLimit ?? 3;
153
156
  // Create HindsightClient
154
157
  const clientOptions = {
155
158
  host: config.hindsight.host,
@@ -173,21 +176,17 @@ export class Mind extends TypedEventEmitter {
173
176
  }
174
177
  // Load custom agents from .claude/agents/
175
178
  this.customAgents = await loadCustomAgents(this.projectPath);
176
- // Load semantic memory
177
- this.semantic = new SemanticMemory(this.projectPath, this.semanticPath);
178
- try {
179
- await this.semantic.load();
180
- }
181
- catch (error) {
182
- // Semantic memory is optional - emit error but continue
183
- this.emit("error", error instanceof Error ? error : new Error(String(error)));
184
- }
185
- // Initialize promotion manager
186
- if (this.semantic.isLoaded()) {
187
- this.promotionManager = new PromotionManager(this.semantic, this, {
188
- threshold: DEFAULT_PROMOTION_THRESHOLD,
179
+ // Initialize offline store (always available)
180
+ this.offlineStore = new OfflineMemoryStore({
181
+ projectPath: this.projectPath,
182
+ });
183
+ // Initialize feedback service if enabled
184
+ if (config.feedback?.enabled) {
185
+ this.feedbackService = createFeedbackService(config.feedback, this.projectPath);
186
+ // Initialize offline feedback queue alongside the service
187
+ this.feedbackQueue = new OfflineFeedbackQueue({
188
+ projectPath: this.projectPath,
189
189
  });
190
- this.promotionManager.startListening();
191
190
  }
192
191
  // Mark initialized
193
192
  this.initialized = true;
@@ -273,30 +272,43 @@ export class Mind extends TypedEventEmitter {
273
272
  }
274
273
  this.sessionActive = true;
275
274
  this.sessionStartTime = new Date();
275
+ this.sessionId = `session-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
276
276
  const contextParts = [];
277
277
  // Add agent orchestration instructions
278
278
  const agentInstructions = this.formatAgentInstructions();
279
279
  if (agentInstructions.trim().length > 0) {
280
280
  contextParts.push(agentInstructions);
281
281
  }
282
- // Load semantic memory context
283
- if (this.semantic?.isLoaded()) {
284
- const semanticContext = this.semantic.toContext();
285
- if (semanticContext.trim().length > 0) {
286
- contextParts.push(semanticContext);
287
- }
288
- }
289
- // Recall recent experiences (if not degraded)
290
- if (!this.degraded && this.client) {
291
- try {
292
- const recent = await this.client.recent(this.bankId, 7);
293
- if (recent.length > 0) {
294
- this.emit("memory:recalled", recent);
295
- contextParts.push(this.formatRecentMemories(recent));
282
+ // Recall recent experiences
283
+ // Only fetch a small number (default 3) to keep context small
284
+ if (this.recentMemoryLimit > 0) {
285
+ if (!this.degraded && this.client) {
286
+ // Online mode: fetch from Hindsight
287
+ try {
288
+ const recent = await this.client.recent(this.bankId, this.recentMemoryLimit);
289
+ if (recent.length > 0) {
290
+ this.emit("memory:recalled", recent);
291
+ contextParts.push(this.formatRecentMemories(recent));
292
+ }
293
+ }
294
+ catch (error) {
295
+ this.handleError(error, "onSessionStart recall");
296
+ // Fall through to offline on error
296
297
  }
297
298
  }
298
- catch (error) {
299
- this.handleError(error, "onSessionStart recall");
299
+ // Offline/degraded mode: fetch from local storage
300
+ if (this.degraded && this.offlineStore) {
301
+ try {
302
+ const offlineRecent = await this.offlineStore.getRecent(this.recentMemoryLimit);
303
+ if (offlineRecent.length > 0) {
304
+ const memories = offlineRecent.map(OfflineMemoryStore.toMemory);
305
+ this.emit("memory:recalled", memories);
306
+ contextParts.push(this.formatRecentMemories(memories));
307
+ }
308
+ }
309
+ catch (error) {
310
+ this.emit("error", error instanceof Error ? error : new Error(String(error)));
311
+ }
300
312
  }
301
313
  }
302
314
  return contextParts.join("\n\n");
@@ -305,8 +317,8 @@ export class Mind extends TypedEventEmitter {
305
317
  * Called at session end. Reflects on session and stores observations.
306
318
  *
307
319
  * This method:
308
- * 1. Retains transcript if provided
309
- * 2. Reflects on the session
320
+ * 1. Retains transcript if provided (online or offline)
321
+ * 2. Reflects on the session (online only)
310
322
  * 3. Emits opinion events
311
323
  *
312
324
  * @param transcript - Optional session transcript to store
@@ -315,20 +327,80 @@ export class Mind extends TypedEventEmitter {
315
327
  async onSessionEnd(transcript) {
316
328
  this.assertInitialized();
317
329
  // Retain transcript if provided
318
- if (transcript && !this.degraded && this.client) {
330
+ if (transcript) {
331
+ if (!this.degraded && this.client) {
332
+ // Online mode: store to Hindsight
333
+ try {
334
+ await this.client.retain({
335
+ bankId: this.bankId,
336
+ content: transcript,
337
+ context: "Session transcript",
338
+ });
339
+ this.emit("memory:retained", transcript);
340
+ }
341
+ catch (error) {
342
+ this.handleError(error, "onSessionEnd retain");
343
+ // Fall through to offline on error
344
+ }
345
+ }
346
+ // Degraded/offline mode: store locally
347
+ if (this.degraded && this.offlineStore) {
348
+ try {
349
+ await this.offlineStore.retain(transcript, "experience", {
350
+ context: "Session transcript",
351
+ });
352
+ this.emit("memory:retained", transcript);
353
+ this.emit("offline:stored", {
354
+ content: transcript,
355
+ factType: "experience",
356
+ });
357
+ }
358
+ catch (error) {
359
+ this.emit("error", error instanceof Error ? error : new Error(String(error)));
360
+ }
361
+ }
362
+ }
363
+ // Process feedback if enabled
364
+ if (this.feedbackService && this.sessionId && transcript) {
319
365
  try {
320
- await this.client.retain(this.bankId, transcript, "Session transcript");
321
- this.emit("memory:retained", transcript);
366
+ const feedbackResult = await this.feedbackService.processFeedback(this.sessionId, {
367
+ conversationText: transcript,
368
+ });
369
+ if (feedbackResult.success && feedbackResult.feedback.length > 0) {
370
+ if (!this.degraded && this.client) {
371
+ // Online mode: Submit feedback signals to Hindsight
372
+ await this.client.signal({
373
+ bankId: this.bankId,
374
+ signals: feedbackResult.feedback,
375
+ });
376
+ this.emit("feedback:processed", {
377
+ sessionId: this.sessionId,
378
+ summary: feedbackResult.summary,
379
+ });
380
+ }
381
+ else if (this.feedbackQueue) {
382
+ // Degraded mode: Queue signals for later sync
383
+ await this.feedbackQueue.enqueueBatch(feedbackResult.feedback);
384
+ this.emit("feedback:queued", {
385
+ sessionId: this.sessionId,
386
+ count: feedbackResult.feedback.length,
387
+ });
388
+ }
389
+ }
322
390
  }
323
391
  catch (error) {
324
- this.handleError(error, "onSessionEnd retain");
392
+ // Silently ignore feedback processing errors - non-critical
393
+ this.emit("error", error instanceof Error ? error : new Error("Feedback processing failed"));
325
394
  }
326
395
  }
327
396
  // Reflect on session (if not degraded)
328
397
  let result = null;
329
398
  if (!this.degraded && this.client) {
330
399
  try {
331
- result = await this.client.reflect(this.bankId, "What insights can I draw from this session?");
400
+ result = await this.client.reflect({
401
+ bankId: this.bankId,
402
+ query: "What insights can I draw from this session?",
403
+ });
332
404
  // Emit opinion events
333
405
  for (const opinion of result.opinions) {
334
406
  this.emit("opinion:formed", opinion);
@@ -338,13 +410,10 @@ export class Mind extends TypedEventEmitter {
338
410
  this.handleError(error, "onSessionEnd reflect");
339
411
  }
340
412
  }
341
- // Clear promotion cache for next session
342
- if (this.promotionManager) {
343
- this.promotionManager.clearCache();
344
- }
345
413
  // Reset session state
346
414
  this.sessionActive = false;
347
415
  this.sessionStartTime = null;
416
+ this.sessionId = null;
348
417
  return result;
349
418
  }
350
419
  // ============================================
@@ -353,7 +422,7 @@ export class Mind extends TypedEventEmitter {
353
422
  /**
354
423
  * Search memories for relevant context.
355
424
  *
356
- * In degraded mode, returns an empty array.
425
+ * In degraded mode, searches offline storage (text-based).
357
426
  *
358
427
  * @param query - What to search for
359
428
  * @param options - Search options (budget, factType, etc.)
@@ -361,19 +430,67 @@ export class Mind extends TypedEventEmitter {
361
430
  */
362
431
  async recall(query, options) {
363
432
  this.assertInitialized();
364
- // Degraded mode: return empty array
365
- if (this.degraded || !this.client) {
366
- return [];
367
- }
368
- try {
369
- const memories = await this.client.recall(this.bankId, query, options);
370
- this.emit("memory:recalled", memories);
371
- return memories;
433
+ // Online mode: use Hindsight
434
+ if (!this.degraded && this.client) {
435
+ try {
436
+ // Build recall input, only including defined properties
437
+ const recallInput = {
438
+ bankId: this.bankId,
439
+ query,
440
+ };
441
+ if (options?.budget !== undefined)
442
+ recallInput.budget = options.budget;
443
+ if (options?.factType !== undefined)
444
+ recallInput.factType = options.factType;
445
+ if (options?.maxTokens !== undefined)
446
+ recallInput.maxTokens = options.maxTokens;
447
+ if (options?.includeEntities !== undefined)
448
+ recallInput.includeEntities = options.includeEntities;
449
+ if (options?.boostByUsefulness !== undefined)
450
+ recallInput.boostByUsefulness = options.boostByUsefulness;
451
+ if (options?.usefulnessWeight !== undefined)
452
+ recallInput.usefulnessWeight = options.usefulnessWeight;
453
+ if (options?.minUsefulness !== undefined)
454
+ recallInput.minUsefulness = options.minUsefulness;
455
+ const memories = await this.client.recall(recallInput);
456
+ this.emit("memory:recalled", memories);
457
+ // Track recall for feedback if enabled
458
+ if (this.feedbackService && this.sessionId && memories.length > 0) {
459
+ try {
460
+ await this.feedbackService.trackRecall(this.sessionId, query, memories);
461
+ }
462
+ catch {
463
+ // Silently ignore feedback tracking errors
464
+ }
465
+ }
466
+ return memories;
467
+ }
468
+ catch (error) {
469
+ this.handleError(error, "recall");
470
+ // Fall through to offline on error
471
+ }
372
472
  }
373
- catch (error) {
374
- this.handleError(error, "recall");
375
- return [];
473
+ // Degraded/offline mode: use local storage
474
+ if (this.offlineStore) {
475
+ try {
476
+ const recallOptions = {
477
+ limit: options?.maxTokens ? Math.floor(options.maxTokens / 100) : 10,
478
+ };
479
+ if (options?.factType) {
480
+ recallOptions.factType = options.factType;
481
+ }
482
+ const offlineMemories = await this.offlineStore.recall(query, recallOptions);
483
+ const memories = offlineMemories.map(OfflineMemoryStore.toMemory);
484
+ if (memories.length > 0) {
485
+ this.emit("memory:recalled", memories);
486
+ }
487
+ return memories;
488
+ }
489
+ catch (error) {
490
+ this.emit("error", error instanceof Error ? error : new Error(String(error)));
491
+ }
376
492
  }
493
+ return [];
377
494
  }
378
495
  /**
379
496
  * Reason about accumulated knowledge and form opinions.
@@ -388,30 +505,78 @@ export class Mind extends TypedEventEmitter {
388
505
  if (this.degraded || !this.client) {
389
506
  throw new HindsightError("reflect() requires Hindsight connection", "HINDSIGHT_UNAVAILABLE", { isRetryable: false });
390
507
  }
391
- const result = await this.client.reflect(this.bankId, query);
508
+ const result = await this.client.reflect({
509
+ bankId: this.bankId,
510
+ query,
511
+ });
392
512
  // Emit opinion events
393
513
  for (const opinion of result.opinions) {
394
514
  this.emit("opinion:formed", opinion);
395
515
  }
396
516
  return result;
397
517
  }
518
+ /**
519
+ * Submit feedback signals for recalled facts.
520
+ *
521
+ * @param signals - Array of signal items with factId, signalType, etc.
522
+ * @returns Signal result
523
+ * @throws {HindsightError} If in degraded mode (signal requires Hindsight)
524
+ */
525
+ async signal(signals) {
526
+ this.assertInitialized();
527
+ if (this.degraded || !this.client) {
528
+ throw new HindsightError("signal() requires Hindsight connection", "HINDSIGHT_UNAVAILABLE", { isRetryable: false });
529
+ }
530
+ return this.client.signal({
531
+ bankId: this.bankId,
532
+ signals,
533
+ });
534
+ }
398
535
  /**
399
536
  * Store content in memory.
400
537
  *
401
- * In degraded mode, skips silently and emits an error event.
538
+ * In degraded mode, stores to offline storage for later sync.
402
539
  *
403
540
  * @param content - Content to store
404
541
  * @param context - Optional additional context
542
+ * @param factType - Memory type (default: experience)
405
543
  */
406
- async retain(content, context) {
544
+ async retain(content, context, factType = "experience") {
407
545
  this.assertInitialized();
408
- // Degraded mode: skip silently
409
- if (this.degraded || !this.client) {
410
- this.emit("error", new Error("retain() skipped: Hindsight unavailable"));
411
- return;
546
+ // Online mode: use Hindsight
547
+ if (!this.degraded && this.client) {
548
+ try {
549
+ // Build retain input, only including defined properties
550
+ const retainInput = {
551
+ bankId: this.bankId,
552
+ content,
553
+ };
554
+ if (context !== undefined)
555
+ retainInput.context = context;
556
+ await this.client.retain(retainInput);
557
+ this.emit("memory:retained", content);
558
+ return;
559
+ }
560
+ catch (error) {
561
+ this.handleError(error, "retain");
562
+ // Fall through to offline on error
563
+ }
564
+ }
565
+ // Degraded/offline mode: store locally
566
+ if (this.offlineStore) {
567
+ try {
568
+ const retainOptions = {};
569
+ if (context) {
570
+ retainOptions.context = context;
571
+ }
572
+ await this.offlineStore.retain(content, factType, retainOptions);
573
+ this.emit("memory:retained", content);
574
+ this.emit("offline:stored", { content, factType });
575
+ }
576
+ catch (error) {
577
+ this.emit("error", error instanceof Error ? error : new Error(String(error)));
578
+ }
412
579
  }
413
- await this.client.retain(this.bankId, content, context);
414
- this.emit("memory:retained", content);
415
580
  }
416
581
  // ============================================
417
582
  // Learn Operation
@@ -522,6 +687,7 @@ ${template.outputFormat}
522
687
  }
523
688
  /**
524
689
  * Attempt to recover from degraded mode.
690
+ * If successful, syncs offline memories to Hindsight.
525
691
  *
526
692
  * @returns True if recovery was successful
527
693
  */
@@ -533,10 +699,112 @@ ${template.outputFormat}
533
699
  if (health.healthy) {
534
700
  this.exitDegradedMode();
535
701
  await this.ensureBank();
702
+ // Auto-sync offline memories
703
+ await this.syncOfflineMemories();
704
+ // Auto-sync offline feedback signals
705
+ await this.syncOfflineFeedback();
536
706
  return true;
537
707
  }
538
708
  return false;
539
709
  }
710
+ /**
711
+ * Sync offline memories to Hindsight.
712
+ * Called automatically on recovery from degraded mode.
713
+ *
714
+ * @returns Number of memories synced
715
+ */
716
+ async syncOfflineMemories() {
717
+ if (!this.client || this.degraded || !this.offlineStore) {
718
+ return 0;
719
+ }
720
+ try {
721
+ await this.offlineStore.recordSyncAttempt();
722
+ const unsynced = await this.offlineStore.getUnsynced();
723
+ if (unsynced.length === 0) {
724
+ return 0;
725
+ }
726
+ const syncedIds = [];
727
+ for (const memory of unsynced) {
728
+ try {
729
+ // Build retain input, only including defined properties
730
+ const retainInput = {
731
+ bankId: this.bankId,
732
+ content: memory.text,
733
+ };
734
+ if (memory.context !== undefined)
735
+ retainInput.context = memory.context;
736
+ await this.client.retain(retainInput);
737
+ syncedIds.push(memory.id);
738
+ }
739
+ catch (error) {
740
+ // Stop on error - don't want to skip memories
741
+ this.emit("error", error instanceof Error
742
+ ? error
743
+ : new Error(`Failed to sync memory ${memory.id}`));
744
+ break;
745
+ }
746
+ }
747
+ if (syncedIds.length > 0) {
748
+ await this.offlineStore.markSynced(syncedIds);
749
+ this.emit("offline:synced", { count: syncedIds.length });
750
+ // Clear synced memories to save space
751
+ await this.offlineStore.clearSynced();
752
+ }
753
+ return syncedIds.length;
754
+ }
755
+ catch (error) {
756
+ this.emit("error", error instanceof Error ? error : new Error(String(error)));
757
+ return 0;
758
+ }
759
+ }
760
+ /**
761
+ * Sync offline feedback signals to Hindsight.
762
+ * Called automatically on recovery from degraded mode.
763
+ *
764
+ * @returns Number of feedback signals synced
765
+ */
766
+ async syncOfflineFeedback() {
767
+ if (!this.client || this.degraded || !this.feedbackQueue) {
768
+ return 0;
769
+ }
770
+ try {
771
+ await this.feedbackQueue.recordSyncAttempt();
772
+ const unsynced = await this.feedbackQueue.getUnsynced();
773
+ if (unsynced.length === 0) {
774
+ return 0;
775
+ }
776
+ // Convert offline signals back to SignalItem format
777
+ const signals = unsynced.map(OfflineFeedbackQueue.toSignalItem);
778
+ // Submit all signals in one batch
779
+ await this.client.signal({
780
+ bankId: this.bankId,
781
+ signals,
782
+ });
783
+ // Mark all as synced
784
+ const ids = unsynced.map((s) => s.id);
785
+ await this.feedbackQueue.markSynced(ids);
786
+ this.emit("feedback:synced", { count: ids.length });
787
+ // Clear synced signals to save space
788
+ await this.feedbackQueue.clearSynced();
789
+ return ids.length;
790
+ }
791
+ catch (error) {
792
+ this.emit("error", error instanceof Error ? error : new Error(String(error)));
793
+ return 0;
794
+ }
795
+ }
796
+ /**
797
+ * Get offline feedback queue (for CLI commands).
798
+ */
799
+ getOfflineFeedbackQueue() {
800
+ return this.feedbackQueue;
801
+ }
802
+ /**
803
+ * Get offline memory store (for CLI commands).
804
+ */
805
+ getOfflineStore() {
806
+ return this.offlineStore;
807
+ }
540
808
  /**
541
809
  * Enter degraded mode.
542
810
  * @internal
@@ -603,28 +871,6 @@ ${template.outputFormat}
603
871
  isSessionActive() {
604
872
  return this.sessionActive;
605
873
  }
606
- /**
607
- * Get the path to semantic memory file.
608
- */
609
- getSemanticPath() {
610
- return this.semanticPath;
611
- }
612
- /**
613
- * Get the SemanticMemory instance.
614
- *
615
- * @returns SemanticMemory or null if not initialized
616
- */
617
- getSemanticMemory() {
618
- return this.semantic;
619
- }
620
- /**
621
- * Get the PromotionManager instance.
622
- *
623
- * @returns PromotionManager or null if not initialized
624
- */
625
- getPromotionManager() {
626
- return this.promotionManager;
627
- }
628
874
  /**
629
875
  * Get the session start time, or null if no session is active.
630
876
  */
@@ -642,15 +888,18 @@ ${template.outputFormat}
642
888
  }
643
889
  /**
644
890
  * Format recent memories as context string.
891
+ * Shows fuller context since we only fetch a few memories.
645
892
  * @internal
646
893
  */
647
894
  formatRecentMemories(memories) {
648
895
  if (memories.length === 0)
649
896
  return "";
650
- const lines = ["### Recent Context"];
651
- for (const mem of memories.slice(0, 5)) {
897
+ const lines = ["## Recent Activity"];
898
+ for (const mem of memories) {
652
899
  const date = new Date(mem.createdAt).toLocaleDateString();
653
- const text = mem.text.length > 100 ? `${mem.text.slice(0, 100)}...` : mem.text;
900
+ // Show more text since we're fetching fewer memories
901
+ const maxLen = 200;
902
+ const text = mem.text.length > maxLen ? `${mem.text.slice(0, maxLen)}...` : mem.text;
654
903
  lines.push(`- ${date}: ${text}`);
655
904
  }
656
905
  return lines.join("\n");
@@ -714,19 +963,17 @@ ${template.outputFormat}
714
963
  * After calling dispose(), the Mind instance should not be used.
715
964
  */
716
965
  dispose() {
717
- // Stop promotion manager event listeners
718
- if (this.promotionManager) {
719
- this.promotionManager.stopListening();
720
- this.promotionManager = null;
721
- }
722
966
  // Clear references
723
- this.semantic = null;
724
967
  this.client = null;
968
+ this.offlineStore = null;
969
+ this.feedbackService = null;
970
+ this.feedbackQueue = null;
725
971
  this.customAgents = [];
726
972
  // Reset state
727
973
  this.initialized = false;
728
974
  this.sessionActive = false;
729
975
  this.sessionStartTime = null;
976
+ this.sessionId = null;
730
977
  this.degraded = false;
731
978
  }
732
979
  }