tracelattice 1.3.0 → 1.3.2

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 (75) hide show
  1. package/README.md +2 -0
  2. package/dist/ServerConfig.d.ts +10 -11
  3. package/dist/ServerConfig.d.ts.map +1 -1
  4. package/dist/ServerConfig.js +7 -7
  5. package/dist/ServerConfig.js.map +1 -1
  6. package/dist/__tests__/core/retraction.test.d.ts +2 -0
  7. package/dist/__tests__/core/retraction.test.d.ts.map +1 -0
  8. package/dist/__tests__/helpers/factories.d.ts +2 -0
  9. package/dist/__tests__/helpers/factories.d.ts.map +1 -1
  10. package/dist/cli.js +6 -6
  11. package/dist/core/HistoryManager.d.ts +45 -523
  12. package/dist/core/HistoryManager.d.ts.map +1 -1
  13. package/dist/core/HistoryManager.js +101 -249
  14. package/dist/core/HistoryManager.js.map +1 -1
  15. package/dist/core/IHistoryManager.d.ts +17 -0
  16. package/dist/core/IHistoryManager.d.ts.map +1 -1
  17. package/dist/core/PersistenceBuffer.d.ts +110 -0
  18. package/dist/core/PersistenceBuffer.d.ts.map +1 -0
  19. package/dist/core/PersistenceBuffer.js +141 -0
  20. package/dist/core/PersistenceBuffer.js.map +1 -0
  21. package/dist/core/SessionManager.d.ts +58 -0
  22. package/dist/core/SessionManager.d.ts.map +1 -0
  23. package/dist/core/SessionManager.js +65 -0
  24. package/dist/core/SessionManager.js.map +1 -0
  25. package/dist/core/ThoughtEvaluator.d.ts.map +1 -1
  26. package/dist/core/ThoughtEvaluator.js +16 -4
  27. package/dist/core/ThoughtEvaluator.js.map +1 -1
  28. package/dist/core/ThoughtFormatter.d.ts.map +1 -1
  29. package/dist/core/ThoughtFormatter.js +2 -1
  30. package/dist/core/ThoughtFormatter.js.map +1 -1
  31. package/dist/core/ThoughtProcessor.d.ts +18 -0
  32. package/dist/core/ThoughtProcessor.d.ts.map +1 -1
  33. package/dist/core/ThoughtProcessor.js +47 -16
  34. package/dist/core/ThoughtProcessor.js.map +1 -1
  35. package/dist/core/evaluator/Aggregator.d.ts.map +1 -1
  36. package/dist/core/evaluator/Aggregator.js +6 -2
  37. package/dist/core/evaluator/Aggregator.js.map +1 -1
  38. package/dist/core/evaluator/PatternDetector.js +2 -2
  39. package/dist/core/evaluator/PatternDetector.js.map +1 -1
  40. package/dist/core/evaluator/SignalComputer.d.ts +57 -5
  41. package/dist/core/evaluator/SignalComputer.d.ts.map +1 -1
  42. package/dist/core/evaluator/SignalComputer.js +52 -10
  43. package/dist/core/evaluator/SignalComputer.js.map +1 -1
  44. package/dist/core/graph/EdgeEmitter.d.ts +64 -0
  45. package/dist/core/graph/EdgeEmitter.d.ts.map +1 -0
  46. package/dist/core/graph/EdgeEmitter.js +99 -0
  47. package/dist/core/graph/EdgeEmitter.js.map +1 -0
  48. package/dist/core/reasoning.d.ts +17 -2
  49. package/dist/core/reasoning.d.ts.map +1 -1
  50. package/dist/core/thought.d.ts +7 -0
  51. package/dist/core/thought.d.ts.map +1 -1
  52. package/dist/core/tools/InMemorySuspensionStore.js +1 -1
  53. package/dist/core/tools/InMemorySuspensionStore.js.map +1 -1
  54. package/dist/lib.d.ts.map +1 -1
  55. package/dist/lib.js +11 -0
  56. package/dist/lib.js.map +1 -1
  57. package/dist/persistence/FilePersistence.d.ts +6 -0
  58. package/dist/persistence/FilePersistence.d.ts.map +1 -1
  59. package/dist/persistence/FilePersistence.js +8 -0
  60. package/dist/persistence/FilePersistence.js.map +1 -1
  61. package/dist/persistence/MemoryPersistence.d.ts +6 -0
  62. package/dist/persistence/MemoryPersistence.d.ts.map +1 -1
  63. package/dist/persistence/MemoryPersistence.js +3 -0
  64. package/dist/persistence/MemoryPersistence.js.map +1 -1
  65. package/dist/persistence/PersistenceBackend.d.ts +6 -0
  66. package/dist/persistence/PersistenceBackend.d.ts.map +1 -1
  67. package/dist/persistence/SqlitePersistence.d.ts +6 -0
  68. package/dist/persistence/SqlitePersistence.d.ts.map +1 -1
  69. package/dist/persistence/SqlitePersistence.js +4 -0
  70. package/dist/persistence/SqlitePersistence.js.map +1 -1
  71. package/dist/schema.d.ts +3 -2
  72. package/dist/schema.d.ts.map +1 -1
  73. package/dist/schema.js +8 -7
  74. package/dist/schema.js.map +1 -1
  75. package/package.json +2 -2
@@ -96,5 +96,22 @@ export interface IHistoryManager {
96
96
  * @returns The last-seen array of skill names, or undefined if never set
97
97
  */
98
98
  getAvailableSkills(sessionId?: string): string[] | undefined;
99
+ /**
100
+ * Pre-declares a branch ID without adding any thoughts.
101
+ * Allows merge_branch_ids to reference branches that have not yet received thoughts.
102
+ *
103
+ * @param sessionId - Optional session ID (defaults to global session)
104
+ * @param branchId - The branch identifier to register
105
+ * @throws ValidationError if branchId is empty or already exists
106
+ */
107
+ registerBranch(sessionId: string | undefined, branchId: string): void;
108
+ /**
109
+ * Checks whether a branch exists (has thoughts OR was pre-declared).
110
+ *
111
+ * @param sessionId - Optional session ID (defaults to global session)
112
+ * @param branchId - The branch identifier to check
113
+ * @returns true if the branch exists or has been registered
114
+ */
115
+ branchExists(sessionId: string | undefined, branchId: string): boolean;
99
116
  }
100
117
  //# sourceMappingURL=IHistoryManager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"IHistoryManager.d.ts","sourceRoot":"","sources":["../../src/core/IHistoryManager.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,WAAW,eAAe;IAC/B;;;;;OAKG;IACH,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAEvC;;;;;OAKG;IACH,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,WAAW,EAAE,CAAC;IAE9C;;;;;OAKG;IACH,gBAAgB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAE7C;;;;;OAKG;IACH,WAAW,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;IAE/D;;;;;OAKG;IACH,YAAY,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAE3C;;;;;;OAMG;IACH,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEhC;;;;;OAKG;IACH,oBAAoB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;IAE/D;;;;;OAKG;IACH,kBAAkB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;CAC7D"}
1
+ {"version":3,"file":"IHistoryManager.d.ts","sourceRoot":"","sources":["../../src/core/IHistoryManager.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,WAAW,eAAe;IAC/B;;;;;OAKG;IACH,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAEvC;;;;;OAKG;IACH,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,WAAW,EAAE,CAAC;IAE9C;;;;;OAKG;IACH,gBAAgB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAE7C;;;;;OAKG;IACH,WAAW,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;IAE/D;;;;;OAKG;IACH,YAAY,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAE3C;;;;;;OAMG;IACH,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEhC;;;;;OAKG;IACH,oBAAoB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;IAE/D;;;;;OAKG;IACH,kBAAkB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;IAE7D;;;;;;;OAOG;IACH,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAEtE;;;;;;OAMG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;CACvE"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * PersistenceBuffer — owns the periodic flush timer, retry/backoff logic, and
3
+ * concurrent-flush guarding for HistoryManager's persistence write buffer.
4
+ *
5
+ * Extracted from HistoryManager. The session Map and write buffers remain
6
+ * owned by HistoryManager (passed by reference) so public access patterns
7
+ * (e.g. `manager._sessions`) are preserved.
8
+ *
9
+ * @module PersistenceBuffer
10
+ */
11
+ import type { IEdgeStore } from '../contracts/interfaces.js';
12
+ import type { Logger } from '../logger/StructuredLogger.js';
13
+ import type { PersistenceBackend } from '../persistence/PersistenceBackend.js';
14
+ import type { ThoughtData } from './thought.js';
15
+ /** Minimal session view: anything that owns a `writeBuffer`. */
16
+ export interface BufferedSession {
17
+ writeBuffer: ThoughtData[];
18
+ }
19
+ /** Event emitter contract for persistence error events. */
20
+ export interface PersistenceEventEmitter {
21
+ emit(event: 'persistenceError', payload: {
22
+ operation: string;
23
+ error: Error;
24
+ }): boolean;
25
+ }
26
+ /** Configuration options for PersistenceBuffer. */
27
+ export interface PersistenceBufferConfig<S extends BufferedSession> {
28
+ persistence: PersistenceBackend;
29
+ bufferSize: number;
30
+ flushInterval: number;
31
+ maxRetries: number;
32
+ defaultSessionId: string;
33
+ /** Returns the live session map (called on each flush). */
34
+ getSessions: () => Map<string, S>;
35
+ /** Returns the requeue target session (default-session buffer). */
36
+ getDefaultSession: () => S;
37
+ /** Optional EdgeStore for flushing edges alongside thoughts. */
38
+ edgeStore?: IEdgeStore;
39
+ /** Optional emitter for `persistenceError` events. */
40
+ eventEmitter?: PersistenceEventEmitter | null;
41
+ logger?: Logger;
42
+ }
43
+ /**
44
+ * Manages buffered persistence writes with periodic flushing, exponential
45
+ * backoff retries, concurrent-flush guarding, and edge store integration.
46
+ */
47
+ export declare class PersistenceBuffer<S extends BufferedSession> {
48
+ private readonly _persistence;
49
+ private readonly _bufferSize;
50
+ private readonly _flushInterval;
51
+ private readonly _maxRetries;
52
+ private readonly _defaultSessionId;
53
+ private readonly _getSessions;
54
+ private readonly _getDefaultSession;
55
+ private readonly _edgeStore?;
56
+ private _eventEmitter;
57
+ private readonly _logger;
58
+ private _flushTimer;
59
+ private _isFlushing;
60
+ private _flushRetryCount;
61
+ constructor(config: PersistenceBufferConfig<S>);
62
+ /** Returns the underlying flush timer (for test introspection). */
63
+ get timer(): ReturnType<typeof setInterval> | null;
64
+ /** Returns true when a flush is in progress. */
65
+ get isFlushing(): boolean;
66
+ /** Sets / replaces the persistence error event emitter. */
67
+ setEventEmitter(emitter: PersistenceEventEmitter | null): void;
68
+ /**
69
+ * Buffers a thought into the given session's write buffer. Triggers an
70
+ * immediate flush when the buffer reaches `bufferSize`.
71
+ */
72
+ bufferThought(session: BufferedSession, thought: ThoughtData): void;
73
+ /**
74
+ * Starts the periodic flush timer. No-op if already started.
75
+ * The timer is unref'd so it does not block process exit.
76
+ */
77
+ startFlushTimer(): void;
78
+ /** Stops the periodic flush timer. */
79
+ stopFlushTimer(): void;
80
+ /**
81
+ * Flushes the write buffer to the persistence backend.
82
+ *
83
+ * Collects all buffered thoughts across all sessions and saves them
84
+ * individually with retry logic. On persistent failure (all retries
85
+ * exhausted), emits a `persistenceError` event and re-queues failed items
86
+ * into the default session's buffer.
87
+ *
88
+ * Safe to call concurrently — duplicate calls are skipped.
89
+ */
90
+ flush(): Promise<void>;
91
+ /**
92
+ * Flushes edges for all known sessions to the persistence backend.
93
+ * No-op when EdgeStore is unavailable.
94
+ */
95
+ private _flushEdges;
96
+ /**
97
+ * Flushes a single thought to persistence with exponential backoff retry.
98
+ * @returns true if saved successfully, false otherwise
99
+ */
100
+ private _flushSingleThought;
101
+ /**
102
+ * Handles the result of a flush operation, re-queuing failures into the
103
+ * default session's buffer and emitting a `persistenceError` event when
104
+ * any items failed.
105
+ */
106
+ private _handleFlushResult;
107
+ /** Returns a promise that resolves after the specified delay. */
108
+ private _delay;
109
+ }
110
+ //# sourceMappingURL=PersistenceBuffer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PersistenceBuffer.d.ts","sourceRoot":"","sources":["../../src/core/PersistenceBuffer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAE7D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAE5D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAC/E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,gEAAgE;AAChE,MAAM,WAAW,eAAe;IAC/B,WAAW,EAAE,WAAW,EAAE,CAAC;CAC3B;AAED,2DAA2D;AAC3D,MAAM,WAAW,uBAAuB;IACvC,IAAI,CAAC,KAAK,EAAE,kBAAkB,EAAE,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,GAAG,OAAO,CAAC;CACvF;AAED,mDAAmD;AACnD,MAAM,WAAW,uBAAuB,CAAC,CAAC,SAAS,eAAe;IACjE,WAAW,EAAE,kBAAkB,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,2DAA2D;IAC3D,WAAW,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAClC,mEAAmE;IACnE,iBAAiB,EAAE,MAAM,CAAC,CAAC;IAC3B,gEAAgE;IAChE,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB,sDAAsD;IACtD,YAAY,CAAC,EAAE,uBAAuB,GAAG,IAAI,CAAC;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,qBAAa,iBAAiB,CAAC,CAAC,SAAS,eAAe;IACvD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAuB;IACpD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAU;IAC7C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAa;IACzC,OAAO,CAAC,aAAa,CAAiC;IACtD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IAEjC,OAAO,CAAC,WAAW,CAA+C;IAClE,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,gBAAgB,CAAK;gBAEjB,MAAM,EAAE,uBAAuB,CAAC,CAAC,CAAC;IAa9C,mEAAmE;IACnE,IAAW,KAAK,IAAI,UAAU,CAAC,OAAO,WAAW,CAAC,GAAG,IAAI,CAExD;IAED,gDAAgD;IAChD,IAAW,UAAU,IAAI,OAAO,CAE/B;IAED,2DAA2D;IACpD,eAAe,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,GAAG,IAAI;IAIrE;;;OAGG;IACI,aAAa,CAAC,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI;IAgB1E;;;OAGG;IACI,eAAe,IAAI,IAAI;IAU9B,sCAAsC;IAC/B,cAAc,IAAI,IAAI;IAO7B;;;;;;;;;OASG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkCnC;;;OAGG;YACW,WAAW;IAkBzB;;;OAGG;YACW,mBAAmB;IA4BjC;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAwB1B,iEAAiE;IACjE,OAAO,CAAC,MAAM;CAGd"}
@@ -0,0 +1,141 @@
1
+ import { getErrorMessage } from "../errors.js";
2
+ import { NullLogger } from "../logger/NullLogger.js";
3
+ class PersistenceBuffer {
4
+ _persistence;
5
+ _bufferSize;
6
+ _flushInterval;
7
+ _maxRetries;
8
+ _defaultSessionId;
9
+ _getSessions;
10
+ _getDefaultSession;
11
+ _edgeStore;
12
+ _eventEmitter;
13
+ _logger;
14
+ _flushTimer = null;
15
+ _isFlushing = false;
16
+ _flushRetryCount = 0;
17
+ constructor(config){
18
+ this._persistence = config.persistence;
19
+ this._bufferSize = config.bufferSize;
20
+ this._flushInterval = config.flushInterval;
21
+ this._maxRetries = config.maxRetries;
22
+ this._defaultSessionId = config.defaultSessionId;
23
+ this._getSessions = config.getSessions;
24
+ this._getDefaultSession = config.getDefaultSession;
25
+ this._edgeStore = config.edgeStore;
26
+ this._eventEmitter = config.eventEmitter ?? null;
27
+ this._logger = config.logger ?? new NullLogger();
28
+ }
29
+ get timer() {
30
+ return this._flushTimer;
31
+ }
32
+ get isFlushing() {
33
+ return this._isFlushing;
34
+ }
35
+ setEventEmitter(emitter) {
36
+ this._eventEmitter = emitter;
37
+ }
38
+ bufferThought(session, thought) {
39
+ if (session.writeBuffer.length >= this._bufferSize && this._isFlushing) this._logger.info('Write buffer full and flush in progress, applying backpressure', {
40
+ bufferSize: session.writeBuffer.length,
41
+ maxSize: this._bufferSize
42
+ });
43
+ session.writeBuffer.push(thought);
44
+ if (session.writeBuffer.length >= this._bufferSize) this.flush();
45
+ }
46
+ startFlushTimer() {
47
+ if (null !== this._flushTimer) return;
48
+ this._flushTimer = setInterval(()=>{
49
+ this.flush();
50
+ }, this._flushInterval);
51
+ if (this._flushTimer && 'object' == typeof this._flushTimer && 'unref' in this._flushTimer) this._flushTimer.unref();
52
+ }
53
+ stopFlushTimer() {
54
+ if (null !== this._flushTimer) {
55
+ clearInterval(this._flushTimer);
56
+ this._flushTimer = null;
57
+ }
58
+ }
59
+ async flush() {
60
+ if (this._isFlushing) return;
61
+ const allPending = [];
62
+ for (const session of this._getSessions().values())if (session.writeBuffer.length > 0) allPending.push(...session.writeBuffer.splice(0));
63
+ if (0 === allPending.length) return;
64
+ this._isFlushing = true;
65
+ const failedItems = [];
66
+ try {
67
+ for (const thought of allPending){
68
+ const saved = await this._flushSingleThought(thought);
69
+ if (!saved) failedItems.push(thought);
70
+ }
71
+ this._handleFlushResult(failedItems, allPending.length);
72
+ if (this._edgeStore) await this._flushEdges();
73
+ } finally{
74
+ this._isFlushing = false;
75
+ }
76
+ }
77
+ async _flushEdges() {
78
+ if (!this._edgeStore) return;
79
+ const sessionKeys = new Set(this._getSessions().keys());
80
+ sessionKeys.add(this._defaultSessionId);
81
+ for (const sessionId of sessionKeys){
82
+ const edges = this._edgeStore.edgesForSession(sessionId);
83
+ if (0 !== edges.length) try {
84
+ await this._persistence.saveEdges(sessionId, edges);
85
+ } catch (err) {
86
+ this._logger.info('Failed to persist edges for session', {
87
+ sessionId,
88
+ error: getErrorMessage(err)
89
+ });
90
+ }
91
+ }
92
+ }
93
+ async _flushSingleThought(thought) {
94
+ const backoffDelays = [
95
+ 100,
96
+ 500,
97
+ 2000
98
+ ];
99
+ for(let attempt = 0; attempt <= this._maxRetries; attempt++)try {
100
+ await this._persistence.saveThought(thought);
101
+ return true;
102
+ } catch (err) {
103
+ if (attempt < this._maxRetries) {
104
+ const delay = backoffDelays[attempt] ?? backoffDelays[backoffDelays.length - 1];
105
+ this._logger.info(`Persistence retry ${attempt + 1}/${this._maxRetries}`, {
106
+ thoughtNumber: thought.thought_number,
107
+ delay,
108
+ error: getErrorMessage(err)
109
+ });
110
+ await this._delay(delay);
111
+ } else this._logger.info('All persistence retries exhausted for thought', {
112
+ thoughtNumber: thought.thought_number,
113
+ error: getErrorMessage(err)
114
+ });
115
+ }
116
+ return false;
117
+ }
118
+ _handleFlushResult(failedItems, totalCount) {
119
+ if (failedItems.length > 0) {
120
+ const defaultSession = this._getDefaultSession();
121
+ defaultSession.writeBuffer.unshift(...failedItems);
122
+ this._flushRetryCount++;
123
+ const error = new Error(`Failed to persist ${failedItems.length} thoughts after ${this._maxRetries} retries`);
124
+ this._eventEmitter?.emit('persistenceError', {
125
+ operation: 'flushBuffer',
126
+ error
127
+ });
128
+ this._logger.info('Flush completed with failures', {
129
+ failed: failedItems.length,
130
+ total: totalCount,
131
+ consecutiveFailures: this._flushRetryCount
132
+ });
133
+ } else this._flushRetryCount = 0;
134
+ }
135
+ _delay(ms) {
136
+ return new Promise((resolve)=>setTimeout(resolve, ms));
137
+ }
138
+ }
139
+ export { PersistenceBuffer };
140
+
141
+ //# sourceMappingURL=PersistenceBuffer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core/PersistenceBuffer.js","sources":["../../src/core/PersistenceBuffer.ts"],"sourcesContent":["/**\n * PersistenceBuffer — owns the periodic flush timer, retry/backoff logic, and\n * concurrent-flush guarding for HistoryManager's persistence write buffer.\n *\n * Extracted from HistoryManager. The session Map and write buffers remain\n * owned by HistoryManager (passed by reference) so public access patterns\n * (e.g. `manager._sessions`) are preserved.\n *\n * @module PersistenceBuffer\n */\n\nimport type { IEdgeStore } from '../contracts/interfaces.js';\nimport { getErrorMessage } from '../errors.js';\nimport type { Logger } from '../logger/StructuredLogger.js';\nimport { NullLogger } from '../logger/NullLogger.js';\nimport type { PersistenceBackend } from '../persistence/PersistenceBackend.js';\nimport type { ThoughtData } from './thought.js';\n\n/** Minimal session view: anything that owns a `writeBuffer`. */\nexport interface BufferedSession {\n\twriteBuffer: ThoughtData[];\n}\n\n/** Event emitter contract for persistence error events. */\nexport interface PersistenceEventEmitter {\n\temit(event: 'persistenceError', payload: { operation: string; error: Error }): boolean;\n}\n\n/** Configuration options for PersistenceBuffer. */\nexport interface PersistenceBufferConfig<S extends BufferedSession> {\n\tpersistence: PersistenceBackend;\n\tbufferSize: number;\n\tflushInterval: number;\n\tmaxRetries: number;\n\tdefaultSessionId: string;\n\t/** Returns the live session map (called on each flush). */\n\tgetSessions: () => Map<string, S>;\n\t/** Returns the requeue target session (default-session buffer). */\n\tgetDefaultSession: () => S;\n\t/** Optional EdgeStore for flushing edges alongside thoughts. */\n\tedgeStore?: IEdgeStore;\n\t/** Optional emitter for `persistenceError` events. */\n\teventEmitter?: PersistenceEventEmitter | null;\n\tlogger?: Logger;\n}\n\n/**\n * Manages buffered persistence writes with periodic flushing, exponential\n * backoff retries, concurrent-flush guarding, and edge store integration.\n */\nexport class PersistenceBuffer<S extends BufferedSession> {\n\tprivate readonly _persistence: PersistenceBackend;\n\tprivate readonly _bufferSize: number;\n\tprivate readonly _flushInterval: number;\n\tprivate readonly _maxRetries: number;\n\tprivate readonly _defaultSessionId: string;\n\tprivate readonly _getSessions: () => Map<string, S>;\n\tprivate readonly _getDefaultSession: () => S;\n\tprivate readonly _edgeStore?: IEdgeStore;\n\tprivate _eventEmitter: PersistenceEventEmitter | null;\n\tprivate readonly _logger: Logger;\n\n\tprivate _flushTimer: ReturnType<typeof setInterval> | null = null;\n\tprivate _isFlushing = false;\n\tprivate _flushRetryCount = 0;\n\n\tconstructor(config: PersistenceBufferConfig<S>) {\n\t\tthis._persistence = config.persistence;\n\t\tthis._bufferSize = config.bufferSize;\n\t\tthis._flushInterval = config.flushInterval;\n\t\tthis._maxRetries = config.maxRetries;\n\t\tthis._defaultSessionId = config.defaultSessionId;\n\t\tthis._getSessions = config.getSessions;\n\t\tthis._getDefaultSession = config.getDefaultSession;\n\t\tthis._edgeStore = config.edgeStore;\n\t\tthis._eventEmitter = config.eventEmitter ?? null;\n\t\tthis._logger = config.logger ?? new NullLogger();\n\t}\n\n\t/** Returns the underlying flush timer (for test introspection). */\n\tpublic get timer(): ReturnType<typeof setInterval> | null {\n\t\treturn this._flushTimer;\n\t}\n\n\t/** Returns true when a flush is in progress. */\n\tpublic get isFlushing(): boolean {\n\t\treturn this._isFlushing;\n\t}\n\n\t/** Sets / replaces the persistence error event emitter. */\n\tpublic setEventEmitter(emitter: PersistenceEventEmitter | null): void {\n\t\tthis._eventEmitter = emitter;\n\t}\n\n\t/**\n\t * Buffers a thought into the given session's write buffer. Triggers an\n\t * immediate flush when the buffer reaches `bufferSize`.\n\t */\n\tpublic bufferThought(session: BufferedSession, thought: ThoughtData): void {\n\t\t// Backpressure: if buffer is full and flush is in progress, log warning\n\t\tif (session.writeBuffer.length >= this._bufferSize && this._isFlushing) {\n\t\t\tthis._logger.info('Write buffer full and flush in progress, applying backpressure', {\n\t\t\t\tbufferSize: session.writeBuffer.length,\n\t\t\t\tmaxSize: this._bufferSize,\n\t\t\t});\n\t\t}\n\n\t\tsession.writeBuffer.push(thought);\n\n\t\tif (session.writeBuffer.length >= this._bufferSize) {\n\t\t\tvoid this.flush();\n\t\t}\n\t}\n\n\t/**\n\t * Starts the periodic flush timer. No-op if already started.\n\t * The timer is unref'd so it does not block process exit.\n\t */\n\tpublic startFlushTimer(): void {\n\t\tif (this._flushTimer !== null) return;\n\t\tthis._flushTimer = setInterval(() => {\n\t\t\tvoid this.flush();\n\t\t}, this._flushInterval);\n\t\tif (this._flushTimer && typeof this._flushTimer === 'object' && 'unref' in this._flushTimer) {\n\t\t\tthis._flushTimer.unref();\n\t\t}\n\t}\n\n\t/** Stops the periodic flush timer. */\n\tpublic stopFlushTimer(): void {\n\t\tif (this._flushTimer !== null) {\n\t\t\tclearInterval(this._flushTimer);\n\t\t\tthis._flushTimer = null;\n\t\t}\n\t}\n\n\t/**\n\t * Flushes the write buffer to the persistence backend.\n\t *\n\t * Collects all buffered thoughts across all sessions and saves them\n\t * individually with retry logic. On persistent failure (all retries\n\t * exhausted), emits a `persistenceError` event and re-queues failed items\n\t * into the default session's buffer.\n\t *\n\t * Safe to call concurrently — duplicate calls are skipped.\n\t */\n\tpublic async flush(): Promise<void> {\n\t\tif (this._isFlushing) return;\n\n\t\t// Collect all pending writes from all sessions\n\t\tconst allPending: ThoughtData[] = [];\n\t\tfor (const session of this._getSessions().values()) {\n\t\t\tif (session.writeBuffer.length > 0) {\n\t\t\t\tallPending.push(...session.writeBuffer.splice(0));\n\t\t\t}\n\t\t}\n\n\t\tif (allPending.length === 0) return;\n\n\t\tthis._isFlushing = true;\n\t\tconst failedItems: ThoughtData[] = [];\n\n\t\ttry {\n\t\t\tfor (const thought of allPending) {\n\t\t\t\tconst saved = await this._flushSingleThought(thought);\n\t\t\t\tif (!saved) {\n\t\t\t\t\tfailedItems.push(thought);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis._handleFlushResult(failedItems, allPending.length);\n\n\t\t\tif (this._edgeStore) {\n\t\t\t\tawait this._flushEdges();\n\t\t\t}\n\t\t} finally {\n\t\t\tthis._isFlushing = false;\n\t\t}\n\t}\n\n\t/**\n\t * Flushes edges for all known sessions to the persistence backend.\n\t * No-op when EdgeStore is unavailable.\n\t */\n\tprivate async _flushEdges(): Promise<void> {\n\t\tif (!this._edgeStore) return;\n\t\tconst sessionKeys = new Set<string>(this._getSessions().keys());\n\t\tsessionKeys.add(this._defaultSessionId);\n\t\tfor (const sessionId of sessionKeys) {\n\t\t\tconst edges = this._edgeStore.edgesForSession(sessionId);\n\t\t\tif (edges.length === 0) continue;\n\t\t\ttry {\n\t\t\t\tawait this._persistence.saveEdges(sessionId, edges);\n\t\t\t} catch (err) {\n\t\t\t\tthis._logger.info('Failed to persist edges for session', {\n\t\t\t\t\tsessionId,\n\t\t\t\t\terror: getErrorMessage(err),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Flushes a single thought to persistence with exponential backoff retry.\n\t * @returns true if saved successfully, false otherwise\n\t */\n\tprivate async _flushSingleThought(thought: ThoughtData): Promise<boolean> {\n\t\tconst backoffDelays = [100, 500, 2000];\n\n\t\tfor (let attempt = 0; attempt <= this._maxRetries; attempt++) {\n\t\t\ttry {\n\t\t\t\tawait this._persistence.saveThought(thought);\n\t\t\t\treturn true;\n\t\t\t} catch (err) {\n\t\t\t\tif (attempt < this._maxRetries) {\n\t\t\t\t\tconst delay = backoffDelays[attempt] ?? backoffDelays[backoffDelays.length - 1]!;\n\t\t\t\t\tthis._logger.info(`Persistence retry ${attempt + 1}/${this._maxRetries}`, {\n\t\t\t\t\t\tthoughtNumber: thought.thought_number,\n\t\t\t\t\t\tdelay,\n\t\t\t\t\t\terror: getErrorMessage(err),\n\t\t\t\t\t});\n\t\t\t\t\tawait this._delay(delay);\n\t\t\t\t} else {\n\t\t\t\t\tthis._logger.info('All persistence retries exhausted for thought', {\n\t\t\t\t\t\tthoughtNumber: thought.thought_number,\n\t\t\t\t\t\terror: getErrorMessage(err),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Handles the result of a flush operation, re-queuing failures into the\n\t * default session's buffer and emitting a `persistenceError` event when\n\t * any items failed.\n\t */\n\tprivate _handleFlushResult(failedItems: ThoughtData[], totalCount: number): void {\n\t\tif (failedItems.length > 0) {\n\t\t\tconst defaultSession = this._getDefaultSession();\n\t\t\tdefaultSession.writeBuffer.unshift(...failedItems);\n\t\t\tthis._flushRetryCount++;\n\n\t\t\tconst error = new Error(\n\t\t\t\t`Failed to persist ${failedItems.length} thoughts after ${this._maxRetries} retries`\n\t\t\t);\n\t\t\tthis._eventEmitter?.emit('persistenceError', {\n\t\t\t\toperation: 'flushBuffer',\n\t\t\t\terror,\n\t\t\t});\n\n\t\t\tthis._logger.info('Flush completed with failures', {\n\t\t\t\tfailed: failedItems.length,\n\t\t\t\ttotal: totalCount,\n\t\t\t\tconsecutiveFailures: this._flushRetryCount,\n\t\t\t});\n\t\t} else {\n\t\t\tthis._flushRetryCount = 0;\n\t\t}\n\t}\n\n\t/** Returns a promise that resolves after the specified delay. */\n\tprivate _delay(ms: number): Promise<void> {\n\t\treturn new Promise((resolve) => setTimeout(resolve, ms));\n\t}\n}\n"],"names":["PersistenceBuffer","config","NullLogger","emitter","session","thought","setInterval","clearInterval","allPending","failedItems","saved","sessionKeys","Set","sessionId","edges","err","getErrorMessage","backoffDelays","attempt","delay","totalCount","defaultSession","error","Error","ms","Promise","resolve","setTimeout"],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * SessionManager — owns periodic session cleanup timer and TTL/LRU eviction logic.
3
+ *
4
+ * Extracted from HistoryManager. The session Map remains owned by HistoryManager
5
+ * (passed by reference) so that public access patterns are preserved.
6
+ *
7
+ * @module SessionManager
8
+ */
9
+ import type { Logger } from '../logger/StructuredLogger.js';
10
+ /** Minimal session contract — anything with a `lastAccessedAt` timestamp. */
11
+ export interface SessionLike {
12
+ lastAccessedAt: number;
13
+ }
14
+ /** Configuration options for SessionManager. */
15
+ export interface SessionManagerConfig {
16
+ /** Default session key that must never be evicted. */
17
+ defaultSessionId: string;
18
+ /** TTL for inactive sessions in milliseconds. */
19
+ sessionTtlMs: number;
20
+ /** Periodic cleanup interval in milliseconds. */
21
+ cleanupIntervalMs: number;
22
+ /** Returns the current MAX_SESSIONS limit (callable so tests can mutate). */
23
+ getMaxSessions: () => number;
24
+ logger?: Logger;
25
+ }
26
+ /**
27
+ * Manages session lifecycle: periodic stale-session cleanup and LRU eviction
28
+ * when the session count exceeds the configured maximum.
29
+ */
30
+ export declare class SessionManager<S extends SessionLike> {
31
+ private readonly _defaultSessionId;
32
+ private readonly _sessionTtlMs;
33
+ private readonly _cleanupIntervalMs;
34
+ private readonly _getMaxSessions;
35
+ private readonly _logger;
36
+ private _cleanupTimer;
37
+ constructor(config: SessionManagerConfig);
38
+ /** Returns the underlying cleanup timer (for test introspection). */
39
+ get timer(): ReturnType<typeof setInterval> | null;
40
+ /**
41
+ * Starts the periodic session cleanup timer. No-op if already started.
42
+ * The timer is unref'd so it does not block process exit.
43
+ */
44
+ startCleanupTimer(sessions: Map<string, S>): void;
45
+ /** Stops the periodic session cleanup timer. */
46
+ stopCleanupTimer(): void;
47
+ /**
48
+ * Evicts sessions that have been inactive longer than `sessionTtlMs`.
49
+ * The default session is never evicted.
50
+ */
51
+ cleanupStaleSessions(sessions: Map<string, S>): void;
52
+ /**
53
+ * Evicts oldest sessions when the configured maximum is exceeded (LRU).
54
+ * The default session is never evicted.
55
+ */
56
+ evictExcessSessions(sessions: Map<string, S>): void;
57
+ }
58
+ //# sourceMappingURL=SessionManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionManager.d.ts","sourceRoot":"","sources":["../../src/core/SessionManager.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAG5D,6EAA6E;AAC7E,MAAM,WAAW,WAAW;IAC3B,cAAc,EAAE,MAAM,CAAC;CACvB;AAED,gDAAgD;AAChD,MAAM,WAAW,oBAAoB;IACpC,sDAAsD;IACtD,gBAAgB,EAAE,MAAM,CAAC;IACzB,iDAAiD;IACjD,YAAY,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,6EAA6E;IAC7E,cAAc,EAAE,MAAM,MAAM,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,qBAAa,cAAc,CAAC,CAAC,SAAS,WAAW;IAChD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAC5C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAe;IAC/C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,aAAa,CAA+C;gBAExD,MAAM,EAAE,oBAAoB;IAQxC,qEAAqE;IACrE,IAAW,KAAK,IAAI,UAAU,CAAC,OAAO,WAAW,CAAC,GAAG,IAAI,CAExD;IAED;;;OAGG;IACI,iBAAiB,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,IAAI;IAcxD,gDAAgD;IACzC,gBAAgB,IAAI,IAAI;IAO/B;;;OAGG;IACI,oBAAoB,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,IAAI;IAW3D;;;OAGG;IACI,mBAAmB,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,IAAI;CAoB1D"}
@@ -0,0 +1,65 @@
1
+ import { NullLogger } from "../logger/NullLogger.js";
2
+ class SessionManager {
3
+ _defaultSessionId;
4
+ _sessionTtlMs;
5
+ _cleanupIntervalMs;
6
+ _getMaxSessions;
7
+ _logger;
8
+ _cleanupTimer = null;
9
+ constructor(config){
10
+ this._defaultSessionId = config.defaultSessionId;
11
+ this._sessionTtlMs = config.sessionTtlMs;
12
+ this._cleanupIntervalMs = config.cleanupIntervalMs;
13
+ this._getMaxSessions = config.getMaxSessions;
14
+ this._logger = config.logger ?? new NullLogger();
15
+ }
16
+ get timer() {
17
+ return this._cleanupTimer;
18
+ }
19
+ startCleanupTimer(sessions) {
20
+ if (null !== this._cleanupTimer) return;
21
+ this._cleanupTimer = setInterval(()=>{
22
+ this.cleanupStaleSessions(sessions);
23
+ }, this._cleanupIntervalMs);
24
+ if (this._cleanupTimer && 'object' == typeof this._cleanupTimer && 'unref' in this._cleanupTimer) this._cleanupTimer.unref();
25
+ }
26
+ stopCleanupTimer() {
27
+ if (null !== this._cleanupTimer) {
28
+ clearInterval(this._cleanupTimer);
29
+ this._cleanupTimer = null;
30
+ }
31
+ }
32
+ cleanupStaleSessions(sessions) {
33
+ const now = Date.now();
34
+ for (const [key, session] of sessions)if (key !== this._defaultSessionId) {
35
+ if (now - session.lastAccessedAt > this._sessionTtlMs) {
36
+ sessions.delete(key);
37
+ this._logger.info('Evicted stale session', {
38
+ sessionId: key
39
+ });
40
+ }
41
+ }
42
+ }
43
+ evictExcessSessions(sessions) {
44
+ const max = this._getMaxSessions();
45
+ while(sessions.size > max){
46
+ let oldestKey = null;
47
+ let oldestTime = 1 / 0;
48
+ for (const [key, session] of sessions)if (key !== this._defaultSessionId) {
49
+ if (session.lastAccessedAt < oldestTime) {
50
+ oldestTime = session.lastAccessedAt;
51
+ oldestKey = key;
52
+ }
53
+ }
54
+ if (null !== oldestKey) {
55
+ sessions.delete(oldestKey);
56
+ this._logger.info('Evicted oldest session (LRU)', {
57
+ sessionId: oldestKey
58
+ });
59
+ } else break;
60
+ }
61
+ }
62
+ }
63
+ export { SessionManager };
64
+
65
+ //# sourceMappingURL=SessionManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core/SessionManager.js","sources":["../../src/core/SessionManager.ts"],"sourcesContent":["/**\n * SessionManager — owns periodic session cleanup timer and TTL/LRU eviction logic.\n *\n * Extracted from HistoryManager. The session Map remains owned by HistoryManager\n * (passed by reference) so that public access patterns are preserved.\n *\n * @module SessionManager\n */\n\nimport type { Logger } from '../logger/StructuredLogger.js';\nimport { NullLogger } from '../logger/NullLogger.js';\n\n/** Minimal session contract — anything with a `lastAccessedAt` timestamp. */\nexport interface SessionLike {\n\tlastAccessedAt: number;\n}\n\n/** Configuration options for SessionManager. */\nexport interface SessionManagerConfig {\n\t/** Default session key that must never be evicted. */\n\tdefaultSessionId: string;\n\t/** TTL for inactive sessions in milliseconds. */\n\tsessionTtlMs: number;\n\t/** Periodic cleanup interval in milliseconds. */\n\tcleanupIntervalMs: number;\n\t/** Returns the current MAX_SESSIONS limit (callable so tests can mutate). */\n\tgetMaxSessions: () => number;\n\tlogger?: Logger;\n}\n\n/**\n * Manages session lifecycle: periodic stale-session cleanup and LRU eviction\n * when the session count exceeds the configured maximum.\n */\nexport class SessionManager<S extends SessionLike> {\n\tprivate readonly _defaultSessionId: string;\n\tprivate readonly _sessionTtlMs: number;\n\tprivate readonly _cleanupIntervalMs: number;\n\tprivate readonly _getMaxSessions: () => number;\n\tprivate readonly _logger: Logger;\n\tprivate _cleanupTimer: ReturnType<typeof setInterval> | null = null;\n\n\tconstructor(config: SessionManagerConfig) {\n\t\tthis._defaultSessionId = config.defaultSessionId;\n\t\tthis._sessionTtlMs = config.sessionTtlMs;\n\t\tthis._cleanupIntervalMs = config.cleanupIntervalMs;\n\t\tthis._getMaxSessions = config.getMaxSessions;\n\t\tthis._logger = config.logger ?? new NullLogger();\n\t}\n\n\t/** Returns the underlying cleanup timer (for test introspection). */\n\tpublic get timer(): ReturnType<typeof setInterval> | null {\n\t\treturn this._cleanupTimer;\n\t}\n\n\t/**\n\t * Starts the periodic session cleanup timer. No-op if already started.\n\t * The timer is unref'd so it does not block process exit.\n\t */\n\tpublic startCleanupTimer(sessions: Map<string, S>): void {\n\t\tif (this._cleanupTimer !== null) return;\n\t\tthis._cleanupTimer = setInterval(() => {\n\t\t\tthis.cleanupStaleSessions(sessions);\n\t\t}, this._cleanupIntervalMs);\n\t\tif (\n\t\t\tthis._cleanupTimer &&\n\t\t\ttypeof this._cleanupTimer === 'object' &&\n\t\t\t'unref' in this._cleanupTimer\n\t\t) {\n\t\t\tthis._cleanupTimer.unref();\n\t\t}\n\t}\n\n\t/** Stops the periodic session cleanup timer. */\n\tpublic stopCleanupTimer(): void {\n\t\tif (this._cleanupTimer !== null) {\n\t\t\tclearInterval(this._cleanupTimer);\n\t\t\tthis._cleanupTimer = null;\n\t\t}\n\t}\n\n\t/**\n\t * Evicts sessions that have been inactive longer than `sessionTtlMs`.\n\t * The default session is never evicted.\n\t */\n\tpublic cleanupStaleSessions(sessions: Map<string, S>): void {\n\t\tconst now = Date.now();\n\t\tfor (const [key, session] of sessions) {\n\t\t\tif (key === this._defaultSessionId) continue;\n\t\t\tif (now - session.lastAccessedAt > this._sessionTtlMs) {\n\t\t\t\tsessions.delete(key);\n\t\t\t\tthis._logger.info('Evicted stale session', { sessionId: key });\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Evicts oldest sessions when the configured maximum is exceeded (LRU).\n\t * The default session is never evicted.\n\t */\n\tpublic evictExcessSessions(sessions: Map<string, S>): void {\n\t\tconst max = this._getMaxSessions();\n\t\twhile (sessions.size > max) {\n\t\t\tlet oldestKey: string | null = null;\n\t\t\tlet oldestTime = Infinity;\n\t\t\tfor (const [key, session] of sessions) {\n\t\t\t\tif (key === this._defaultSessionId) continue;\n\t\t\t\tif (session.lastAccessedAt < oldestTime) {\n\t\t\t\t\toldestTime = session.lastAccessedAt;\n\t\t\t\t\toldestKey = key;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (oldestKey !== null) {\n\t\t\t\tsessions.delete(oldestKey);\n\t\t\t\tthis._logger.info('Evicted oldest session (LRU)', { sessionId: oldestKey });\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n}\n"],"names":["SessionManager","config","NullLogger","sessions","setInterval","clearInterval","now","Date","key","session","max","oldestKey","oldestTime","Infinity"],"mappings":";AAkCO,MAAMA;IACK,kBAA0B;IAC1B,cAAsB;IACtB,mBAA2B;IAC3B,gBAA8B;IAC9B,QAAgB;IACzB,gBAAuD,KAAK;IAEpE,YAAYC,MAA4B,CAAE;QACzC,IAAI,CAAC,iBAAiB,GAAGA,OAAO,gBAAgB;QAChD,IAAI,CAAC,aAAa,GAAGA,OAAO,YAAY;QACxC,IAAI,CAAC,kBAAkB,GAAGA,OAAO,iBAAiB;QAClD,IAAI,CAAC,eAAe,GAAGA,OAAO,cAAc;QAC5C,IAAI,CAAC,OAAO,GAAGA,OAAO,MAAM,IAAI,IAAIC;IACrC;IAGA,IAAW,QAA+C;QACzD,OAAO,IAAI,CAAC,aAAa;IAC1B;IAMO,kBAAkBC,QAAwB,EAAQ;QACxD,IAAI,AAAuB,SAAvB,IAAI,CAAC,aAAa,EAAW;QACjC,IAAI,CAAC,aAAa,GAAGC,YAAY;YAChC,IAAI,CAAC,oBAAoB,CAACD;QAC3B,GAAG,IAAI,CAAC,kBAAkB;QAC1B,IACC,IAAI,CAAC,aAAa,IAClB,AAA8B,YAA9B,OAAO,IAAI,CAAC,aAAa,IACzB,WAAW,IAAI,CAAC,aAAa,EAE7B,IAAI,CAAC,aAAa,CAAC,KAAK;IAE1B;IAGO,mBAAyB;QAC/B,IAAI,AAAuB,SAAvB,IAAI,CAAC,aAAa,EAAW;YAChCE,cAAc,IAAI,CAAC,aAAa;YAChC,IAAI,CAAC,aAAa,GAAG;QACtB;IACD;IAMO,qBAAqBF,QAAwB,EAAQ;QAC3D,MAAMG,MAAMC,KAAK,GAAG;QACpB,KAAK,MAAM,CAACC,KAAKC,QAAQ,IAAIN,SAC5B,IAAIK,QAAQ,IAAI,CAAC,iBAAiB,EAClC;YAAA,IAAIF,MAAMG,QAAQ,cAAc,GAAG,IAAI,CAAC,aAAa,EAAE;gBACtDN,SAAS,MAAM,CAACK;gBAChB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,yBAAyB;oBAAE,WAAWA;gBAAI;YAC7D;QAAA;IAEF;IAMO,oBAAoBL,QAAwB,EAAQ;QAC1D,MAAMO,MAAM,IAAI,CAAC,eAAe;QAChC,MAAOP,SAAS,IAAI,GAAGO,IAAK;YAC3B,IAAIC,YAA2B;YAC/B,IAAIC,aAAaC;YACjB,KAAK,MAAM,CAACL,KAAKC,QAAQ,IAAIN,SAC5B,IAAIK,QAAQ,IAAI,CAAC,iBAAiB,EAClC;gBAAA,IAAIC,QAAQ,cAAc,GAAGG,YAAY;oBACxCA,aAAaH,QAAQ,cAAc;oBACnCE,YAAYH;gBACb;YAAA;YAED,IAAIG,AAAc,SAAdA,WAAoB;gBACvBR,SAAS,MAAM,CAACQ;gBAChB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,gCAAgC;oBAAE,WAAWA;gBAAU;YAC1E,OACC;QAEF;IACD;AACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"ThoughtEvaluator.d.ts","sourceRoot":"","sources":["../../src/core/ThoughtEvaluator.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAGX,WAAW,EACX,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,cAAc,EAAe,MAAM,gBAAgB,CAAC;AACpG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AA8ChD;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAiB;IACjD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAa;IACzC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAkB;IACnD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;gBAE9B,UAAU,CAAC,EAAE,WAAW;IAOpC,yEAAyE;IAClE,wBAAwB,CAC9B,OAAO,EAAE,WAAW,EAAE,EACtB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,GACrC,iBAAiB;IAmBpB,gEAAgE;IACzD,qBAAqB,CAC3B,OAAO,EAAE,WAAW,EAAE,EACtB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,GACrC,cAAc;IAIjB,wFAAwF;IACjF,qBAAqB,CAC3B,OAAO,EAAE,WAAW,EAAE,EACtB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,GACrC,aAAa,EAAE;CAGlB"}
1
+ {"version":3,"file":"ThoughtEvaluator.d.ts","sourceRoot":"","sources":["../../src/core/ThoughtEvaluator.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAGX,WAAW,EACX,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,cAAc,EAAe,MAAM,gBAAgB,CAAC;AACpG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AA8ChD;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAiB;IACjD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAa;IACzC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAkB;IACnD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;gBAE9B,UAAU,CAAC,EAAE,WAAW;IAOpC,yEAAyE;IAClE,wBAAwB,CAC9B,OAAO,EAAE,WAAW,EAAE,EACtB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,GACrC,iBAAiB;IAoBpB,gEAAgE;IACzD,qBAAqB,CAC3B,OAAO,EAAE,WAAW,EAAE,EACtB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,GACrC,cAAc;IAKjB,wFAAwF;IACjF,qBAAqB,CAC3B,OAAO,EAAE,WAAW,EAAE,EACtB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,GACrC,aAAa,EAAE;CAIlB"}
@@ -46,9 +46,10 @@ class ThoughtEvaluator {
46
46
  this._calibrator = calibrator ?? new NoOpCalibrator();
47
47
  }
48
48
  computeConfidenceSignals(history, branches) {
49
- const signals = this._signalComputer.computeConfidenceSignals(history, branches);
49
+ const { history: h, branches: b } = filterRetracted(history, branches);
50
+ const signals = this._signalComputer.computeConfidenceSignals(h, b);
50
51
  if (!this._calibrator.enabled) return signals;
51
- const lastThought = history[history.length - 1];
52
+ const lastThought = h[h.length - 1];
52
53
  if (lastThought?.confidence === void 0) return signals;
53
54
  const result = this._calibrator.calibrate(lastThought.confidence, lastThought.thought_type ?? 'regular', lastThought.session_id ?? '');
54
55
  return {
@@ -58,12 +59,23 @@ class ThoughtEvaluator {
58
59
  };
59
60
  }
60
61
  computeReasoningStats(history, branches) {
61
- return this._aggregator.computeReasoningStats(history, branches);
62
+ const { history: h, branches: b } = filterRetracted(history, branches);
63
+ return this._aggregator.computeReasoningStats(h, b);
62
64
  }
63
65
  computePatternSignals(history, branches) {
64
- return this._patternDetector.computePatternSignals(history, branches);
66
+ const { history: h, branches: b } = filterRetracted(history, branches);
67
+ return this._patternDetector.computePatternSignals(h, b);
65
68
  }
66
69
  }
70
+ function filterRetracted(history, branches) {
71
+ const h = history.filter((t)=>!t.retracted);
72
+ const b = {};
73
+ for (const [id, list] of Object.entries(branches))b[id] = list.filter((t)=>!t.retracted);
74
+ return {
75
+ history: h,
76
+ branches: b
77
+ };
78
+ }
67
79
  export { ThoughtEvaluator };
68
80
 
69
81
  //# sourceMappingURL=ThoughtEvaluator.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"core/ThoughtEvaluator.js","sources":["../../src/core/ThoughtEvaluator.ts"],"sourcesContent":["/**\n * Quality signal computation for sequential thinking.\n *\n * Thin facade that composes the extracted evaluator submodules\n * ({@link SignalComputer}, {@link Aggregator}, {@link PatternDetector})\n * to preserve the original {@link ThoughtEvaluator} public API.\n *\n * @module core/ThoughtEvaluator\n */\n\nimport type {\n\tCalibrationMetrics,\n\tCalibrationResult,\n\tICalibrator,\n} from '../contracts/calibrator.js';\nimport type { ConfidenceSignals, PatternSignal, ReasoningStats, ThoughtType } from './reasoning.js';\nimport type { ThoughtData } from './thought.js';\nimport { Aggregator } from './evaluator/Aggregator.js';\nimport { PatternDetector } from './evaluator/PatternDetector.js';\nimport { SignalComputer } from './evaluator/SignalComputer.js';\n\n/**\n * No-op calibrator used when calibration is disabled or no calibrator is injected.\n *\n * @remarks\n * Returns the raw confidence unchanged, exposes `enabled = false`, and reports\n * empty metrics. Keeps the {@link ThoughtEvaluator} constructor zero-arg compatible.\n */\nclass NoOpCalibrator implements ICalibrator {\n\tpublic readonly enabled = false;\n\n\tpublic calibrate(rawConfidence: number, _type: ThoughtType, _sessionId: string): CalibrationResult {\n\t\tconst raw = Math.min(1, Math.max(0, rawConfidence));\n\t\treturn { raw, calibrated: raw, temperature: 1.0, priorWeight: 0 };\n\t}\n\n\tpublic metrics(_sessionId?: string): CalibrationMetrics {\n\t\treturn {\n\t\t\tbrierScore: null,\n\t\t\tece: null,\n\t\t\tsampleCount: 0,\n\t\t\tperTypeBrier: {\n\t\t\t\tregular: null,\n\t\t\t\thypothesis: null,\n\t\t\t\tverification: null,\n\t\t\t\tcritique: null,\n\t\t\t\tsynthesis: null,\n\t\t\t\tmeta: null,\n\t\t\t\ttool_call: null,\n\t\t\t\ttool_observation: null,\n\t\t\t\tassumption: null,\n\t\t\t\tdecomposition: null,\n\t\t\t\tbacktrack: null,\n\t\t\t},\n\t\t};\n\t}\n\n\tpublic refit(_sessionId?: string): void {\n\t\t// no-op\n\t}\n}\n\n/**\n * Stateless service that computes quality signals and reasoning analytics\n * from thought history and branch data.\n *\n * @remarks\n * All methods are pure computations — no side effects, no I/O, no internal state.\n * Designed to be registered as transient in the DI container.\n *\n * @example\n * ```typescript\n * const evaluator = new ThoughtEvaluator();\n * const signals = evaluator.computeConfidenceSignals(history, branches);\n * const stats = evaluator.computeReasoningStats(history, branches);\n * const patterns = evaluator.computePatternSignals(history, branches);\n * ```\n */\nexport class ThoughtEvaluator {\n\tprivate readonly _signalComputer: SignalComputer;\n\tprivate readonly _aggregator: Aggregator;\n\tprivate readonly _patternDetector: PatternDetector;\n\tprivate readonly _calibrator: ICalibrator;\n\n\tconstructor(calibrator?: ICalibrator) {\n\t\tthis._signalComputer = new SignalComputer();\n\t\tthis._aggregator = new Aggregator();\n\t\tthis._patternDetector = new PatternDetector();\n\t\tthis._calibrator = calibrator ?? new NoOpCalibrator();\n\t}\n\n\t/** Compute confidence signals from history context. Pure computation. */\n\tpublic computeConfidenceSignals(\n\t\thistory: ThoughtData[],\n\t\tbranches: Record<string, ThoughtData[]>\n\t): ConfidenceSignals {\n\t\tconst signals = this._signalComputer.computeConfidenceSignals(history, branches);\n\t\tif (!this._calibrator.enabled) return signals;\n\n\t\tconst lastThought = history[history.length - 1];\n\t\tif (lastThought?.confidence === undefined) return signals;\n\n\t\tconst result = this._calibrator.calibrate(\n\t\t\tlastThought.confidence,\n\t\t\tlastThought.thought_type ?? 'regular',\n\t\t\tlastThought.session_id ?? ''\n\t\t);\n\t\treturn {\n\t\t\t...signals,\n\t\t\tcalibrated_confidence: result.calibrated,\n\t\t\tcalibration_metrics: this._calibrator.metrics(lastThought.session_id),\n\t\t};\n\t}\n\n\t/** Compute aggregated reasoning analytics. Pure computation. */\n\tpublic computeReasoningStats(\n\t\thistory: ThoughtData[],\n\t\tbranches: Record<string, ThoughtData[]>\n\t): ReasoningStats {\n\t\treturn this._aggregator.computeReasoningStats(history, branches);\n\t}\n\n\t/** Detect reasoning patterns (anti-patterns and positive signals). Pure computation. */\n\tpublic computePatternSignals(\n\t\thistory: ThoughtData[],\n\t\tbranches: Record<string, ThoughtData[]>\n\t): PatternSignal[] {\n\t\treturn this._patternDetector.computePatternSignals(history, branches);\n\t}\n}\n"],"names":["NoOpCalibrator","rawConfidence","_type","_sessionId","raw","Math","ThoughtEvaluator","calibrator","SignalComputer","Aggregator","PatternDetector","history","branches","signals","lastThought","undefined","result"],"mappings":";;;AA4BA,MAAMA;IACW,UAAU,MAAM;IAEzB,UAAUC,aAAqB,EAAEC,KAAkB,EAAEC,UAAkB,EAAqB;QAClG,MAAMC,MAAMC,KAAK,GAAG,CAAC,GAAGA,KAAK,GAAG,CAAC,GAAGJ;QACpC,OAAO;YAAEG;YAAK,YAAYA;YAAK,aAAa;YAAK,aAAa;QAAE;IACjE;IAEO,QAAQD,UAAmB,EAAsB;QACvD,OAAO;YACN,YAAY;YACZ,KAAK;YACL,aAAa;YACb,cAAc;gBACb,SAAS;gBACT,YAAY;gBACZ,cAAc;gBACd,UAAU;gBACV,WAAW;gBACX,MAAM;gBACN,WAAW;gBACX,kBAAkB;gBAClB,YAAY;gBACZ,eAAe;gBACf,WAAW;YACZ;QACD;IACD;IAEO,MAAMA,UAAmB,EAAQ,CAExC;AACD;AAkBO,MAAMG;IACK,gBAAgC;IAChC,YAAwB;IACxB,iBAAkC;IAClC,YAAyB;IAE1C,YAAYC,UAAwB,CAAE;QACrC,IAAI,CAAC,eAAe,GAAG,IAAIC;QAC3B,IAAI,CAAC,WAAW,GAAG,IAAIC;QACvB,IAAI,CAAC,gBAAgB,GAAG,IAAIC;QAC5B,IAAI,CAAC,WAAW,GAAGH,cAAc,IAAIP;IACtC;IAGO,yBACNW,OAAsB,EACtBC,QAAuC,EACnB;QACpB,MAAMC,UAAU,IAAI,CAAC,eAAe,CAAC,wBAAwB,CAACF,SAASC;QACvE,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,OAAOC;QAEtC,MAAMC,cAAcH,OAAO,CAACA,QAAQ,MAAM,GAAG,EAAE;QAC/C,IAAIG,aAAa,eAAeC,QAAW,OAAOF;QAElD,MAAMG,SAAS,IAAI,CAAC,WAAW,CAAC,SAAS,CACxCF,YAAY,UAAU,EACtBA,YAAY,YAAY,IAAI,WAC5BA,YAAY,UAAU,IAAI;QAE3B,OAAO;YACN,GAAGD,OAAO;YACV,uBAAuBG,OAAO,UAAU;YACxC,qBAAqB,IAAI,CAAC,WAAW,CAAC,OAAO,CAACF,YAAY,UAAU;QACrE;IACD;IAGO,sBACNH,OAAsB,EACtBC,QAAuC,EACtB;QACjB,OAAO,IAAI,CAAC,WAAW,CAAC,qBAAqB,CAACD,SAASC;IACxD;IAGO,sBACND,OAAsB,EACtBC,QAAuC,EACrB;QAClB,OAAO,IAAI,CAAC,gBAAgB,CAAC,qBAAqB,CAACD,SAASC;IAC7D;AACD"}
1
+ {"version":3,"file":"core/ThoughtEvaluator.js","sources":["../../src/core/ThoughtEvaluator.ts"],"sourcesContent":["/**\n * Quality signal computation for sequential thinking.\n *\n * Thin facade that composes the extracted evaluator submodules\n * ({@link SignalComputer}, {@link Aggregator}, {@link PatternDetector})\n * to preserve the original {@link ThoughtEvaluator} public API.\n *\n * @module core/ThoughtEvaluator\n */\n\nimport type {\n\tCalibrationMetrics,\n\tCalibrationResult,\n\tICalibrator,\n} from '../contracts/calibrator.js';\nimport type { ConfidenceSignals, PatternSignal, ReasoningStats, ThoughtType } from './reasoning.js';\nimport type { ThoughtData } from './thought.js';\nimport { Aggregator } from './evaluator/Aggregator.js';\nimport { PatternDetector } from './evaluator/PatternDetector.js';\nimport { SignalComputer } from './evaluator/SignalComputer.js';\n\n/**\n * No-op calibrator used when calibration is disabled or no calibrator is injected.\n *\n * @remarks\n * Returns the raw confidence unchanged, exposes `enabled = false`, and reports\n * empty metrics. Keeps the {@link ThoughtEvaluator} constructor zero-arg compatible.\n */\nclass NoOpCalibrator implements ICalibrator {\n\tpublic readonly enabled = false;\n\n\tpublic calibrate(rawConfidence: number, _type: ThoughtType, _sessionId: string): CalibrationResult {\n\t\tconst raw = Math.min(1, Math.max(0, rawConfidence));\n\t\treturn { raw, calibrated: raw, temperature: 1.0, priorWeight: 0 };\n\t}\n\n\tpublic metrics(_sessionId?: string): CalibrationMetrics {\n\t\treturn {\n\t\t\tbrierScore: null,\n\t\t\tece: null,\n\t\t\tsampleCount: 0,\n\t\t\tperTypeBrier: {\n\t\t\t\tregular: null,\n\t\t\t\thypothesis: null,\n\t\t\t\tverification: null,\n\t\t\t\tcritique: null,\n\t\t\t\tsynthesis: null,\n\t\t\t\tmeta: null,\n\t\t\t\ttool_call: null,\n\t\t\t\ttool_observation: null,\n\t\t\t\tassumption: null,\n\t\t\t\tdecomposition: null,\n\t\t\t\tbacktrack: null,\n\t\t\t},\n\t\t};\n\t}\n\n\tpublic refit(_sessionId?: string): void {\n\t\t// no-op\n\t}\n}\n\n/**\n * Stateless service that computes quality signals and reasoning analytics\n * from thought history and branch data.\n *\n * @remarks\n * All methods are pure computations — no side effects, no I/O, no internal state.\n * Designed to be registered as transient in the DI container.\n *\n * @example\n * ```typescript\n * const evaluator = new ThoughtEvaluator();\n * const signals = evaluator.computeConfidenceSignals(history, branches);\n * const stats = evaluator.computeReasoningStats(history, branches);\n * const patterns = evaluator.computePatternSignals(history, branches);\n * ```\n */\nexport class ThoughtEvaluator {\n\tprivate readonly _signalComputer: SignalComputer;\n\tprivate readonly _aggregator: Aggregator;\n\tprivate readonly _patternDetector: PatternDetector;\n\tprivate readonly _calibrator: ICalibrator;\n\n\tconstructor(calibrator?: ICalibrator) {\n\t\tthis._signalComputer = new SignalComputer();\n\t\tthis._aggregator = new Aggregator();\n\t\tthis._patternDetector = new PatternDetector();\n\t\tthis._calibrator = calibrator ?? new NoOpCalibrator();\n\t}\n\n\t/** Compute confidence signals from history context. Pure computation. */\n\tpublic computeConfidenceSignals(\n\t\thistory: ThoughtData[],\n\t\tbranches: Record<string, ThoughtData[]>\n\t): ConfidenceSignals {\n\t\tconst { history: h, branches: b } = filterRetracted(history, branches);\n\t\tconst signals = this._signalComputer.computeConfidenceSignals(h, b);\n\t\tif (!this._calibrator.enabled) return signals;\n\n\t\tconst lastThought = h[h.length - 1];\n\t\tif (lastThought?.confidence === undefined) return signals;\n\n\t\tconst result = this._calibrator.calibrate(\n\t\t\tlastThought.confidence,\n\t\t\tlastThought.thought_type ?? 'regular',\n\t\t\tlastThought.session_id ?? ''\n\t\t);\n\t\treturn {\n\t\t\t...signals,\n\t\t\tcalibrated_confidence: result.calibrated,\n\t\t\tcalibration_metrics: this._calibrator.metrics(lastThought.session_id),\n\t\t};\n\t}\n\n\t/** Compute aggregated reasoning analytics. Pure computation. */\n\tpublic computeReasoningStats(\n\t\thistory: ThoughtData[],\n\t\tbranches: Record<string, ThoughtData[]>\n\t): ReasoningStats {\n\t\tconst { history: h, branches: b } = filterRetracted(history, branches);\n\t\treturn this._aggregator.computeReasoningStats(h, b);\n\t}\n\n\t/** Detect reasoning patterns (anti-patterns and positive signals). Pure computation. */\n\tpublic computePatternSignals(\n\t\thistory: ThoughtData[],\n\t\tbranches: Record<string, ThoughtData[]>\n\t): PatternSignal[] {\n\t\tconst { history: h, branches: b } = filterRetracted(history, branches);\n\t\treturn this._patternDetector.computePatternSignals(h, b);\n\t}\n}\n\n/**\n * Filters out logically retracted thoughts from history and branches.\n * Retracted thoughts remain in storage (event-sourcing) but are excluded\n * from quality signal calculations.\n */\nfunction filterRetracted(\n\thistory: ThoughtData[],\n\tbranches: Record<string, ThoughtData[]>\n): { history: ThoughtData[]; branches: Record<string, ThoughtData[]> } {\n\tconst h = history.filter((t) => !t.retracted);\n\tconst b: Record<string, ThoughtData[]> = {};\n\tfor (const [id, list] of Object.entries(branches)) {\n\t\tb[id] = list.filter((t) => !t.retracted);\n\t}\n\treturn { history: h, branches: b };\n}\n"],"names":["NoOpCalibrator","rawConfidence","_type","_sessionId","raw","Math","ThoughtEvaluator","calibrator","SignalComputer","Aggregator","PatternDetector","history","branches","h","b","filterRetracted","signals","lastThought","undefined","result","t","id","list","Object"],"mappings":";;;AA4BA,MAAMA;IACW,UAAU,MAAM;IAEzB,UAAUC,aAAqB,EAAEC,KAAkB,EAAEC,UAAkB,EAAqB;QAClG,MAAMC,MAAMC,KAAK,GAAG,CAAC,GAAGA,KAAK,GAAG,CAAC,GAAGJ;QACpC,OAAO;YAAEG;YAAK,YAAYA;YAAK,aAAa;YAAK,aAAa;QAAE;IACjE;IAEO,QAAQD,UAAmB,EAAsB;QACvD,OAAO;YACN,YAAY;YACZ,KAAK;YACL,aAAa;YACb,cAAc;gBACb,SAAS;gBACT,YAAY;gBACZ,cAAc;gBACd,UAAU;gBACV,WAAW;gBACX,MAAM;gBACN,WAAW;gBACX,kBAAkB;gBAClB,YAAY;gBACZ,eAAe;gBACf,WAAW;YACZ;QACD;IACD;IAEO,MAAMA,UAAmB,EAAQ,CAExC;AACD;AAkBO,MAAMG;IACK,gBAAgC;IAChC,YAAwB;IACxB,iBAAkC;IAClC,YAAyB;IAE1C,YAAYC,UAAwB,CAAE;QACrC,IAAI,CAAC,eAAe,GAAG,IAAIC;QAC3B,IAAI,CAAC,WAAW,GAAG,IAAIC;QACvB,IAAI,CAAC,gBAAgB,GAAG,IAAIC;QAC5B,IAAI,CAAC,WAAW,GAAGH,cAAc,IAAIP;IACtC;IAGO,yBACNW,OAAsB,EACtBC,QAAuC,EACnB;QACpB,MAAM,EAAE,SAASC,CAAC,EAAE,UAAUC,CAAC,EAAE,GAAGC,gBAAgBJ,SAASC;QAC7D,MAAMI,UAAU,IAAI,CAAC,eAAe,CAAC,wBAAwB,CAACH,GAAGC;QACjE,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,OAAOE;QAEtC,MAAMC,cAAcJ,CAAC,CAACA,EAAE,MAAM,GAAG,EAAE;QACnC,IAAII,aAAa,eAAeC,QAAW,OAAOF;QAElD,MAAMG,SAAS,IAAI,CAAC,WAAW,CAAC,SAAS,CACxCF,YAAY,UAAU,EACtBA,YAAY,YAAY,IAAI,WAC5BA,YAAY,UAAU,IAAI;QAE3B,OAAO;YACN,GAAGD,OAAO;YACV,uBAAuBG,OAAO,UAAU;YACxC,qBAAqB,IAAI,CAAC,WAAW,CAAC,OAAO,CAACF,YAAY,UAAU;QACrE;IACD;IAGO,sBACNN,OAAsB,EACtBC,QAAuC,EACtB;QACjB,MAAM,EAAE,SAASC,CAAC,EAAE,UAAUC,CAAC,EAAE,GAAGC,gBAAgBJ,SAASC;QAC7D,OAAO,IAAI,CAAC,WAAW,CAAC,qBAAqB,CAACC,GAAGC;IAClD;IAGO,sBACNH,OAAsB,EACtBC,QAAuC,EACrB;QAClB,MAAM,EAAE,SAASC,CAAC,EAAE,UAAUC,CAAC,EAAE,GAAGC,gBAAgBJ,SAASC;QAC7D,OAAO,IAAI,CAAC,gBAAgB,CAAC,qBAAqB,CAACC,GAAGC;IACvD;AACD;AAOA,SAASC,gBACRJ,OAAsB,EACtBC,QAAuC;IAEvC,MAAMC,IAAIF,QAAQ,MAAM,CAAC,CAACS,IAAM,CAACA,EAAE,SAAS;IAC5C,MAAMN,IAAmC,CAAC;IAC1C,KAAK,MAAM,CAACO,IAAIC,KAAK,IAAIC,OAAO,OAAO,CAACX,UACvCE,CAAC,CAACO,GAAG,GAAGC,KAAK,MAAM,CAAC,CAACF,IAAM,CAACA,EAAE,SAAS;IAExC,OAAO;QAAE,SAASP;QAAG,UAAUC;IAAE;AAClC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ThoughtFormatter.d.ts","sourceRoot":"","sources":["../../src/core/ThoughtFormatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,qBAAa,gBAAgB;IAC5B;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACI,oBAAoB,CAAC,IAAI,EAAE,kBAAkB,GAAG,MAAM;IAuB7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6CG;IACI,aAAa,CAAC,WAAW,EAAE,WAAW,GAAG,MAAM;CAmGtD"}
1
+ {"version":3,"file":"ThoughtFormatter.d.ts","sourceRoot":"","sources":["../../src/core/ThoughtFormatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,qBAAa,gBAAgB;IAC5B;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACI,oBAAoB,CAAC,IAAI,EAAE,kBAAkB,GAAG,MAAM;IAuB7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6CG;IACI,aAAa,CAAC,WAAW,EAAE,WAAW,GAAG,MAAM;CAoGtD"}
@@ -74,7 +74,8 @@ class ThoughtFormatter {
74
74
  break;
75
75
  }
76
76
  }
77
- const header = `${icon} ${label} ${thought_number}/${total_thoughts}${suffix}: `;
77
+ const retractedTag = thoughtData.retracted ? chalk.red.strikethrough('[RETRACTED] ') : '';
78
+ const header = `${icon} ${label} ${thought_number}/${total_thoughts}${suffix}: ${retractedTag}`;
78
79
  const lines = [];
79
80
  lines.push(`${header}${thought}`);
80
81
  if (current_step) {
@@ -1 +1 @@
1
- {"version":3,"file":"core/ThoughtFormatter.js","sources":["../../src/core/ThoughtFormatter.ts"],"sourcesContent":["/**\n * Display formatting for thoughts and recommendations.\n *\n * This module provides the `ThoughtFormatter` class which handles all\n * presentation logic for thought data, including clean, simple output\n * formatting and structured display of tool/skill recommendations.\n *\n * @module formatter\n */\n\nimport chalk from 'chalk';\nimport type { ThoughtType } from './reasoning.js';\nimport type { StepRecommendation } from './step.js';\nimport type { ThoughtData } from './thought.js';\n\n/**\n * Formatter for thought data and step recommendations.\n *\n * This class separates presentation concerns from business logic, providing\n * clean, readable output for thoughts with structured display of\n * tool and skill recommendations.\n *\n * @remarks\n * Output Format is clean and simple:\n * - 💭 Thought - Regular thought (blue)\n * - 🔬 Hypothesis - Proposed explanation (magenta)\n * - ✅ Verification - Testing a hypothesis (green)\n * - 🔍 Critique - Self-critique of reasoning (red)\n * - 🧬 Synthesis - Combining thoughts/branches (cyan)\n * - 🧠 Meta - Metacognitive observation (gray)\n * - 🔄 Revision - Thought that revises a previous thought (yellow)\n * - 🌿 Branch - Thought that creates a new branch (green)\n * @example\n * ```typescript\n * const formatter = new ThoughtFormatter();\n *\n * // Format a thought with recommendations\n * const output = formatter.formatThought({\n * thought: 'I need to analyze the data structure',\n * thought_number: 1,\n * total_thoughts: 3,\n * next_thought_needed: true,\n * current_step: {\n * step_description: 'Analyze data structure',\n * recommended_tools: [{\n * tool_name: 'Read',\n * confidence: 0.95,\n * rationale: 'Direct file reading',\n * priority: 1,\n * suggested_inputs: { file_path: './data/schema.json' }\n * }],\n * expected_outcome: 'Understanding of data schema'\n * }\n * });\n *\n * console.log(output);\n * ```\n */\nexport class ThoughtFormatter {\n\t/**\n\t * Formats a step recommendation into a readable string.\n\t *\n\t * Creates a structured display of the step description, recommended tools,\n\t * recommended skills, expected outcome, and conditions for the next step.\n\t *\n\t * @param step - The step recommendation to format\n\t * @returns A formatted string representation of the recommendation\n\t *\n\t * @example\n\t * ```typescript\n\t * const step: StepRecommendation = {\n\t * step_description: 'Search for API endpoints',\n\t * recommended_tools: [{\n\t * tool_name: 'Grep',\n\t * confidence: 0.9,\n\t * rationale: 'Best for searching code patterns',\n\t * priority: 1,\n\t * suggested_inputs: { pattern: 'export.*function' }\n\t * }],\n\t * expected_outcome: 'List of all exported API functions',\n\t * next_step_conditions: ['If no results, try broader pattern']\n\t * };\n\t *\n\t * const formatted = formatter.formatRecommendation(step);\n\t * console.log(formatted);\n\t * ```\n\t */\n\tpublic formatRecommendation(step: StepRecommendation): string {\n\t\tconst parts: string[] = [];\n\n\t\t// Add tools if present\n\t\tif (step.recommended_tools?.length) {\n\t\t\tconst toolNames = step.recommended_tools.map((t) => t.tool_name).join(', ');\n\t\t\tparts.push(chalk.cyan(`Tools: ${toolNames}`));\n\t\t}\n\n\t\t// Add skills if present\n\t\tif (step.recommended_skills?.length) {\n\t\t\tconst skillNames = step.recommended_skills.map((s) => s.skill_name).join(', ');\n\t\t\tparts.push(chalk.green(`Skills: ${skillNames}`));\n\t\t}\n\n\t\t// Add expected outcome\n\t\tif (step.expected_outcome) {\n\t\t\tparts.push(chalk.gray(`→ ${step.expected_outcome}`));\n\t\t}\n\n\t\treturn parts.join(' | ');\n\t}\n\n\t/**\n\t * Formats a thought into a clean, simple display.\n\t *\n\t * Creates a clean output containing the thought content with an appropriate\n\t * header indicating the thought type. Priority order for icon selection:\n\t * `is_revision` > `branch_from_thought` > `thought_type`.\n\t *\n\t * Supported `thought_type` icons:\n\t * - `'regular'` (or undefined): 💭 blue \"Thought\" (default)\n\t * - `'hypothesis'`: 🔬 magenta \"Hypothesis\"\n\t * - `'verification'`: ✅ green \"Verification\"\n\t * - `'critique'`: 🔍 red \"Critique\"\n\t * - `'synthesis'`: 🧬 cyan \"Synthesis\"\n\t * - `'meta'`: 🧠 gray \"Meta\"\n\t *\n\t * @param thoughtData - The thought data to format\n\t * @returns A formatted string with thought and recommendations\n\t *\n\t * @example\n\t * ```typescript\n\t * // Regular thought\n\t * const regular = formatter.formatThought({\n\t * thought: 'I should read the configuration file',\n\t * thought_number: 1,\n\t * total_thoughts: 3,\n\t * next_thought_needed: true\n\t * });\n\t * // Output: 💭 Thought 1/3: I should read the configuration file\n\t *\n\t * // With recommendation\n\t * const withRec = formatter.formatThought({\n\t * thought: 'I need to search the codebase',\n\t * thought_number: 1,\n\t * total_thoughts: 3,\n\t * next_thought_needed: true,\n\t * current_step: {\n\t * step_description: 'Search for files',\n\t * recommended_tools: [{ tool_name: 'Grep', priority: 1 }],\n\t * expected_outcome: 'List of matching files'\n\t * }\n\t * });\n\t * // Output:\n\t * // 💭 Thought 1/3: I need to search the codebase\n\t * // → Tools: Grep | List of matching files\n\t * ```\n\t */\n\tpublic formatThought(thoughtData: ThoughtData): string {\n\t\tconst {\n\t\t\tthought_number,\n\t\t\ttotal_thoughts,\n\t\t\tthought,\n\t\t\tis_revision,\n\t\t\trevises_thought,\n\t\t\tbranch_from_thought,\n\t\t\tcurrent_step,\n\t\t} = thoughtData;\n\n\t\tlet icon: string;\n\t\tlet label = 'Thought';\n\t\tlet suffix = '';\n\n\t\tif (is_revision) {\n\t\t\ticon = chalk.yellow('🔄');\n\t\t\tlabel = 'Revision';\n\t\t\tsuffix = chalk.gray(` (revise #${revises_thought})`);\n\t\t} else if (branch_from_thought) {\n\t\t\ticon = chalk.green('🌿');\n\t\t\tlabel = 'Branch';\n\t\t\tsuffix = chalk.gray(` (from #${branch_from_thought})`);\n\t\t} else {\n\t\t\tconst thoughtType: ThoughtType = thoughtData.thought_type ?? 'regular';\n\t\t\tswitch (thoughtType) {\n\t\t\t\tcase 'hypothesis':\n\t\t\t\t\ticon = chalk.magenta('🔬');\n\t\t\t\t\tlabel = 'Hypothesis';\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'verification':\n\t\t\t\t\ticon = chalk.green('✅');\n\t\t\t\t\tlabel = 'Verification';\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'critique':\n\t\t\t\t\ticon = chalk.red('🔍');\n\t\t\t\t\tlabel = 'Critique';\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'synthesis':\n\t\t\t\t\ticon = chalk.cyan('🧬');\n\t\t\t\t\tlabel = 'Synthesis';\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'meta':\n\t\t\t\t\ticon = chalk.gray('🧠');\n\t\t\t\t\tlabel = 'Meta';\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'tool_call':\n\t\t\t\t\ticon = chalk.yellow('🔧');\n\t\t\t\t\tlabel = 'Tool Call';\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'tool_observation':\n\t\t\t\t\ticon = chalk.yellow('👁️');\n\t\t\t\t\tlabel = 'Tool Observation';\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'assumption':\n\t\t\t\t\ticon = chalk.yellow('💡');\n\t\t\t\t\tlabel = 'Assumption';\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'decomposition':\n\t\t\t\t\ticon = chalk.cyan('🧩');\n\t\t\t\t\tlabel = 'Decomposition';\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'backtrack':\n\t\t\t\t\ticon = chalk.red('↩️');\n\t\t\t\t\tlabel = 'Backtrack';\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\ticon = chalk.blue('💭');\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// Build header: \"💭 Thought 1/3: \"\n\t\tconst header = `${icon} ${label} ${thought_number}/${total_thoughts}${suffix}: `;\n\n\t\t// Build content lines\n\t\tconst lines: string[] = [];\n\n\t\t// Add the thought content\n\t\tlines.push(`${header}${thought}`);\n\n\t\t// Add recommendation if present\n\t\tif (current_step) {\n\t\t\tconst recommendation = this.formatRecommendation(current_step);\n\t\t\tlines.push(` ${recommendation}`);\n\t\t}\n\n\t\t// Add id if present (for DAG debugging)\n\t\tif (thoughtData.id) {\n\t\t\tlines.push(` ${chalk.gray(`🆔 ${thoughtData.id}`)}`);\n\t\t}\n\n\t\t// Add meta observation if present\n\t\tif (thoughtData.meta_observation) {\n\t\t\tlines.push(` ${chalk.gray(`📝 ${thoughtData.meta_observation}`)}`);\n\t\t}\n\n\t\treturn lines.join('\\n');\n\t}\n}\n"],"names":["ThoughtFormatter","step","parts","toolNames","t","chalk","skillNames","s","thoughtData","thought_number","total_thoughts","thought","is_revision","revises_thought","branch_from_thought","current_step","icon","label","suffix","thoughtType","header","lines","recommendation"],"mappings":"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"}
1
+ {"version":3,"file":"core/ThoughtFormatter.js","sources":["../../src/core/ThoughtFormatter.ts"],"sourcesContent":["/**\n * Display formatting for thoughts and recommendations.\n *\n * This module provides the `ThoughtFormatter` class which handles all\n * presentation logic for thought data, including clean, simple output\n * formatting and structured display of tool/skill recommendations.\n *\n * @module formatter\n */\n\nimport chalk from 'chalk';\nimport type { ThoughtType } from './reasoning.js';\nimport type { StepRecommendation } from './step.js';\nimport type { ThoughtData } from './thought.js';\n\n/**\n * Formatter for thought data and step recommendations.\n *\n * This class separates presentation concerns from business logic, providing\n * clean, readable output for thoughts with structured display of\n * tool and skill recommendations.\n *\n * @remarks\n * Output Format is clean and simple:\n * - 💭 Thought - Regular thought (blue)\n * - 🔬 Hypothesis - Proposed explanation (magenta)\n * - ✅ Verification - Testing a hypothesis (green)\n * - 🔍 Critique - Self-critique of reasoning (red)\n * - 🧬 Synthesis - Combining thoughts/branches (cyan)\n * - 🧠 Meta - Metacognitive observation (gray)\n * - 🔄 Revision - Thought that revises a previous thought (yellow)\n * - 🌿 Branch - Thought that creates a new branch (green)\n * @example\n * ```typescript\n * const formatter = new ThoughtFormatter();\n *\n * // Format a thought with recommendations\n * const output = formatter.formatThought({\n * thought: 'I need to analyze the data structure',\n * thought_number: 1,\n * total_thoughts: 3,\n * next_thought_needed: true,\n * current_step: {\n * step_description: 'Analyze data structure',\n * recommended_tools: [{\n * tool_name: 'Read',\n * confidence: 0.95,\n * rationale: 'Direct file reading',\n * priority: 1,\n * suggested_inputs: { file_path: './data/schema.json' }\n * }],\n * expected_outcome: 'Understanding of data schema'\n * }\n * });\n *\n * console.log(output);\n * ```\n */\nexport class ThoughtFormatter {\n\t/**\n\t * Formats a step recommendation into a readable string.\n\t *\n\t * Creates a structured display of the step description, recommended tools,\n\t * recommended skills, expected outcome, and conditions for the next step.\n\t *\n\t * @param step - The step recommendation to format\n\t * @returns A formatted string representation of the recommendation\n\t *\n\t * @example\n\t * ```typescript\n\t * const step: StepRecommendation = {\n\t * step_description: 'Search for API endpoints',\n\t * recommended_tools: [{\n\t * tool_name: 'Grep',\n\t * confidence: 0.9,\n\t * rationale: 'Best for searching code patterns',\n\t * priority: 1,\n\t * suggested_inputs: { pattern: 'export.*function' }\n\t * }],\n\t * expected_outcome: 'List of all exported API functions',\n\t * next_step_conditions: ['If no results, try broader pattern']\n\t * };\n\t *\n\t * const formatted = formatter.formatRecommendation(step);\n\t * console.log(formatted);\n\t * ```\n\t */\n\tpublic formatRecommendation(step: StepRecommendation): string {\n\t\tconst parts: string[] = [];\n\n\t\t// Add tools if present\n\t\tif (step.recommended_tools?.length) {\n\t\t\tconst toolNames = step.recommended_tools.map((t) => t.tool_name).join(', ');\n\t\t\tparts.push(chalk.cyan(`Tools: ${toolNames}`));\n\t\t}\n\n\t\t// Add skills if present\n\t\tif (step.recommended_skills?.length) {\n\t\t\tconst skillNames = step.recommended_skills.map((s) => s.skill_name).join(', ');\n\t\t\tparts.push(chalk.green(`Skills: ${skillNames}`));\n\t\t}\n\n\t\t// Add expected outcome\n\t\tif (step.expected_outcome) {\n\t\t\tparts.push(chalk.gray(`→ ${step.expected_outcome}`));\n\t\t}\n\n\t\treturn parts.join(' | ');\n\t}\n\n\t/**\n\t * Formats a thought into a clean, simple display.\n\t *\n\t * Creates a clean output containing the thought content with an appropriate\n\t * header indicating the thought type. Priority order for icon selection:\n\t * `is_revision` > `branch_from_thought` > `thought_type`.\n\t *\n\t * Supported `thought_type` icons:\n\t * - `'regular'` (or undefined): 💭 blue \"Thought\" (default)\n\t * - `'hypothesis'`: 🔬 magenta \"Hypothesis\"\n\t * - `'verification'`: ✅ green \"Verification\"\n\t * - `'critique'`: 🔍 red \"Critique\"\n\t * - `'synthesis'`: 🧬 cyan \"Synthesis\"\n\t * - `'meta'`: 🧠 gray \"Meta\"\n\t *\n\t * @param thoughtData - The thought data to format\n\t * @returns A formatted string with thought and recommendations\n\t *\n\t * @example\n\t * ```typescript\n\t * // Regular thought\n\t * const regular = formatter.formatThought({\n\t * thought: 'I should read the configuration file',\n\t * thought_number: 1,\n\t * total_thoughts: 3,\n\t * next_thought_needed: true\n\t * });\n\t * // Output: 💭 Thought 1/3: I should read the configuration file\n\t *\n\t * // With recommendation\n\t * const withRec = formatter.formatThought({\n\t * thought: 'I need to search the codebase',\n\t * thought_number: 1,\n\t * total_thoughts: 3,\n\t * next_thought_needed: true,\n\t * current_step: {\n\t * step_description: 'Search for files',\n\t * recommended_tools: [{ tool_name: 'Grep', priority: 1 }],\n\t * expected_outcome: 'List of matching files'\n\t * }\n\t * });\n\t * // Output:\n\t * // 💭 Thought 1/3: I need to search the codebase\n\t * // → Tools: Grep | List of matching files\n\t * ```\n\t */\n\tpublic formatThought(thoughtData: ThoughtData): string {\n\t\tconst {\n\t\t\tthought_number,\n\t\t\ttotal_thoughts,\n\t\t\tthought,\n\t\t\tis_revision,\n\t\t\trevises_thought,\n\t\t\tbranch_from_thought,\n\t\t\tcurrent_step,\n\t\t} = thoughtData;\n\n\t\tlet icon: string;\n\t\tlet label = 'Thought';\n\t\tlet suffix = '';\n\n\t\tif (is_revision) {\n\t\t\ticon = chalk.yellow('🔄');\n\t\t\tlabel = 'Revision';\n\t\t\tsuffix = chalk.gray(` (revise #${revises_thought})`);\n\t\t} else if (branch_from_thought) {\n\t\t\ticon = chalk.green('🌿');\n\t\t\tlabel = 'Branch';\n\t\t\tsuffix = chalk.gray(` (from #${branch_from_thought})`);\n\t\t} else {\n\t\t\tconst thoughtType: ThoughtType = thoughtData.thought_type ?? 'regular';\n\t\t\tswitch (thoughtType) {\n\t\t\t\tcase 'hypothesis':\n\t\t\t\t\ticon = chalk.magenta('🔬');\n\t\t\t\t\tlabel = 'Hypothesis';\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'verification':\n\t\t\t\t\ticon = chalk.green('✅');\n\t\t\t\t\tlabel = 'Verification';\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'critique':\n\t\t\t\t\ticon = chalk.red('🔍');\n\t\t\t\t\tlabel = 'Critique';\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'synthesis':\n\t\t\t\t\ticon = chalk.cyan('🧬');\n\t\t\t\t\tlabel = 'Synthesis';\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'meta':\n\t\t\t\t\ticon = chalk.gray('🧠');\n\t\t\t\t\tlabel = 'Meta';\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'tool_call':\n\t\t\t\t\ticon = chalk.yellow('🔧');\n\t\t\t\t\tlabel = 'Tool Call';\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'tool_observation':\n\t\t\t\t\ticon = chalk.yellow('👁️');\n\t\t\t\t\tlabel = 'Tool Observation';\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'assumption':\n\t\t\t\t\ticon = chalk.yellow('💡');\n\t\t\t\t\tlabel = 'Assumption';\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'decomposition':\n\t\t\t\t\ticon = chalk.cyan('🧩');\n\t\t\t\t\tlabel = 'Decomposition';\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'backtrack':\n\t\t\t\t\ticon = chalk.red('↩️');\n\t\t\t\t\tlabel = 'Backtrack';\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\ticon = chalk.blue('💭');\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// Build header: \"💭 Thought 1/3: \"\n\t\tconst retractedTag = thoughtData.retracted ? chalk.red.strikethrough('[RETRACTED] ') : '';\n\t\tconst header = `${icon} ${label} ${thought_number}/${total_thoughts}${suffix}: ${retractedTag}`;\n\n\t\t// Build content lines\n\t\tconst lines: string[] = [];\n\n\t\t// Add the thought content\n\t\tlines.push(`${header}${thought}`);\n\n\t\t// Add recommendation if present\n\t\tif (current_step) {\n\t\t\tconst recommendation = this.formatRecommendation(current_step);\n\t\t\tlines.push(` ${recommendation}`);\n\t\t}\n\n\t\t// Add id if present (for DAG debugging)\n\t\tif (thoughtData.id) {\n\t\t\tlines.push(` ${chalk.gray(`🆔 ${thoughtData.id}`)}`);\n\t\t}\n\n\t\t// Add meta observation if present\n\t\tif (thoughtData.meta_observation) {\n\t\t\tlines.push(` ${chalk.gray(`📝 ${thoughtData.meta_observation}`)}`);\n\t\t}\n\n\t\treturn lines.join('\\n');\n\t}\n}\n"],"names":["ThoughtFormatter","step","parts","toolNames","t","chalk","skillNames","s","thoughtData","thought_number","total_thoughts","thought","is_revision","revises_thought","branch_from_thought","current_step","icon","label","suffix","thoughtType","retractedTag","header","lines","recommendation"],"mappings":";AA0DO,MAAMA;IA6BL,qBAAqBC,IAAwB,EAAU;QAC7D,MAAMC,QAAkB,EAAE;QAG1B,IAAID,KAAK,iBAAiB,EAAE,QAAQ;YACnC,MAAME,YAAYF,KAAK,iBAAiB,CAAC,GAAG,CAAC,CAACG,IAAMA,EAAE,SAAS,EAAE,IAAI,CAAC;YACtEF,MAAM,IAAI,CAACG,MAAM,IAAI,CAAC,CAAC,OAAO,EAAEF,WAAW;QAC5C;QAGA,IAAIF,KAAK,kBAAkB,EAAE,QAAQ;YACpC,MAAMK,aAAaL,KAAK,kBAAkB,CAAC,GAAG,CAAC,CAACM,IAAMA,EAAE,UAAU,EAAE,IAAI,CAAC;YACzEL,MAAM,IAAI,CAACG,MAAM,KAAK,CAAC,CAAC,QAAQ,EAAEC,YAAY;QAC/C;QAGA,IAAIL,KAAK,gBAAgB,EACxBC,MAAM,IAAI,CAACG,MAAM,IAAI,CAAC,CAAC,EAAE,EAAEJ,KAAK,gBAAgB,EAAE;QAGnD,OAAOC,MAAM,IAAI,CAAC;IACnB;IAgDO,cAAcM,WAAwB,EAAU;QACtD,MAAM,EACLC,cAAc,EACdC,cAAc,EACdC,OAAO,EACPC,WAAW,EACXC,eAAe,EACfC,mBAAmB,EACnBC,YAAY,EACZ,GAAGP;QAEJ,IAAIQ;QACJ,IAAIC,QAAQ;QACZ,IAAIC,SAAS;QAEb,IAAIN,aAAa;YAChBI,OAAOX,MAAM,MAAM,CAAC;YACpBY,QAAQ;YACRC,SAASb,MAAM,IAAI,CAAC,CAAC,UAAU,EAAEQ,gBAAgB,CAAC,CAAC;QACpD,OAAO,IAAIC,qBAAqB;YAC/BE,OAAOX,MAAM,KAAK,CAAC;YACnBY,QAAQ;YACRC,SAASb,MAAM,IAAI,CAAC,CAAC,QAAQ,EAAES,oBAAoB,CAAC,CAAC;QACtD,OAAO;YACN,MAAMK,cAA2BX,YAAY,YAAY,IAAI;YAC7D,OAAQW;gBACP,KAAK;oBACJH,OAAOX,MAAM,OAAO,CAAC;oBACrBY,QAAQ;oBACR;gBACD,KAAK;oBACJD,OAAOX,MAAM,KAAK,CAAC;oBACnBY,QAAQ;oBACR;gBACD,KAAK;oBACJD,OAAOX,MAAM,GAAG,CAAC;oBACjBY,QAAQ;oBACR;gBACD,KAAK;oBACJD,OAAOX,MAAM,IAAI,CAAC;oBAClBY,QAAQ;oBACR;gBACD,KAAK;oBACJD,OAAOX,MAAM,IAAI,CAAC;oBAClBY,QAAQ;oBACR;gBACD,KAAK;oBACJD,OAAOX,MAAM,MAAM,CAAC;oBACpBY,QAAQ;oBACR;gBACD,KAAK;oBACJD,OAAOX,MAAM,MAAM,CAAC;oBACpBY,QAAQ;oBACR;gBACD,KAAK;oBACJD,OAAOX,MAAM,MAAM,CAAC;oBACpBY,QAAQ;oBACR;gBACD,KAAK;oBACJD,OAAOX,MAAM,IAAI,CAAC;oBAClBY,QAAQ;oBACR;gBACD,KAAK;oBACJD,OAAOX,MAAM,GAAG,CAAC;oBACjBY,QAAQ;oBACR;gBACD;oBACCD,OAAOX,MAAM,IAAI,CAAC;oBAClB;YACF;QACD;QAGA,MAAMe,eAAeZ,YAAY,SAAS,GAAGH,MAAM,GAAG,CAAC,aAAa,CAAC,kBAAkB;QACvF,MAAMgB,SAAS,GAAGL,KAAK,CAAC,EAAEC,MAAM,CAAC,EAAER,eAAe,CAAC,EAAEC,iBAAiBQ,OAAO,EAAE,EAAEE,cAAc;QAG/F,MAAME,QAAkB,EAAE;QAG1BA,MAAM,IAAI,CAAC,GAAGD,SAASV,SAAS;QAGhC,IAAII,cAAc;YACjB,MAAMQ,iBAAiB,IAAI,CAAC,oBAAoB,CAACR;YACjDO,MAAM,IAAI,CAAC,CAAC,EAAE,EAAEC,gBAAgB;QACjC;QAGA,IAAIf,YAAY,EAAE,EACjBc,MAAM,IAAI,CAAC,CAAC,EAAE,EAAEjB,MAAM,IAAI,CAAC,CAAC,GAAG,EAAEG,YAAY,EAAE,EAAE,GAAG;QAIrD,IAAIA,YAAY,gBAAgB,EAC/Bc,MAAM,IAAI,CAAC,CAAC,EAAE,EAAEjB,MAAM,IAAI,CAAC,CAAC,GAAG,EAAEG,YAAY,gBAAgB,EAAE,GAAG;QAGnE,OAAOc,MAAM,IAAI,CAAC;IACnB;AACD"}