tracelattice 1.3.2 → 1.3.3

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 (148) hide show
  1. package/README.md +25 -25
  2. package/dist/ServerConfig.d.ts +2 -23
  3. package/dist/ServerConfig.d.ts.map +1 -1
  4. package/dist/ServerConfig.js.map +1 -1
  5. package/dist/__tests__/eval/fixtures/scenarios.d.ts.map +1 -1
  6. package/dist/__tests__/helpers/factories.d.ts +13 -1
  7. package/dist/__tests__/helpers/factories.d.ts.map +1 -1
  8. package/dist/cache/DiscoveryCache.d.ts +1 -1
  9. package/dist/cache/DiscoveryCache.d.ts.map +1 -1
  10. package/dist/cache/DiscoveryCache.js.map +1 -1
  11. package/dist/cli.js +3483 -8
  12. package/dist/config/ConfigLoader.d.ts +2 -2
  13. package/dist/config/ConfigLoader.d.ts.map +1 -1
  14. package/dist/config/ConfigLoader.js +6 -4
  15. package/dist/config/ConfigLoader.js.map +1 -1
  16. package/dist/contracts/PersistenceBackend.d.ts.map +1 -0
  17. package/dist/contracts/features.d.ts +39 -0
  18. package/dist/contracts/features.d.ts.map +1 -0
  19. package/dist/contracts/features.js +15 -0
  20. package/dist/contracts/features.js.map +1 -0
  21. package/dist/contracts/ids.d.ts +58 -0
  22. package/dist/contracts/ids.d.ts.map +1 -0
  23. package/dist/contracts/ids.js +31 -0
  24. package/dist/contracts/ids.js.map +1 -0
  25. package/dist/contracts/interfaces.d.ts +6 -3
  26. package/dist/contracts/interfaces.d.ts.map +1 -1
  27. package/dist/contracts/strategy.d.ts +2 -2
  28. package/dist/contracts/strategy.d.ts.map +1 -1
  29. package/dist/contracts/suspension.d.ts +3 -2
  30. package/dist/contracts/suspension.d.ts.map +1 -1
  31. package/dist/contracts/transport.d.ts +25 -0
  32. package/dist/contracts/transport.d.ts.map +1 -0
  33. package/dist/core/HistoryManager.d.ts +2 -3
  34. package/dist/core/HistoryManager.d.ts.map +1 -1
  35. package/dist/core/HistoryManager.js.map +1 -1
  36. package/dist/core/IHistoryManager.d.ts +10 -0
  37. package/dist/core/IHistoryManager.d.ts.map +1 -1
  38. package/dist/core/IThoughtFormatter.d.ts +51 -0
  39. package/dist/core/IThoughtFormatter.d.ts.map +1 -0
  40. package/dist/core/IThoughtFormatter.js +1 -0
  41. package/dist/core/InputNormalizer.d.ts.map +1 -1
  42. package/dist/core/InputNormalizer.js +4 -3
  43. package/dist/core/InputNormalizer.js.map +1 -1
  44. package/dist/core/PersistenceBuffer.d.ts +1 -1
  45. package/dist/core/PersistenceBuffer.d.ts.map +1 -1
  46. package/dist/core/PersistenceBuffer.js.map +1 -1
  47. package/dist/core/ThoughtFormatter.d.ts +2 -1
  48. package/dist/core/ThoughtFormatter.d.ts.map +1 -1
  49. package/dist/core/ThoughtFormatter.js +3 -0
  50. package/dist/core/ThoughtFormatter.js.map +1 -1
  51. package/dist/core/ThoughtProcessor.d.ts +2 -2
  52. package/dist/core/ThoughtProcessor.d.ts.map +1 -1
  53. package/dist/core/ThoughtProcessor.js +8 -3
  54. package/dist/core/ThoughtProcessor.js.map +1 -1
  55. package/dist/core/compression/CompressionService.js +3 -3
  56. package/dist/core/compression/CompressionService.js.map +1 -1
  57. package/dist/core/compression/Summary.d.ts +4 -3
  58. package/dist/core/compression/Summary.d.ts.map +1 -1
  59. package/dist/core/graph/Edge.d.ts +11 -4
  60. package/dist/core/graph/Edge.d.ts.map +1 -1
  61. package/dist/core/graph/EdgeEmitter.js +5 -5
  62. package/dist/core/graph/EdgeEmitter.js.map +1 -1
  63. package/dist/core/reasoning/strategies/StrategyFactory.d.ts +1 -1
  64. package/dist/core/reasoning/strategies/StrategyFactory.d.ts.map +1 -1
  65. package/dist/core/reasoning/strategies/StrategyFactory.js.map +1 -1
  66. package/dist/core/reasoning/strategies/TreeOfThoughtStrategy.d.ts.map +1 -1
  67. package/dist/core/reasoning/strategies/TreeOfThoughtStrategy.js +5 -0
  68. package/dist/core/reasoning/strategies/TreeOfThoughtStrategy.js.map +1 -1
  69. package/dist/core/reasoning.d.ts +8 -1
  70. package/dist/core/reasoning.d.ts.map +1 -1
  71. package/dist/core/step.d.ts +5 -0
  72. package/dist/core/step.d.ts.map +1 -1
  73. package/dist/core/thought.d.ts +4 -3
  74. package/dist/core/thought.d.ts.map +1 -1
  75. package/dist/core/tools/InMemorySuspensionStore.d.ts +3 -1
  76. package/dist/core/tools/InMemorySuspensionStore.d.ts.map +1 -1
  77. package/dist/core/tools/InMemorySuspensionStore.js +2 -2
  78. package/dist/core/tools/InMemorySuspensionStore.js.map +1 -1
  79. package/dist/di/Container.d.ts +6 -3
  80. package/dist/di/Container.d.ts.map +1 -1
  81. package/dist/di/Container.js.map +1 -1
  82. package/dist/di/ServiceRegistry.d.ts +3 -3
  83. package/dist/di/ServiceRegistry.d.ts.map +1 -1
  84. package/dist/errors.d.ts +36 -2
  85. package/dist/errors.d.ts.map +1 -1
  86. package/dist/errors.js +49 -22
  87. package/dist/errors.js.map +1 -1
  88. package/dist/health/HealthChecker.d.ts +1 -1
  89. package/dist/health/HealthChecker.d.ts.map +1 -1
  90. package/dist/health/HealthChecker.js.map +1 -1
  91. package/dist/lib.d.ts +60 -2
  92. package/dist/lib.d.ts.map +1 -1
  93. package/dist/lib.js.map +1 -1
  94. package/dist/persistence/FilePersistence.d.ts +2 -2
  95. package/dist/persistence/FilePersistence.d.ts.map +1 -1
  96. package/dist/persistence/FilePersistence.js.map +1 -1
  97. package/dist/persistence/MemoryPersistence.d.ts +1 -1
  98. package/dist/persistence/MemoryPersistence.d.ts.map +1 -1
  99. package/dist/persistence/MemoryPersistence.js.map +1 -1
  100. package/dist/persistence/PersistenceFactory.d.ts +1 -1
  101. package/dist/persistence/PersistenceFactory.d.ts.map +1 -1
  102. package/dist/persistence/PersistenceFactory.js.map +1 -1
  103. package/dist/persistence/SqlitePersistence.d.ts +1 -1
  104. package/dist/persistence/SqlitePersistence.d.ts.map +1 -1
  105. package/dist/persistence/SqlitePersistence.js.map +1 -1
  106. package/dist/pool/ConnectionPool.d.ts +11 -13
  107. package/dist/pool/ConnectionPool.d.ts.map +1 -1
  108. package/dist/pool/ConnectionPool.js.map +1 -1
  109. package/dist/pool/IConnectionPool.d.ts +100 -0
  110. package/dist/pool/IConnectionPool.d.ts.map +1 -0
  111. package/dist/pool/IConnectionPool.js +1 -0
  112. package/dist/registry/BaseRegistry.d.ts +1 -1
  113. package/dist/registry/BaseRegistry.d.ts.map +1 -1
  114. package/dist/registry/BaseRegistry.js.map +1 -1
  115. package/dist/schema.d.ts.map +1 -1
  116. package/dist/schema.js.map +1 -1
  117. package/dist/transport/BaseTransport.d.ts +3 -2
  118. package/dist/transport/BaseTransport.d.ts.map +1 -1
  119. package/dist/transport/BaseTransport.js +1 -1
  120. package/dist/transport/BaseTransport.js.map +1 -1
  121. package/dist/transport/HttpTransport.d.ts +4 -2
  122. package/dist/transport/HttpTransport.d.ts.map +1 -1
  123. package/dist/transport/HttpTransport.js +4 -1
  124. package/dist/transport/HttpTransport.js.map +1 -1
  125. package/dist/transport/SseTransport.d.ts +4 -2
  126. package/dist/transport/SseTransport.d.ts.map +1 -1
  127. package/dist/transport/SseTransport.js +3 -0
  128. package/dist/transport/SseTransport.js.map +1 -1
  129. package/dist/transport/StreamableHttpTransport.d.ts +4 -2
  130. package/dist/transport/StreamableHttpTransport.d.ts.map +1 -1
  131. package/dist/transport/StreamableHttpTransport.js +4 -1
  132. package/dist/transport/StreamableHttpTransport.js.map +1 -1
  133. package/dist/types/skill.d.ts +5 -0
  134. package/dist/types/skill.d.ts.map +1 -1
  135. package/dist/types/tool.d.ts +5 -0
  136. package/dist/types/tool.d.ts.map +1 -1
  137. package/package.json +11 -11
  138. package/dist/__tests__/helpers/index.d.ts +0 -3
  139. package/dist/__tests__/helpers/index.d.ts.map +0 -1
  140. package/dist/contracts/index.d.ts +0 -14
  141. package/dist/contracts/index.d.ts.map +0 -1
  142. package/dist/index.d.ts +0 -2
  143. package/dist/index.d.ts.map +0 -1
  144. package/dist/index.js +0 -1
  145. package/dist/persistence/PersistenceBackend.d.ts.map +0 -1
  146. /package/dist/{persistence → contracts}/PersistenceBackend.d.ts +0 -0
  147. /package/dist/{persistence → contracts}/PersistenceBackend.js +0 -0
  148. /package/dist/contracts/{index.js → transport.js} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"core/HistoryManager.js","sources":["../../src/core/HistoryManager.ts"],"sourcesContent":["/**\n * History and branch management for sequential thinking.\n *\n * This module provides the `HistoryManager` class which manages thought history,\n * branching, and optional persistence with per-session state isolation.\n *\n * Internally delegates to three focused collaborators:\n * - `EdgeEmitter` — DAG edge emission\n * - `PersistenceBuffer` — buffered persistence + retry/backoff\n * - `SessionManager` — session lifecycle (TTL/LRU eviction)\n *\n * @module HistoryManager\n */\n\nimport type { IMetrics } from '../contracts/index.js';\nimport type { IEdgeStore } from '../contracts/interfaces.js';\nimport type { ISummaryStore } from '../contracts/summary.js';\nimport { ValidationError, getErrorMessage } from '../errors.js';\nimport { NullLogger } from '../logger/NullLogger.js';\nimport type { Logger } from '../logger/StructuredLogger.js';\nimport type { PersistenceBackend } from '../persistence/PersistenceBackend.js';\nimport {\n\tDehydrationPolicy,\n\ttype DehydrationOptions,\n\ttype HydratedEntry,\n} from './compression/DehydrationPolicy.js';\nimport { EdgeEmitter } from './graph/EdgeEmitter.js';\nimport type { IHistoryManager } from './IHistoryManager.js';\nimport { PersistenceBuffer, type PersistenceEventEmitter } from './PersistenceBuffer.js';\nimport { SessionManager } from './SessionManager.js';\nimport type { ThoughtData } from './thought.js';\n\nexport type { PersistenceEventEmitter } from './PersistenceBuffer.js';\n\n/** Absolute maximum history size (~20MB at 2KB/thought). Cannot be overridden. */\nexport const ABSOLUTE_MAX_HISTORY_SIZE = 10_000;\n\ninterface SessionState {\n\tthought_history: ThoughtData[];\n\tbranches: Record<string, ThoughtData[]>;\n\tavailableMcpTools: string[] | undefined;\n\tavailableSkills: string[] | undefined;\n\twriteBuffer: ThoughtData[];\n\tlastAccessedAt: number;\n\tregisteredBranches: Set<string>;\n}\n\nexport interface HistoryManagerConfig {\n\t/** Maximum number of thoughts to keep in main history. @default 1000 */\n\tmaxHistorySize?: number;\n\t/** Maximum number of branches to maintain. @default 50 */\n\tmaxBranches?: number;\n\t/** Maximum size of each branch. @default 100 */\n\tmaxBranchSize?: number;\n\tlogger?: Logger;\n\tpersistence?: PersistenceBackend | null;\n\tmetrics?: IMetrics;\n\t/** Maximum number of thoughts to buffer before flushing. @default 100 */\n\tpersistenceBufferSize?: number;\n\t/** Periodic flush interval in ms. @default 1000 */\n\tpersistenceFlushInterval?: number;\n\t/** Max retries for failed persistence flushes. @default 3 */\n\tpersistenceMaxRetries?: number;\n\teventEmitter?: PersistenceEventEmitter;\n\tedgeStore?: IEdgeStore;\n\tsummaryStore?: ISummaryStore;\n\t/** Whether to emit DAG edges (gated independently of edgeStore). @default false */\n\tdagEdges?: boolean;\n}\n\n/**\n * Manages thought history and branching for sequential thinking.\n *\n * Owns the per-session `Map<string, SessionState>`. Delegates DAG edge emission,\n * buffered persistence, and session TTL/LRU eviction to focused collaborators while\n * preserving test-coupled private member names (`_flushTimer`, `_startFlushTimer`,\n * `_flushBuffer`, `_sessions`).\n */\nexport class HistoryManager implements IHistoryManager {\n\tprivate static readonly DEFAULT_SESSION = '__global__';\n\tprivate static readonly SESSION_TTL_MS = 30 * 60 * 1000;\n\tprivate static readonly MAX_SESSIONS = 100;\n\tprivate _sessions: Map<string, SessionState> = new Map();\n\tprivate _maxHistorySize: number;\n\tprivate _maxBranches: number;\n\tprivate _maxBranchSize: number;\n\tprivate _logger: Logger;\n\tprivate _persistence: PersistenceBackend | null;\n\tprivate _persistenceEnabled: boolean;\n\tprivate _metrics?: IMetrics;\n\n\tprivate _edgeStore?: IEdgeStore;\n\tprivate _summaryStore?: ISummaryStore;\n\tprivate _dagEdges: boolean;\n\n\tprivate _eventEmitter: PersistenceEventEmitter | null;\n\n\tprivate readonly _edgeEmitter: EdgeEmitter;\n\tprivate _persistenceBuffer: PersistenceBuffer<SessionState> | null;\n\tprivate readonly _sessionManager: SessionManager<SessionState>;\n\n\tconstructor(config: HistoryManagerConfig = {}) {\n\t\tthis._logger = config.logger ?? new NullLogger();\n\t\tconst requestedMaxSize = config.maxHistorySize ?? 10000;\n\t\tthis._maxHistorySize = Math.min(requestedMaxSize, ABSOLUTE_MAX_HISTORY_SIZE);\n\t\tif (requestedMaxSize > ABSOLUTE_MAX_HISTORY_SIZE) {\n\t\t\tthis._logger.warn('maxHistorySize exceeds absolute maximum, capped', {\n\t\t\t\trequested: requestedMaxSize,\n\t\t\t\tapplied: ABSOLUTE_MAX_HISTORY_SIZE,\n\t\t\t});\n\t\t}\n\t\tthis._maxBranches = config.maxBranches || 50;\n\t\tthis._maxBranchSize = config.maxBranchSize || 100;\n\t\tthis._persistence = config.persistence ?? null;\n\t\tthis._persistenceEnabled = this._persistence !== null;\n\t\tthis._metrics = config.metrics;\n\t\tthis._eventEmitter = config.eventEmitter ?? null;\n\t\tthis._edgeStore = config.edgeStore;\n\t\tthis._summaryStore = config.summaryStore;\n\t\tthis._dagEdges = config.dagEdges ?? true;\n\n\t\t// Wire delegates\n\t\tthis._edgeEmitter = new EdgeEmitter({\n\t\t\tedgeStore: this._edgeStore,\n\t\t\tdagEdges: this._dagEdges,\n\t\t\tdefaultSessionId: HistoryManager.DEFAULT_SESSION,\n\t\t\tlogger: this._logger,\n\t\t});\n\n\t\tthis._sessionManager = new SessionManager<SessionState>({\n\t\t\tdefaultSessionId: HistoryManager.DEFAULT_SESSION,\n\t\t\tsessionTtlMs: HistoryManager.SESSION_TTL_MS,\n\t\t\tcleanupIntervalMs: 5 * 60 * 1000,\n\t\t\tgetMaxSessions: () => HistoryManager.MAX_SESSIONS,\n\t\t\tlogger: this._logger,\n\t\t});\n\n\t\tthis._persistenceBuffer = null;\n\t\tif (this._persistenceEnabled && this._persistence) {\n\t\t\tthis._persistenceBuffer = new PersistenceBuffer<SessionState>({\n\t\t\t\tpersistence: this._persistence,\n\t\t\t\tbufferSize: config.persistenceBufferSize ?? 100,\n\t\t\t\tflushInterval: config.persistenceFlushInterval ?? 1000,\n\t\t\t\tmaxRetries: config.persistenceMaxRetries ?? 3,\n\t\t\t\tdefaultSessionId: HistoryManager.DEFAULT_SESSION,\n\t\t\t\tgetSessions: () => this._sessions,\n\t\t\t\tgetDefaultSession: () => this._getSession(),\n\t\t\t\tedgeStore: this._edgeStore,\n\t\t\t\teventEmitter: this._eventEmitter,\n\t\t\t\tlogger: this._logger,\n\t\t\t});\n\t\t\tthis._startFlushTimer();\n\t\t}\n\n\t\tthis._sessionManager.startCleanupTimer(this._sessions);\n\t}\n\n\t// Test-coupled accessors: these private member names must remain reachable\n\t// via `manager as unknown as { _flushTimer; _startFlushTimer }`.\n\tprivate get _flushTimer(): ReturnType<typeof setInterval> | null {\n\t\treturn this._persistenceBuffer?.timer ?? null;\n\t}\n\n\tprivate _startFlushTimer(): void {\n\t\tthis._persistenceBuffer?.startFlushTimer();\n\t}\n\n\tprivate _stopFlushTimer(): void {\n\t\tif (this._flushTimer === null) return;\n\t\tthis._persistenceBuffer?.stopFlushTimer();\n\t}\n\n\t/** @internal Public for backward-compatible test coupling. */\n\tpublic async _flushBuffer(): Promise<void> {\n\t\tawait this._persistenceBuffer?.flush();\n\t}\n\n\t/** EdgeStore instance, if configured. Used by ThoughtProcessor for StrategyContext. */\n\tpublic getEdgeStore(): IEdgeStore | undefined {\n\t\treturn this._edgeStore;\n\t}\n\n\tprivate log(message: string, meta?: Record<string, unknown>): void {\n\t\tthis._logger.info(message, meta);\n\t}\n\n\t/** Gets or creates session state; updates lastAccessedAt. */\n\tprivate _getSession(sessionId?: string): SessionState {\n\t\tconst key = sessionId ?? HistoryManager.DEFAULT_SESSION;\n\t\tlet session = this._sessions.get(key);\n\t\tif (!session) {\n\t\t\tsession = {\n\t\t\t\tthought_history: [],\n\t\t\t\tbranches: {},\n\t\t\t\tavailableMcpTools: undefined,\n\t\t\t\tavailableSkills: undefined,\n\t\t\t\twriteBuffer: [],\n\t\t\t\tlastAccessedAt: Date.now(),\n\t\t\t\tregisteredBranches: new Set<string>(),\n\t\t\t};\n\t\t\tthis._sessions.set(key, session);\n\t\t\tthis._sessionManager.evictExcessSessions(this._sessions);\n\t\t}\n\t\tsession.lastAccessedAt = Date.now();\n\t\treturn session;\n\t}\n\n\t/**\n\t * Adds a thought to the history. Routes per-session, applies retraction for backtrack,\n\t * caches tools/skills, trims, branches, emits DAG edges, and buffers for persistence.\n\t */\n\tpublic addThought(thought: ThoughtData): void {\n\t\tconst session = this._getSession(thought.session_id);\n\t\tthis._metrics?.counter(\n\t\t\t'thought_requests_total',\n\t\t\t1,\n\t\t\t{},\n\t\t\t'Total thought requests added to history'\n\t\t);\n\n\t\tsession.thought_history.push(thought);\n\n\t\t// Logical retraction: when a backtrack thought is added, mark its target\n\t\t// as retracted (append-only — target remains in history).\n\t\tif (thought.thought_type === 'backtrack' && thought.backtrack_target !== undefined) {\n\t\t\tthis._applyRetraction(session, thought.backtrack_target);\n\t\t}\n\n\t\t// Cache available_mcp_tools/available_skills for cross-call persistence\n\t\tif (thought.available_mcp_tools) {\n\t\t\tsession.availableMcpTools = thought.available_mcp_tools;\n\t\t}\n\t\tif (thought.available_skills) {\n\t\t\tsession.availableSkills = thought.available_skills;\n\t\t}\n\n\t\tif (session.thought_history.length > this._maxHistorySize) {\n\t\t\tsession.thought_history = session.thought_history.slice(-this._maxHistorySize);\n\t\t\tthis.log(`History trimmed to ${this._maxHistorySize} items`, {\n\t\t\t\tmaxSize: this._maxHistorySize,\n\t\t\t});\n\t\t}\n\n\t\tif (thought.branch_from_thought && thought.branch_id) {\n\t\t\tthis._addToSessionBranch(session, thought.branch_id, thought);\n\t\t}\n\n\t\t// Track merge operations for analytics\n\t\tif (thought.merge_from_thoughts?.length || thought.merge_branch_ids?.length) {\n\t\t\tthis._metrics?.counter(\n\t\t\t\t'thought_merge_operations_total',\n\t\t\t\t1,\n\t\t\t\t{},\n\t\t\t\t'Total merge operations (graph topology)'\n\t\t\t);\n\t\t}\n\n\t\t// Emit DAG edges (no-op unless edgeStore + dagEdges flag both enabled)\n\t\tthis._edgeEmitter.emitEdgesForThought(session, thought);\n\n\t\t// Buffer thought for persistence (no-op when persistence disabled)\n\t\tif (this._persistenceBuffer) {\n\t\t\tthis._persistenceBuffer.bufferThought(session, thought);\n\t\t}\n\t}\n\n\t/** Marks the thought as retracted within the session (append-only). */\n\tprivate _applyRetraction(session: SessionState, targetNumber: number): void {\n\t\tfor (const t of session.thought_history) {\n\t\t\tif (t.thought_number === targetNumber) {\n\t\t\t\tt.retracted = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tfor (const branchThoughts of Object.values(session.branches)) {\n\t\t\tfor (const t of branchThoughts) {\n\t\t\t\tif (t.thought_number === targetNumber) {\n\t\t\t\t\tt.retracted = true;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate _addToSessionBranch(session: SessionState, branchId: string, thought: ThoughtData): void {\n\t\tif (!session.branches[branchId]) {\n\t\t\tsession.branches[branchId] = [];\n\t\t}\n\t\tthis._trimSessionBranchSize(session, branchId);\n\t\tsession.branches[branchId].push(thought);\n\n\t\tif (Object.keys(session.branches).length > this._maxBranches) {\n\t\t\tthis._cleanupSessionBranches(session);\n\t\t}\n\n\t\t// Persist branch to backend if enabled\n\t\tif (this._persistenceEnabled && this._persistence) {\n\t\t\tthis._persistence.saveBranch(branchId, session.branches[branchId]).catch((err) => {\n\t\t\t\tthis.log('Failed to persist branch', {\n\t\t\t\t\tbranchId,\n\t\t\t\t\terror: getErrorMessage(err),\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate _cleanupSessionBranches(session: SessionState): void {\n\t\tconst branchCount = Object.keys(session.branches).length;\n\t\tif (branchCount > this._maxBranches) {\n\t\t\tconst branchesToRemove = Object.keys(session.branches).slice(\n\t\t\t\t0,\n\t\t\t\tbranchCount - this._maxBranches\n\t\t\t);\n\t\t\tfor (const branchId of branchesToRemove) {\n\t\t\t\tdelete session.branches[branchId];\n\t\t\t\tthis.log(`Removed old branch: ${branchId}`, { branchId });\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate _trimSessionBranchSize(session: SessionState, branchId: string): void {\n\t\tif ((session.branches[branchId] ?? []).length > this._maxBranchSize) {\n\t\t\tconst removed = session.branches[branchId]!.length - this._maxBranchSize;\n\t\t\tsession.branches[branchId] = session.branches[branchId]!.slice(-this._maxBranchSize);\n\t\t\tthis.log(`Trimmed branch '${branchId}': removed ${removed} old thoughts`, {\n\t\t\t\tbranchId,\n\t\t\t\tremoved,\n\t\t\t});\n\t\t}\n\t}\n\n\tpublic getHistory(sessionId?: string): ThoughtData[] {\n\t\treturn this._getSession(sessionId).thought_history;\n\t}\n\n\t/**\n\t * Returns history with optional sliding-window dehydration. Non-mutating: when\n\t * `dagEdges` is off OR no `ISummaryStore` is configured, returns same as getHistory.\n\t */\n\tpublic getHistoryHydrated(\n\t\tsessionId?: string,\n\t\topts?: DehydrationOptions\n\t): HydratedEntry[] {\n\t\tconst history = this.getHistory(sessionId);\n\t\tif (!this._dagEdges || !this._summaryStore) {\n\t\t\treturn history.slice();\n\t\t}\n\t\tconst sid = sessionId ?? HistoryManager.DEFAULT_SESSION;\n\t\tconst policy = new DehydrationPolicy(this._summaryStore);\n\t\treturn policy.apply(history, sid, opts);\n\t}\n\n\tpublic getHistoryLength(sessionId?: string): number {\n\t\treturn this._getSession(sessionId).thought_history.length;\n\t}\n\n\tpublic getBranches(sessionId?: string): Record<string, ThoughtData[]> {\n\t\treturn this._getSession(sessionId).branches;\n\t}\n\n\tpublic getBranchIds(sessionId?: string): string[] {\n\t\tconst session = this._getSession(sessionId);\n\t\tconst ids = new Set<string>(Object.keys(session.branches));\n\t\tfor (const id of session.registeredBranches) ids.add(id);\n\t\treturn Array.from(ids);\n\t}\n\n\t/** @throws {ValidationError} If branchId is empty or already exists. */\n\tpublic registerBranch(sessionId: string | undefined, branchId: string): void {\n\t\tif (typeof branchId !== 'string' || branchId.length === 0) {\n\t\t\tthrow new ValidationError('branch_id', 'branch_id must be a non-empty string');\n\t\t}\n\t\tconst session = this._getSession(sessionId);\n\t\tif (branchId in session.branches || session.registeredBranches.has(branchId)) {\n\t\t\tthrow new ValidationError('branch_id', `Branch already exists: ${branchId}`);\n\t\t}\n\t\tsession.registeredBranches.add(branchId);\n\t\tthis.log('Registered branch', { branchId, sessionId: sessionId ?? null });\n\t}\n\n\tpublic branchExists(sessionId: string | undefined, branchId: string): boolean {\n\t\tconst session = this._getSession(sessionId);\n\t\treturn branchId in session.branches || session.registeredBranches.has(branchId);\n\t}\n\n\tpublic getAvailableMcpTools(sessionId?: string): string[] | undefined {\n\t\treturn this._getSession(sessionId).availableMcpTools;\n\t}\n\n\tpublic getAvailableSkills(sessionId?: string): string[] | undefined {\n\t\treturn this._getSession(sessionId).availableSkills;\n\t}\n\n\tpublic getBranch(branchId: string, sessionId?: string): ThoughtData[] | undefined {\n\t\treturn this._getSession(sessionId).branches[branchId];\n\t}\n\n\t/** Clears history and branches. If sessionId provided, clears only that session. */\n\tpublic clear(sessionId?: string): void {\n\t\t// Clear edges from EdgeStore (before session map mutation so keys are still available)\n\t\tif (this._edgeStore) {\n\t\t\tif (sessionId !== undefined) {\n\t\t\t\tthis._edgeStore.clearSession(sessionId);\n\t\t\t} else {\n\t\t\t\tfor (const sid of this._sessions.keys()) {\n\t\t\t\t\tthis._edgeStore.clearSession(sid);\n\t\t\t\t}\n\t\t\t\t// Also clear the default session in case no session entries exist yet\n\t\t\t\tthis._edgeStore.clearSession(HistoryManager.DEFAULT_SESSION);\n\t\t\t}\n\t\t}\n\n\t\tif (sessionId !== undefined) {\n\t\t\tthis._sessions.delete(sessionId);\n\t\t\tthis.log('Session cleared', { sessionId });\n\t\t} else {\n\t\t\tthis._sessions.clear();\n\t\t\tthis.log('History cleared (all sessions)');\n\t\t}\n\n\t\t// Clear persisted data if enabled\n\t\tif (this._persistenceEnabled && this._persistence) {\n\t\t\tthis._persistence.clear().catch((err) => {\n\t\t\t\tthis.log('Failed to clear persisted data', {\n\t\t\t\t\terror: getErrorMessage(err),\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\t}\n\n\tpublic clearSession(sessionId: string): void {\n\t\tthis.clear(sessionId);\n\t}\n\n\tpublic getSessionIds(): string[] {\n\t\treturn Array.from(this._sessions.keys());\n\t}\n\n\tpublic getSessionCount(): number {\n\t\treturn this._sessions.size;\n\t}\n\n\t/** Loads history from persistence into the global session. Call at init. */\n\tpublic async loadFromPersistence(): Promise<void> {\n\t\tif (!this._persistenceEnabled || !this._persistence) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst isHealthy = await this._persistence.healthy();\n\t\t\tif (!isHealthy) {\n\t\t\t\tthis.log('Persistence backend not healthy, skipping load');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst globalSession = this._getSession();\n\n\t\t\tconst history = await this._persistence.loadHistory();\n\t\t\tif (history.length > 0) {\n\t\t\t\tglobalSession.thought_history = history.slice(-this._maxHistorySize);\n\t\t\t\tthis.log(`Loaded ${globalSession.thought_history.length} thoughts from persistence`);\n\t\t\t}\n\n\t\t\tconst branchIds = await this._persistence.listBranches();\n\t\t\tfor (const branchId of branchIds) {\n\t\t\t\tconst branchData = await this._persistence.loadBranch(branchId);\n\t\t\t\tif (branchData) {\n\t\t\t\t\tglobalSession.branches[branchId] = branchData.slice(-this._maxBranchSize);\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.log(`Loaded ${Object.keys(globalSession.branches).length} branches from persistence`);\n\n\t\t\t// Load edges if EdgeStore is configured — restore for ALL persisted sessions\n\t\t\tif (this._edgeStore) {\n\t\t\t\ttry {\n\t\t\t\t\tconst edgeSessions = await this._persistence.listEdgeSessions();\n\t\t\t\t\tlet totalEdges = 0;\n\t\t\t\t\tfor (const sessionId of edgeSessions) {\n\t\t\t\t\t\tconst edges = await this._persistence.loadEdges(sessionId);\n\t\t\t\t\t\tfor (const edge of edges) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tthis._edgeStore.addEdge(edge);\n\t\t\t\t\t\t\t\ttotalEdges++;\n\t\t\t\t\t\t\t} catch (edgeErr) {\n\t\t\t\t\t\t\t\tthis.log('Failed to restore edge', {\n\t\t\t\t\t\t\t\t\tedgeId: edge.id,\n\t\t\t\t\t\t\t\t\tsessionId,\n\t\t\t\t\t\t\t\t\terror: getErrorMessage(edgeErr),\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthis.log(\n\t\t\t\t\t\t`Loaded ${totalEdges} edges across ${edgeSessions.length} sessions from persistence`,\n\t\t\t\t\t);\n\t\t\t\t} catch (edgeError) {\n\t\t\t\t\tthis.log('Failed to load edges from persistence', {\n\t\t\t\t\t\terror: getErrorMessage(edgeError),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthis.log('Failed to load from persistence', {\n\t\t\t\terror: getErrorMessage(error),\n\t\t\t});\n\t\t}\n\t}\n\n\tpublic isPersistenceEnabled(): boolean {\n\t\treturn this._persistenceEnabled;\n\t}\n\n\tpublic getPersistenceBackend(): PersistenceBackend | null {\n\t\treturn this._persistence;\n\t}\n\n\t/** Sets the event emitter for persistence error events (post-construction wiring). */\n\tpublic setEventEmitter(emitter: PersistenceEventEmitter): void {\n\t\tthis._eventEmitter = emitter;\n\t\tthis._persistenceBuffer?.setEventEmitter(emitter);\n\t}\n\n\t/** Stops timers and flushes any remaining buffered writes. */\n\tpublic async shutdown(): Promise<void> {\n\t\tthis._stopFlushTimer();\n\t\tthis._sessionManager.stopCleanupTimer();\n\t\tawait this._flushBuffer();\n\t}\n\n\t/** Total write buffer length across all sessions. */\n\tpublic getWriteBufferLength(): number {\n\t\tlet total = 0;\n\t\tfor (const session of this._sessions.values()) {\n\t\t\ttotal += session.writeBuffer.length;\n\t\t}\n\t\treturn total;\n\t}\n}\n"],"names":["ABSOLUTE_MAX_HISTORY_SIZE","HistoryManager","Map","config","NullLogger","requestedMaxSize","Math","EdgeEmitter","SessionManager","PersistenceBuffer","message","meta","sessionId","key","session","undefined","Date","Set","thought","targetNumber","t","branchThoughts","Object","branchId","err","getErrorMessage","branchCount","branchesToRemove","removed","opts","history","sid","policy","DehydrationPolicy","ids","id","Array","ValidationError","isHealthy","globalSession","branchIds","branchData","edgeSessions","totalEdges","edges","edge","edgeErr","edgeError","error","emitter","total"],"mappings":"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"}
1
+ {"version":3,"file":"core/HistoryManager.js","sources":["../../src/core/HistoryManager.ts"],"sourcesContent":["/**\n * History and branch management for sequential thinking.\n *\n * This module provides the `HistoryManager` class which manages thought history,\n * branching, and optional persistence with per-session state isolation.\n *\n * Internally delegates to three focused collaborators:\n * - `EdgeEmitter` — DAG edge emission\n * - `PersistenceBuffer` — buffered persistence + retry/backoff\n * - `SessionManager` — session lifecycle (TTL/LRU eviction)\n *\n * @module HistoryManager\n */\n\nimport type { IMetrics } from '../contracts/interfaces.js';\nimport type { IEdgeStore } from '../contracts/interfaces.js';\nimport type { ISummaryStore } from '../contracts/summary.js';\nimport { ValidationError, getErrorMessage } from '../errors.js';\nimport { NullLogger } from '../logger/NullLogger.js';\nimport type { Logger } from '../logger/StructuredLogger.js';\nimport type { PersistenceBackend } from '../contracts/PersistenceBackend.js';\nimport {\n\tDehydrationPolicy,\n\ttype DehydrationOptions,\n\ttype HydratedEntry,\n} from './compression/DehydrationPolicy.js';\nimport { EdgeEmitter } from './graph/EdgeEmitter.js';\nimport type { IHistoryManager } from './IHistoryManager.js';\nimport { PersistenceBuffer, type PersistenceEventEmitter } from './PersistenceBuffer.js';\nimport { SessionManager } from './SessionManager.js';\nimport type { ThoughtData } from './thought.js';\n\n\n/** Absolute maximum history size (~20MB at 2KB/thought). Cannot be overridden. */\nexport const ABSOLUTE_MAX_HISTORY_SIZE = 10_000;\n\ninterface SessionState {\n\tthought_history: ThoughtData[];\n\tbranches: Record<string, ThoughtData[]>;\n\tavailableMcpTools: string[] | undefined;\n\tavailableSkills: string[] | undefined;\n\twriteBuffer: ThoughtData[];\n\tlastAccessedAt: number;\n\tregisteredBranches: Set<string>;\n}\n\nexport interface HistoryManagerConfig {\n\t/** Maximum number of thoughts to keep in main history. @default 1000 */\n\tmaxHistorySize?: number;\n\t/** Maximum number of branches to maintain. @default 50 */\n\tmaxBranches?: number;\n\t/** Maximum size of each branch. @default 100 */\n\tmaxBranchSize?: number;\n\tlogger?: Logger;\n\tpersistence?: PersistenceBackend | null;\n\tmetrics?: IMetrics;\n\t/** Maximum number of thoughts to buffer before flushing. @default 100 */\n\tpersistenceBufferSize?: number;\n\t/** Periodic flush interval in ms. @default 1000 */\n\tpersistenceFlushInterval?: number;\n\t/** Max retries for failed persistence flushes. @default 3 */\n\tpersistenceMaxRetries?: number;\n\teventEmitter?: PersistenceEventEmitter;\n\tedgeStore?: IEdgeStore;\n\tsummaryStore?: ISummaryStore;\n\t/** Whether to emit DAG edges (gated independently of edgeStore). @default false */\n\tdagEdges?: boolean;\n}\n\n/**\n * Manages thought history and branching for sequential thinking.\n *\n * Owns the per-session `Map<string, SessionState>`. Delegates DAG edge emission,\n * buffered persistence, and session TTL/LRU eviction to focused collaborators while\n * preserving test-coupled private member names (`_flushTimer`, `_startFlushTimer`,\n * `_flushBuffer`, `_sessions`).\n */\nexport class HistoryManager implements IHistoryManager {\n\tprivate static readonly DEFAULT_SESSION = '__global__';\n\tprivate static readonly SESSION_TTL_MS = 30 * 60 * 1000;\n\tprivate static readonly MAX_SESSIONS = 100;\n\tprivate _sessions: Map<string, SessionState> = new Map();\n\tprivate _maxHistorySize: number;\n\tprivate _maxBranches: number;\n\tprivate _maxBranchSize: number;\n\tprivate _logger: Logger;\n\tprivate _persistence: PersistenceBackend | null;\n\tprivate _persistenceEnabled: boolean;\n\tprivate _metrics?: IMetrics;\n\n\tprivate _edgeStore?: IEdgeStore;\n\tprivate _summaryStore?: ISummaryStore;\n\tprivate _dagEdges: boolean;\n\n\tprivate _eventEmitter: PersistenceEventEmitter | null;\n\n\tprivate readonly _edgeEmitter: EdgeEmitter;\n\tprivate _persistenceBuffer: PersistenceBuffer<SessionState> | null;\n\tprivate readonly _sessionManager: SessionManager<SessionState>;\n\n\tconstructor(config: HistoryManagerConfig = {}) {\n\t\tthis._logger = config.logger ?? new NullLogger();\n\t\tconst requestedMaxSize = config.maxHistorySize ?? 10000;\n\t\tthis._maxHistorySize = Math.min(requestedMaxSize, ABSOLUTE_MAX_HISTORY_SIZE);\n\t\tif (requestedMaxSize > ABSOLUTE_MAX_HISTORY_SIZE) {\n\t\t\tthis._logger.warn('maxHistorySize exceeds absolute maximum, capped', {\n\t\t\t\trequested: requestedMaxSize,\n\t\t\t\tapplied: ABSOLUTE_MAX_HISTORY_SIZE,\n\t\t\t});\n\t\t}\n\t\tthis._maxBranches = config.maxBranches || 50;\n\t\tthis._maxBranchSize = config.maxBranchSize || 100;\n\t\tthis._persistence = config.persistence ?? null;\n\t\tthis._persistenceEnabled = this._persistence !== null;\n\t\tthis._metrics = config.metrics;\n\t\tthis._eventEmitter = config.eventEmitter ?? null;\n\t\tthis._edgeStore = config.edgeStore;\n\t\tthis._summaryStore = config.summaryStore;\n\t\tthis._dagEdges = config.dagEdges ?? true;\n\n\t\t// Wire delegates\n\t\tthis._edgeEmitter = new EdgeEmitter({\n\t\t\tedgeStore: this._edgeStore,\n\t\t\tdagEdges: this._dagEdges,\n\t\t\tdefaultSessionId: HistoryManager.DEFAULT_SESSION,\n\t\t\tlogger: this._logger,\n\t\t});\n\n\t\tthis._sessionManager = new SessionManager<SessionState>({\n\t\t\tdefaultSessionId: HistoryManager.DEFAULT_SESSION,\n\t\t\tsessionTtlMs: HistoryManager.SESSION_TTL_MS,\n\t\t\tcleanupIntervalMs: 5 * 60 * 1000,\n\t\t\tgetMaxSessions: () => HistoryManager.MAX_SESSIONS,\n\t\t\tlogger: this._logger,\n\t\t});\n\n\t\tthis._persistenceBuffer = null;\n\t\tif (this._persistenceEnabled && this._persistence) {\n\t\t\tthis._persistenceBuffer = new PersistenceBuffer<SessionState>({\n\t\t\t\tpersistence: this._persistence,\n\t\t\t\tbufferSize: config.persistenceBufferSize ?? 100,\n\t\t\t\tflushInterval: config.persistenceFlushInterval ?? 1000,\n\t\t\t\tmaxRetries: config.persistenceMaxRetries ?? 3,\n\t\t\t\tdefaultSessionId: HistoryManager.DEFAULT_SESSION,\n\t\t\t\tgetSessions: () => this._sessions,\n\t\t\t\tgetDefaultSession: () => this._getSession(),\n\t\t\t\tedgeStore: this._edgeStore,\n\t\t\t\teventEmitter: this._eventEmitter,\n\t\t\t\tlogger: this._logger,\n\t\t\t});\n\t\t\tthis._startFlushTimer();\n\t\t}\n\n\t\tthis._sessionManager.startCleanupTimer(this._sessions);\n\t}\n\n\t// Test-coupled accessors: these private member names must remain reachable\n\t// via `manager as unknown as { _flushTimer; _startFlushTimer }`.\n\tprivate get _flushTimer(): ReturnType<typeof setInterval> | null {\n\t\treturn this._persistenceBuffer?.timer ?? null;\n\t}\n\n\tprivate _startFlushTimer(): void {\n\t\tthis._persistenceBuffer?.startFlushTimer();\n\t}\n\n\tprivate _stopFlushTimer(): void {\n\t\tif (this._flushTimer === null) return;\n\t\tthis._persistenceBuffer?.stopFlushTimer();\n\t}\n\n\t/** @internal Public for backward-compatible test coupling. */\n\tpublic async _flushBuffer(): Promise<void> {\n\t\tawait this._persistenceBuffer?.flush();\n\t}\n\n\t/** EdgeStore instance, if configured. Used by ThoughtProcessor for StrategyContext. */\n\tpublic getEdgeStore(): IEdgeStore | undefined {\n\t\treturn this._edgeStore;\n\t}\n\n\tprivate log(message: string, meta?: Record<string, unknown>): void {\n\t\tthis._logger.info(message, meta);\n\t}\n\n\t/** Gets or creates session state; updates lastAccessedAt. */\n\tprivate _getSession(sessionId?: string): SessionState {\n\t\tconst key = sessionId ?? HistoryManager.DEFAULT_SESSION;\n\t\tlet session = this._sessions.get(key);\n\t\tif (!session) {\n\t\t\tsession = {\n\t\t\t\tthought_history: [],\n\t\t\t\tbranches: {},\n\t\t\t\tavailableMcpTools: undefined,\n\t\t\t\tavailableSkills: undefined,\n\t\t\t\twriteBuffer: [],\n\t\t\t\tlastAccessedAt: Date.now(),\n\t\t\t\tregisteredBranches: new Set<string>(),\n\t\t\t};\n\t\t\tthis._sessions.set(key, session);\n\t\t\tthis._sessionManager.evictExcessSessions(this._sessions);\n\t\t}\n\t\tsession.lastAccessedAt = Date.now();\n\t\treturn session;\n\t}\n\n\t/**\n\t * Adds a thought to the history. Routes per-session, applies retraction for backtrack,\n\t * caches tools/skills, trims, branches, emits DAG edges, and buffers for persistence.\n\t */\n\tpublic addThought(thought: ThoughtData): void {\n\t\tconst session = this._getSession(thought.session_id);\n\t\tthis._metrics?.counter(\n\t\t\t'thought_requests_total',\n\t\t\t1,\n\t\t\t{},\n\t\t\t'Total thought requests added to history'\n\t\t);\n\n\t\tsession.thought_history.push(thought);\n\n\t\t// Logical retraction: when a backtrack thought is added, mark its target\n\t\t// as retracted (append-only — target remains in history).\n\t\tif (thought.thought_type === 'backtrack' && thought.backtrack_target !== undefined) {\n\t\t\tthis._applyRetraction(session, thought.backtrack_target);\n\t\t}\n\n\t\t// Cache available_mcp_tools/available_skills for cross-call persistence\n\t\tif (thought.available_mcp_tools) {\n\t\t\tsession.availableMcpTools = thought.available_mcp_tools;\n\t\t}\n\t\tif (thought.available_skills) {\n\t\t\tsession.availableSkills = thought.available_skills;\n\t\t}\n\n\t\tif (session.thought_history.length > this._maxHistorySize) {\n\t\t\tsession.thought_history = session.thought_history.slice(-this._maxHistorySize);\n\t\t\tthis.log(`History trimmed to ${this._maxHistorySize} items`, {\n\t\t\t\tmaxSize: this._maxHistorySize,\n\t\t\t});\n\t\t}\n\n\t\tif (thought.branch_from_thought && thought.branch_id) {\n\t\t\tthis._addToSessionBranch(session, thought.branch_id, thought);\n\t\t}\n\n\t\t// Track merge operations for analytics\n\t\tif (thought.merge_from_thoughts?.length || thought.merge_branch_ids?.length) {\n\t\t\tthis._metrics?.counter(\n\t\t\t\t'thought_merge_operations_total',\n\t\t\t\t1,\n\t\t\t\t{},\n\t\t\t\t'Total merge operations (graph topology)'\n\t\t\t);\n\t\t}\n\n\t\t// Emit DAG edges (no-op unless edgeStore + dagEdges flag both enabled)\n\t\tthis._edgeEmitter.emitEdgesForThought(session, thought);\n\n\t\t// Buffer thought for persistence (no-op when persistence disabled)\n\t\tif (this._persistenceBuffer) {\n\t\t\tthis._persistenceBuffer.bufferThought(session, thought);\n\t\t}\n\t}\n\n\t/** Marks the thought as retracted within the session (append-only). */\n\tprivate _applyRetraction(session: SessionState, targetNumber: number): void {\n\t\tfor (const t of session.thought_history) {\n\t\t\tif (t.thought_number === targetNumber) {\n\t\t\t\tt.retracted = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tfor (const branchThoughts of Object.values(session.branches)) {\n\t\t\tfor (const t of branchThoughts) {\n\t\t\t\tif (t.thought_number === targetNumber) {\n\t\t\t\t\tt.retracted = true;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate _addToSessionBranch(session: SessionState, branchId: string, thought: ThoughtData): void {\n\t\tif (!session.branches[branchId]) {\n\t\t\tsession.branches[branchId] = [];\n\t\t}\n\t\tthis._trimSessionBranchSize(session, branchId);\n\t\tsession.branches[branchId].push(thought);\n\n\t\tif (Object.keys(session.branches).length > this._maxBranches) {\n\t\t\tthis._cleanupSessionBranches(session);\n\t\t}\n\n\t\t// Persist branch to backend if enabled\n\t\tif (this._persistenceEnabled && this._persistence) {\n\t\t\tthis._persistence.saveBranch(branchId, session.branches[branchId]).catch((err) => {\n\t\t\t\tthis.log('Failed to persist branch', {\n\t\t\t\t\tbranchId,\n\t\t\t\t\terror: getErrorMessage(err),\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate _cleanupSessionBranches(session: SessionState): void {\n\t\tconst branchCount = Object.keys(session.branches).length;\n\t\tif (branchCount > this._maxBranches) {\n\t\t\tconst branchesToRemove = Object.keys(session.branches).slice(\n\t\t\t\t0,\n\t\t\t\tbranchCount - this._maxBranches\n\t\t\t);\n\t\t\tfor (const branchId of branchesToRemove) {\n\t\t\t\tdelete session.branches[branchId];\n\t\t\t\tthis.log(`Removed old branch: ${branchId}`, { branchId });\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate _trimSessionBranchSize(session: SessionState, branchId: string): void {\n\t\tif ((session.branches[branchId] ?? []).length > this._maxBranchSize) {\n\t\t\tconst removed = session.branches[branchId]!.length - this._maxBranchSize;\n\t\t\tsession.branches[branchId] = session.branches[branchId]!.slice(-this._maxBranchSize);\n\t\t\tthis.log(`Trimmed branch '${branchId}': removed ${removed} old thoughts`, {\n\t\t\t\tbranchId,\n\t\t\t\tremoved,\n\t\t\t});\n\t\t}\n\t}\n\n\tpublic getHistory(sessionId?: string): ThoughtData[] {\n\t\treturn this._getSession(sessionId).thought_history;\n\t}\n\n\t/**\n\t * Returns history with optional sliding-window dehydration. Non-mutating: when\n\t * `dagEdges` is off OR no `ISummaryStore` is configured, returns same as getHistory.\n\t */\n\tpublic getHistoryHydrated(\n\t\tsessionId?: string,\n\t\topts?: DehydrationOptions\n\t): HydratedEntry[] {\n\t\tconst history = this.getHistory(sessionId);\n\t\tif (!this._dagEdges || !this._summaryStore) {\n\t\t\treturn history.slice();\n\t\t}\n\t\tconst sid = sessionId ?? HistoryManager.DEFAULT_SESSION;\n\t\tconst policy = new DehydrationPolicy(this._summaryStore);\n\t\treturn policy.apply(history, sid, opts);\n\t}\n\n\tpublic getHistoryLength(sessionId?: string): number {\n\t\treturn this._getSession(sessionId).thought_history.length;\n\t}\n\n\tpublic getBranches(sessionId?: string): Record<string, ThoughtData[]> {\n\t\treturn this._getSession(sessionId).branches;\n\t}\n\n\tpublic getBranchIds(sessionId?: string): string[] {\n\t\tconst session = this._getSession(sessionId);\n\t\tconst ids = new Set<string>(Object.keys(session.branches));\n\t\tfor (const id of session.registeredBranches) ids.add(id);\n\t\treturn Array.from(ids);\n\t}\n\n\t/** @throws {ValidationError} If branchId is empty or already exists. */\n\tpublic registerBranch(sessionId: string | undefined, branchId: string): void {\n\t\tif (typeof branchId !== 'string' || branchId.length === 0) {\n\t\t\tthrow new ValidationError('branch_id', 'branch_id must be a non-empty string');\n\t\t}\n\t\tconst session = this._getSession(sessionId);\n\t\tif (branchId in session.branches || session.registeredBranches.has(branchId)) {\n\t\t\tthrow new ValidationError('branch_id', `Branch already exists: ${branchId}`);\n\t\t}\n\t\tsession.registeredBranches.add(branchId);\n\t\tthis.log('Registered branch', { branchId, sessionId: sessionId ?? null });\n\t}\n\n\tpublic branchExists(sessionId: string | undefined, branchId: string): boolean {\n\t\tconst session = this._getSession(sessionId);\n\t\treturn branchId in session.branches || session.registeredBranches.has(branchId);\n\t}\n\n\tpublic getAvailableMcpTools(sessionId?: string): string[] | undefined {\n\t\treturn this._getSession(sessionId).availableMcpTools;\n\t}\n\n\tpublic getAvailableSkills(sessionId?: string): string[] | undefined {\n\t\treturn this._getSession(sessionId).availableSkills;\n\t}\n\n\tpublic getBranch(branchId: string, sessionId?: string): ThoughtData[] | undefined {\n\t\treturn this._getSession(sessionId).branches[branchId];\n\t}\n\n\t/** Clears history and branches. If sessionId provided, clears only that session. */\n\tpublic clear(sessionId?: string): void {\n\t\t// Clear edges from EdgeStore (before session map mutation so keys are still available)\n\t\tif (this._edgeStore) {\n\t\t\tif (sessionId !== undefined) {\n\t\t\t\tthis._edgeStore.clearSession(sessionId);\n\t\t\t} else {\n\t\t\t\tfor (const sid of this._sessions.keys()) {\n\t\t\t\t\tthis._edgeStore.clearSession(sid);\n\t\t\t\t}\n\t\t\t\t// Also clear the default session in case no session entries exist yet\n\t\t\t\tthis._edgeStore.clearSession(HistoryManager.DEFAULT_SESSION);\n\t\t\t}\n\t\t}\n\n\t\tif (sessionId !== undefined) {\n\t\t\tthis._sessions.delete(sessionId);\n\t\t\tthis.log('Session cleared', { sessionId });\n\t\t} else {\n\t\t\tthis._sessions.clear();\n\t\t\tthis.log('History cleared (all sessions)');\n\t\t}\n\n\t\t// Clear persisted data if enabled\n\t\tif (this._persistenceEnabled && this._persistence) {\n\t\t\tthis._persistence.clear().catch((err) => {\n\t\t\t\tthis.log('Failed to clear persisted data', {\n\t\t\t\t\terror: getErrorMessage(err),\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\t}\n\n\tpublic clearSession(sessionId: string): void {\n\t\tthis.clear(sessionId);\n\t}\n\n\tpublic getSessionIds(): string[] {\n\t\treturn Array.from(this._sessions.keys());\n\t}\n\n\tpublic getSessionCount(): number {\n\t\treturn this._sessions.size;\n\t}\n\n\t/** Loads history from persistence into the global session. Call at init. */\n\tpublic async loadFromPersistence(): Promise<void> {\n\t\tif (!this._persistenceEnabled || !this._persistence) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst isHealthy = await this._persistence.healthy();\n\t\t\tif (!isHealthy) {\n\t\t\t\tthis.log('Persistence backend not healthy, skipping load');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst globalSession = this._getSession();\n\n\t\t\tconst history = await this._persistence.loadHistory();\n\t\t\tif (history.length > 0) {\n\t\t\t\tglobalSession.thought_history = history.slice(-this._maxHistorySize);\n\t\t\t\tthis.log(`Loaded ${globalSession.thought_history.length} thoughts from persistence`);\n\t\t\t}\n\n\t\t\tconst branchIds = await this._persistence.listBranches();\n\t\t\tfor (const branchId of branchIds) {\n\t\t\t\tconst branchData = await this._persistence.loadBranch(branchId);\n\t\t\t\tif (branchData) {\n\t\t\t\t\tglobalSession.branches[branchId] = branchData.slice(-this._maxBranchSize);\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.log(`Loaded ${Object.keys(globalSession.branches).length} branches from persistence`);\n\n\t\t\t// Load edges if EdgeStore is configured — restore for ALL persisted sessions\n\t\t\tif (this._edgeStore) {\n\t\t\t\ttry {\n\t\t\t\t\tconst edgeSessions = await this._persistence.listEdgeSessions();\n\t\t\t\t\tlet totalEdges = 0;\n\t\t\t\t\tfor (const sessionId of edgeSessions) {\n\t\t\t\t\t\tconst edges = await this._persistence.loadEdges(sessionId);\n\t\t\t\t\t\tfor (const edge of edges) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tthis._edgeStore.addEdge(edge);\n\t\t\t\t\t\t\t\ttotalEdges++;\n\t\t\t\t\t\t\t} catch (edgeErr) {\n\t\t\t\t\t\t\t\tthis.log('Failed to restore edge', {\n\t\t\t\t\t\t\t\t\tedgeId: edge.id,\n\t\t\t\t\t\t\t\t\tsessionId,\n\t\t\t\t\t\t\t\t\terror: getErrorMessage(edgeErr),\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthis.log(\n\t\t\t\t\t\t`Loaded ${totalEdges} edges across ${edgeSessions.length} sessions from persistence`,\n\t\t\t\t\t);\n\t\t\t\t} catch (edgeError) {\n\t\t\t\t\tthis.log('Failed to load edges from persistence', {\n\t\t\t\t\t\terror: getErrorMessage(edgeError),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthis.log('Failed to load from persistence', {\n\t\t\t\terror: getErrorMessage(error),\n\t\t\t});\n\t\t}\n\t}\n\n\tpublic isPersistenceEnabled(): boolean {\n\t\treturn this._persistenceEnabled;\n\t}\n\n\tpublic getPersistenceBackend(): PersistenceBackend | null {\n\t\treturn this._persistence;\n\t}\n\n\t/** Sets the event emitter for persistence error events (post-construction wiring). */\n\tpublic setEventEmitter(emitter: PersistenceEventEmitter): void {\n\t\tthis._eventEmitter = emitter;\n\t\tthis._persistenceBuffer?.setEventEmitter(emitter);\n\t}\n\n\t/** Stops timers and flushes any remaining buffered writes. */\n\tpublic async shutdown(): Promise<void> {\n\t\tthis._stopFlushTimer();\n\t\tthis._sessionManager.stopCleanupTimer();\n\t\tawait this._flushBuffer();\n\t}\n\n\t/** Total write buffer length across all sessions. */\n\tpublic getWriteBufferLength(): number {\n\t\tlet total = 0;\n\t\tfor (const session of this._sessions.values()) {\n\t\t\ttotal += session.writeBuffer.length;\n\t\t}\n\t\treturn total;\n\t}\n}\n"],"names":["ABSOLUTE_MAX_HISTORY_SIZE","HistoryManager","Map","config","NullLogger","requestedMaxSize","Math","EdgeEmitter","SessionManager","PersistenceBuffer","message","meta","sessionId","key","session","undefined","Date","Set","thought","targetNumber","t","branchThoughts","Object","branchId","err","getErrorMessage","branchCount","branchesToRemove","removed","opts","history","sid","policy","DehydrationPolicy","ids","id","Array","ValidationError","isHealthy","globalSession","branchIds","branchData","edgeSessions","totalEdges","edges","edge","edgeErr","edgeError","error","emitter","total"],"mappings":"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"}
@@ -6,6 +6,7 @@
6
6
  *
7
7
  * @module IHistoryManager
8
8
  */
9
+ import type { IEdgeStore } from '../contracts/interfaces.js';
9
10
  import type { ThoughtData } from './thought.js';
10
11
  /**
11
12
  * Interface for history and branch management.
@@ -113,5 +114,14 @@ export interface IHistoryManager {
113
114
  * @returns true if the branch exists or has been registered
114
115
  */
115
116
  branchExists(sessionId: string | undefined, branchId: string): boolean;
117
+ /**
118
+ * Access the EdgeStore, if configured.
119
+ *
120
+ * Returns undefined when DAG edges are not enabled.
121
+ * Used by ThoughtProcessor to build StrategyContext.
122
+ *
123
+ * @returns The edge store, or undefined if not configured
124
+ */
125
+ getEdgeStore(): IEdgeStore | undefined;
116
126
  }
117
127
  //# 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;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"}
1
+ {"version":3,"file":"IHistoryManager.d.ts","sourceRoot":"","sources":["../../src/core/IHistoryManager.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAC7D,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;IAEvE;;;;;;;OAOG;IACH,YAAY,IAAI,UAAU,GAAG,SAAS,CAAC;CACvC"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Interface for thought and step recommendation formatting.
3
+ *
4
+ * This module provides the `IThoughtFormatter` interface which defines the
5
+ * contract for formatter implementations. This allows for decoupling and
6
+ * testability of presentation logic.
7
+ *
8
+ * @module IThoughtFormatter
9
+ */
10
+ import type { StepRecommendation } from './step.js';
11
+ import type { ThoughtData } from './thought.js';
12
+ /**
13
+ * Interface for formatting thought data and step recommendations.
14
+ *
15
+ * This interface defines the contract for formatter implementations,
16
+ * allowing for decoupling between components like ThoughtProcessor and
17
+ * concrete implementations. It supports dependency injection and mocking
18
+ * for testing purposes.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * class MockFormatter implements IThoughtFormatter {
23
+ * formatRecommendation(_step: StepRecommendation): string { return ''; }
24
+ * formatThought(_thought: ThoughtData): string { return ''; }
25
+ * }
26
+ * ```
27
+ */
28
+ export interface IThoughtFormatter {
29
+ /**
30
+ * Formats a step recommendation into a readable string.
31
+ *
32
+ * Creates a structured display of the step description, recommended tools,
33
+ * recommended skills, expected outcome, and conditions for the next step.
34
+ *
35
+ * @param step - The step recommendation to format
36
+ * @returns A formatted string representation of the recommendation
37
+ */
38
+ formatRecommendation(step: StepRecommendation): string;
39
+ /**
40
+ * Formats a thought into a clean, simple display.
41
+ *
42
+ * Creates a clean output containing the thought content with an appropriate
43
+ * header indicating the thought type. Priority order for icon selection:
44
+ * `is_revision` > `branch_from_thought` > `thought_type`.
45
+ *
46
+ * @param thoughtData - The thought data to format
47
+ * @returns A formatted string with thought and recommendations
48
+ */
49
+ formatThought(thoughtData: ThoughtData): string;
50
+ }
51
+ //# sourceMappingURL=IThoughtFormatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IThoughtFormatter.d.ts","sourceRoot":"","sources":["../../src/core/IThoughtFormatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,iBAAiB;IACjC;;;;;;;;OAQG;IACH,oBAAoB,CAAC,IAAI,EAAE,kBAAkB,GAAG,MAAM,CAAC;IAEvD;;;;;;;;;OASG;IACH,aAAa,CAAC,WAAW,EAAE,WAAW,GAAG,MAAM,CAAC;CAChD"}
@@ -0,0 +1 @@
1
+ export { };
@@ -1 +1 @@
1
- {"version":3,"file":"InputNormalizer.d.ts","sourceRoot":"","sources":["../../src/core/InputNormalizer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAahD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAuBzD;AAUD;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CASzD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQvE;AAsHD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAsD7E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAsD1D"}
1
+ {"version":3,"file":"InputNormalizer.d.ts","sourceRoot":"","sources":["../../src/core/InputNormalizer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAchD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAuBzD;AAUD;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CASzD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQvE;AAsHD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAsD7E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAsD1D"}
@@ -1,6 +1,7 @@
1
1
  import { ValidationError } from "../errors.js";
2
2
  import { sanitizeString } from "../sanitize.js";
3
- import { MAX_SESSION_ID_LENGTH, SESSION_ID_PATTERN, generateUlid } from "./ids.js";
3
+ import { MAX_SESSION_ID_LENGTH, SESSION_ID_PATTERN } from "./ids.js";
4
+ import { asSessionId, generateThoughtId } from "../contracts/ids.js";
4
5
  const DEFAULT_RECOMMENDATION_CONFIDENCE = 0.5;
5
6
  const DEFAULT_RECOMMENDATION_PRIORITY = 999;
6
7
  const DEFAULT_RECOMMENDATION_RATIONALE = '';
@@ -78,9 +79,9 @@ function normalizeInput(input) {
78
79
  if ('string' == typeof normalized.session_id) {
79
80
  const sanitized = sanitizeSessionId(normalized.session_id);
80
81
  if (void 0 === sanitized) delete normalized.session_id;
81
- else normalized.session_id = sanitized;
82
+ else normalized.session_id = asSessionId(sanitized);
82
83
  }
83
- if (!normalized.id || 'string' != typeof normalized.id) normalized.id = generateUlid();
84
+ if (!normalized.id || 'string' != typeof normalized.id) normalized.id = generateThoughtId();
84
85
  normalizeReasoningFields(normalized);
85
86
  const sanitized = sanitizeRecursive(normalized);
86
87
  return sanitized;
@@ -1 +1 @@
1
- {"version":3,"file":"core/InputNormalizer.js","sources":["../../src/core/InputNormalizer.ts"],"sourcesContent":["/**\n * Input normalization for common LLM field name mistakes.\n *\n * This module provides normalization logic to handle common mistakes\n * that LLMs make when generating field names, such as using singular\n * instead of plural forms (e.g., `recommended_tool` vs `recommended_tools`).\n *\n * It also fills in sensible defaults for missing fields in `previous_steps`,\n * which LLMs naturally provide as partial/skeletal data (historical context).\n *\n * @module processor\n */\n\nimport { ValidationError } from '../errors.js';\nimport { sanitizeString } from '../sanitize.js';\nimport type { ThoughtData } from './thought.js';\nimport { generateUlid, SESSION_ID_PATTERN, MAX_SESSION_ID_LENGTH } from './ids.js';\n\n/**\n * Default values for missing partial recommendation fields.\n *\n * Shared between tool and skill recommendations (identical defaults).\n */\nconst DEFAULT_RECOMMENDATION_CONFIDENCE = 0.5;\nconst DEFAULT_RECOMMENDATION_PRIORITY = 999;\nconst DEFAULT_RECOMMENDATION_RATIONALE = '';\nconst DEFAULT_STEP_OUTCOME = '';\n\n/**\n * Recursively sanitizes all string values within an unknown structure.\n * Walks into plain objects and arrays to reach deeply nested strings.\n * Non-plain objects (Date, RegExp, etc.) are returned as-is.\n *\n * @param value - The value to sanitize recursively\n * @returns The sanitized value with all nested strings cleaned\n *\n * @example\n * ```typescript\n * sanitizeRecursive('<script>x</script>'); // 'x'\n * sanitizeRecursive({ a: { b: '<iframe>y' } }); // { a: { b: 'y' } }\n * sanitizeRecursive(['a\\x00b', 42]); // ['ab', 42]\n * ```\n */\nexport function sanitizeRecursive(value: unknown): unknown {\n\tif (value === null || value === undefined) {\n\t\treturn value;\n\t}\n\tif (typeof value === 'string') {\n\t\treturn sanitizeString(value);\n\t}\n\tif (Array.isArray(value)) {\n\t\treturn value.map((item) => sanitizeRecursive(item));\n\t}\n\tif (typeof value === 'object') {\n\t\t// Only recurse into plain objects — skip Date, RegExp, Map, Set, etc.\n\t\tconst proto = Object.getPrototypeOf(value);\n\t\tif (proto !== Object.prototype && proto !== null) {\n\t\t\treturn value;\n\t\t}\n\t\tconst result: Record<string, unknown> = {};\n\t\tfor (const [key, val] of Object.entries(value as Record<string, unknown>)) {\n\t\t\tresult[key] = sanitizeRecursive(val);\n\t\t}\n\t\treturn result;\n\t}\n\treturn value;\n}\n\n/**\n * Valid branch ID pattern: alphanumeric, hyphens, underscores only.\n * Prevents path traversal attacks by rejecting special characters like / . \\ etc.\n */\nconst BRANCH_ID_PATTERN = /^[a-zA-Z0-9_-]{1,64}$/;\n\n\n\n/**\n * Sanitizes and validates a branch ID to prevent path traversal attacks.\n *\n * @param branchId - The branch ID to sanitize\n * @returns The sanitized branch ID\n * @throws ValidationError if the branch ID contains invalid characters\n *\n * @example\n * ```typescript\n * sanitizeBranchId('my-branch_01'); // 'my-branch_01'\n * sanitizeBranchId('../etc/passwd'); // throws ValidationError\n * ```\n */\nexport function sanitizeBranchId(branchId: string): string {\n\t// Validate format\n\tif (!BRANCH_ID_PATTERN.test(branchId)) {\n\t\tthrow new ValidationError(\n\t\t\t'branch_id',\n\t\t\t'Invalid format - must be 1-64 alphanumeric characters, hyphens, or underscores only'\n\t\t);\n\t}\n\treturn branchId;\n}\n\n/**\n * Sanitizes and validates a session ID.\n *\n * @param sessionId - The session ID to sanitize\n * @returns The sanitized session ID, or undefined if invalid after sanitization\n *\n * @example\n * ```typescript\n * sanitizeSessionId('analysis-task-42'); // 'analysis-task-42'\n * sanitizeSessionId('bad session!'); // undefined (stripped)\n * ```\n */\nexport function sanitizeSessionId(sessionId: string): string | undefined {\n\t// First sanitize control characters\n\tconst cleaned = sanitizeString(sessionId);\n\t// Validate format after sanitization\n\tif (cleaned.length > MAX_SESSION_ID_LENGTH || !SESSION_ID_PATTERN.test(cleaned)) {\n\t\treturn undefined;\n\t}\n\treturn cleaned;\n}\n\n\n/**\n * Normalizes a recommendation object (tool or skill) with default values.\n *\n * Fills in sensible defaults for missing optional fields:\n * - `confidence`: 0.5\n * - `priority`: 999\n * - `rationale`: empty string\n *\n * @param rec - The recommendation object to normalize\n * @returns The normalized recommendation with defaults filled in\n *\n * @example\n * ```typescript\n * const input = { tool_name: 'Read', rationale: 'Read the file' };\n * const normalized = normalizeRecommendation(input);\n * // { tool_name: 'Read', rationale: 'Read the file', confidence: 0.5, priority: 999 }\n * ```\n */\nfunction normalizeRecommendation(rec: Record<string, unknown>): Record<string, unknown> {\n\tconst normalized: Record<string, unknown> = { ...rec };\n\n\t// Fill in default confidence if missing\n\tif (!('confidence' in normalized) || normalized.confidence === undefined) {\n\t\tnormalized.confidence = DEFAULT_RECOMMENDATION_CONFIDENCE;\n\t}\n\n\t// Fill in default priority if missing\n\tif (!('priority' in normalized) || normalized.priority === undefined) {\n\t\tnormalized.priority = DEFAULT_RECOMMENDATION_PRIORITY;\n\t}\n\n\t// Fill in default rationale if missing\n\tif (!('rationale' in normalized) || normalized.rationale === undefined) {\n\t\tnormalized.rationale = DEFAULT_RECOMMENDATION_RATIONALE;\n\t}\n\n\treturn normalized;\n}\n/**\n * Normalizes step recommendation objects.\n *\n * Handles common field name mistakes:\n * - `recommended_tool` (singular) → `recommended_tools` (plural)\n * - `recommended_skill` (singular) → `recommended_skills` (plural)\n *\n * Also normalizes tool recommendations within the step to fill in defaults.\n *\n * @param step - The step recommendation to normalize\n * @param lenient - Whether to use lenient mode (fill in defaults for missing fields)\n * @returns The normalized step recommendation\n *\n * @example\n * ```typescript\n * // Strict mode (for current_step)\n * const input = {\n * step_description: 'Analyze data',\n * recommended_tool: [{ tool_name: 'Read', confidence: 0.9, rationale: 'test', priority: 1 }],\n * expected_outcome: 'Data analyzed'\n * };\n * const normalized = normalizeStepRecommendation(input, false);\n * // normalized.recommended_tools exists (plural form)\n *\n * // Lenient mode (for previous_steps)\n * const partialInput = {\n * step_description: 'Read file',\n * recommended_tools: [{ tool_name: 'Read', rationale: 'Read file' }]\n * };\n * const normalized = normalizeStepRecommendation(partialInput, true);\n * // confidence: 0.5, priority: 999, expected_outcome: '' filled in\n * ```\n */\nfunction normalizeStepRecommendation(\n\tstep: Record<string, unknown>,\n\tlenient: boolean\n): Record<string, unknown> {\n\tconst normalized: Record<string, unknown> = { ...step };\n\n\t// Transform `recommended_tool` (singular) → `recommended_tools` (plural)\n\tif ('recommended_tool' in normalized && !('recommended_tools' in normalized)) {\n\t\tnormalized.recommended_tools = normalized.recommended_tool;\n\t\tdelete normalized.recommended_tool;\n\t}\n\n\t// Transform `recommended_skill` (singular) → `recommended_skills` (plural)\n\tif ('recommended_skill' in normalized && !('recommended_skills' in normalized)) {\n\t\tnormalized.recommended_skills = normalized.recommended_skill;\n\t\tdelete normalized.recommended_skill;\n\t}\n\n\t// Normalize recommended_tools array if present\n\tif (Array.isArray(normalized.recommended_tools)) {\n\t\tnormalized.recommended_tools = normalized.recommended_tools.map((tool) =>\n\t\t\ttypeof tool === 'object' && tool !== null\n\t\t\t\t? normalizeRecommendation(tool as Record<string, unknown>)\n\t\t\t\t: tool\n\t\t);\n\t}\n\n\t// Normalize recommended_skills array if present\n\tif (Array.isArray(normalized.recommended_skills)) {\n\t\tnormalized.recommended_skills = normalized.recommended_skills.map((skill) =>\n\t\t\ttypeof skill === 'object' && skill !== null\n\t\t\t\t? normalizeRecommendation(skill as Record<string, unknown>)\n\t\t\t\t: skill\n\t\t);\n\t}\n\t// In lenient mode, fill in default expected_outcome if missing\n\tif (lenient && !('expected_outcome' in normalized)) {\n\t\tnormalized.expected_outcome = DEFAULT_STEP_OUTCOME;\n\t}\n\n\treturn normalized;\n}\n\n\n/**\n * Normalizes reasoning-specific fields on a thought input object.\n * Always applies reasoning normalization — reasoning is the default pipeline.\n * Applies the following normalization rules:\n * - Defaults `thought_type` to `'regular'` if not provided\n * - Clamps `quality_score` to [0, 1] range\n * - Clamps `confidence` to [0, 1] range\n * - Sanitizes `hypothesis_id` using `sanitizeBranchId` pattern\n * - Filters `synthesis_sources` to positive integers only\n * - Filters `merge_from_thoughts` to positive integers only\n * - Sanitizes each entry in `merge_branch_ids`\n * - Defaults `reasoning_depth` to `'moderate'` for hypothesis/verification types\n *\n * @param input - The mutable normalized input object to apply reasoning defaults to\n *\n * @example\n * ```typescript\n * const input: Record<string, unknown> = { thought_type: 'hypothesis', quality_score: 1.5 };\n * normalizeReasoningFields(input);\n * // input.quality_score === 1, input.reasoning_depth === 'moderate'\n * ```\n */\nexport function normalizeReasoningFields(input: Record<string, unknown>): void {\n\t// Always apply reasoning field normalization — reasoning is the default pipeline\n\t// Default thought_type to 'regular'\n\n\tif (!('thought_type' in input) || input.thought_type === undefined) {\n\t\tinput.thought_type = 'regular';\n\t}\n\n\t// Clamp quality_score to [0, 1]\n\tif (typeof input.quality_score === 'number') {\n\t\tinput.quality_score = Math.max(0, Math.min(1, input.quality_score));\n\t}\n\n\t// Clamp confidence to [0, 1]\n\tif (typeof input.confidence === 'number') {\n\t\tinput.confidence = Math.max(0, Math.min(1, input.confidence));\n\t}\n\n\t// Sanitize hypothesis_id (same rules as branch_id)\n\tif (typeof input.hypothesis_id === 'string') {\n\t\tinput.hypothesis_id = sanitizeBranchId(input.hypothesis_id);\n\t}\n\n\t// Filter synthesis_sources to positive integers only\n\tif (Array.isArray(input.synthesis_sources)) {\n\t\tinput.synthesis_sources = input.synthesis_sources.filter(\n\t\t\t(v: unknown) => typeof v === 'number' && Number.isInteger(v) && v > 0\n\t\t);\n\t}\n\n\t// Filter merge_from_thoughts to positive integers only\n\tif (Array.isArray(input.merge_from_thoughts)) {\n\t\tinput.merge_from_thoughts = input.merge_from_thoughts.filter(\n\t\t\t(v: unknown) => typeof v === 'number' && Number.isInteger(v) && v > 0\n\t\t);\n\t}\n\n\t// Sanitize merge_branch_ids entries\n\tif (Array.isArray(input.merge_branch_ids)) {\n\t\tinput.merge_branch_ids = input.merge_branch_ids.map((id: unknown) => {\n\t\t\tif (typeof id === 'string') {\n\t\t\t\treturn sanitizeBranchId(id);\n\t\t\t}\n\t\t\treturn id;\n\t\t});\n\t}\n\n\t// Default reasoning_depth to 'moderate' for hypothesis/verification types\n\tif (\n\t\t(input.thought_type === 'hypothesis' || input.thought_type === 'verification') &&\n\t\t!('reasoning_depth' in input)\n\t) {\n\t\tinput.reasoning_depth = 'moderate';\n\t}\n}\n\n/**\n * Normalizes thought input data by fixing common LLM field name mistakes.\n *\n * This function handles cases where LLMs incorrectly use singular forms\n * of field names that should be plural. It applies normalization to both\n * `current_step` and `previous_steps` fields.\n *\n * The normalization is applied BEFORE schema validation, allowing the\n * strict Valibot schema to remain correct while still being tolerant\n * of common LLM mistakes.\n *\n * @param input - The raw thought input data to normalize\n * @returns Normalized thought data with correct field names\n *\n * @remarks\n * **Normalization Rules:**\n * - `recommended_tool` (singular) → `recommended_tools` (plural)\n * - `recommended_skill` (singular) → `recommended_skills` (plural)\n * - Applied to `current_step` if present (strict mode)\n * - Applied to all items in `previous_steps` if present (lenient mode with defaults)\n *\n * **Design Rationale:**\n * LLMs sometimes use singular field names even when the schema explicitly\n * defines plural forms. Rather than forcing the LLM to be perfect (which\n * leads to cryptic validation errors), we normalize the input to handle\n * these common mistakes gracefully.\n *\n * Additionally, LLMs naturally provide complete data for `current_step`\n * but only partial/skeletal data for `previous_steps` (historical context).\n * The lenient mode for `previous_steps` fills in sensible defaults:\n * - `confidence`: 0.5 for missing tool recommendation confidence\n * - `priority`: 999 for missing tool recommendation priority\n * - `rationale`: empty string for missing tool recommendation rationale\n * - `expected_outcome`: empty string for missing step expected outcome\n *\n * @example\n * ```typescript\n * const input = {\n * thought: 'I need to analyze the data',\n * thought_number: 1,\n * total_thoughts: 3,\n * next_thought_needed: true,\n * current_step: {\n * step_description: 'Read the data file',\n * recommended_tool: [{ tool_name: 'Read', confidence: 0.9, rationale: 'test', priority: 1 }],\n * expected_outcome: 'Data loaded'\n * },\n * previous_steps: [{\n * step_description: 'Previous step',\n * recommended_tools: [{ tool_name: 'Grep', rationale: 'Search code' }]\n * }]\n * };\n *\n * const normalized = normalizeInput(input);\n * // current_step: recommended_tools exists (plural form)\n * // previous_steps[0]: confidence=0.5, priority=999, expected_outcome='' filled in\n * ```\n */\nexport function normalizeInput(input: unknown): ThoughtData {\n\tif (typeof input !== 'object' || input === null) {\n\t\treturn input as ThoughtData;\n\t}\n\n\tconst normalized = { ...input } as Record<string, unknown>;\n\n\t// Normalize current_step if present (strict mode - no defaults)\n\tif (normalized.current_step && typeof normalized.current_step === 'object') {\n\t\tnormalized.current_step = normalizeStepRecommendation(\n\t\t\tnormalized.current_step as Record<string, unknown>,\n\t\t\tfalse // strict mode\n\t\t);\n\t}\n\n\n\t// Normalize all items in previous_steps if present (lenient mode - with defaults)\n\tif (Array.isArray(normalized.previous_steps) && normalized.previous_steps.length > 0) {\n\t\tnormalized.previous_steps = normalized.previous_steps.map((step) =>\n\t\t\ttypeof step === 'object' && step !== null\n\t\t\t\t? normalizeStepRecommendation(step as Record<string, unknown>, true) // lenient mode\n\t\t\t\t: step\n\t\t);\n\t}\n\n\t// Sanitize branch_id to prevent path traversal attacks\n\tif (typeof normalized.branch_id === 'string') {\n\t\tnormalized.branch_id = sanitizeBranchId(normalized.branch_id);\n\t}\n\n\t// Sanitize session_id (same pattern as branch_id but allows 1-100 chars)\n\tif (typeof normalized.session_id === 'string') {\n\t\tconst sanitized = sanitizeSessionId(normalized.session_id);\n\t\tif (sanitized === undefined) {\n\t\t\tdelete normalized.session_id;\n\t\t} else {\n\t\t\tnormalized.session_id = sanitized;\n\t\t}\n\t}\n\n\t// Auto-generate id if not provided (for DAG node identity)\n\tif (!normalized.id || typeof normalized.id !== 'string') {\n\t\tnormalized.id = generateUlid();\n\t}\n\n\t// Normalize reasoning fields\n\tnormalizeReasoningFields(normalized);\n\n\n\t// Sanitize all free-text string fields recursively (dangerous HTML tags + null bytes)\n\t// This was moved from schema transforms because v.transform() cannot be converted to JSON Schema\n\tconst sanitized = sanitizeRecursive(normalized);\n\n\treturn sanitized as unknown as ThoughtData;\n}\n"],"names":["DEFAULT_RECOMMENDATION_CONFIDENCE","DEFAULT_RECOMMENDATION_PRIORITY","DEFAULT_RECOMMENDATION_RATIONALE","DEFAULT_STEP_OUTCOME","sanitizeRecursive","value","sanitizeString","Array","item","proto","Object","result","key","val","BRANCH_ID_PATTERN","sanitizeBranchId","branchId","ValidationError","sanitizeSessionId","sessionId","cleaned","MAX_SESSION_ID_LENGTH","SESSION_ID_PATTERN","normalizeRecommendation","rec","normalized","undefined","normalizeStepRecommendation","step","lenient","tool","skill","normalizeReasoningFields","input","Math","v","Number","id","normalizeInput","sanitized","generateUlid"],"mappings":"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"}
1
+ {"version":3,"file":"core/InputNormalizer.js","sources":["../../src/core/InputNormalizer.ts"],"sourcesContent":["/**\n * Input normalization for common LLM field name mistakes.\n *\n * This module provides normalization logic to handle common mistakes\n * that LLMs make when generating field names, such as using singular\n * instead of plural forms (e.g., `recommended_tool` vs `recommended_tools`).\n *\n * It also fills in sensible defaults for missing fields in `previous_steps`,\n * which LLMs naturally provide as partial/skeletal data (historical context).\n *\n * @module processor\n */\n\nimport { ValidationError } from '../errors.js';\nimport { sanitizeString } from '../sanitize.js';\nimport type { ThoughtData } from './thought.js';\nimport { SESSION_ID_PATTERN, MAX_SESSION_ID_LENGTH } from './ids.js';\nimport { asSessionId, generateThoughtId } from '../contracts/ids.js';\n\n/**\n * Default values for missing partial recommendation fields.\n *\n * Shared between tool and skill recommendations (identical defaults).\n */\nconst DEFAULT_RECOMMENDATION_CONFIDENCE = 0.5;\nconst DEFAULT_RECOMMENDATION_PRIORITY = 999;\nconst DEFAULT_RECOMMENDATION_RATIONALE = '';\nconst DEFAULT_STEP_OUTCOME = '';\n\n/**\n * Recursively sanitizes all string values within an unknown structure.\n * Walks into plain objects and arrays to reach deeply nested strings.\n * Non-plain objects (Date, RegExp, etc.) are returned as-is.\n *\n * @param value - The value to sanitize recursively\n * @returns The sanitized value with all nested strings cleaned\n *\n * @example\n * ```typescript\n * sanitizeRecursive('<script>x</script>'); // 'x'\n * sanitizeRecursive({ a: { b: '<iframe>y' } }); // { a: { b: 'y' } }\n * sanitizeRecursive(['a\\x00b', 42]); // ['ab', 42]\n * ```\n */\nexport function sanitizeRecursive(value: unknown): unknown {\n\tif (value === null || value === undefined) {\n\t\treturn value;\n\t}\n\tif (typeof value === 'string') {\n\t\treturn sanitizeString(value);\n\t}\n\tif (Array.isArray(value)) {\n\t\treturn value.map((item) => sanitizeRecursive(item));\n\t}\n\tif (typeof value === 'object') {\n\t\t// Only recurse into plain objects — skip Date, RegExp, Map, Set, etc.\n\t\tconst proto = Object.getPrototypeOf(value);\n\t\tif (proto !== Object.prototype && proto !== null) {\n\t\t\treturn value;\n\t\t}\n\t\tconst result: Record<string, unknown> = {};\n\t\tfor (const [key, val] of Object.entries(value as Record<string, unknown>)) {\n\t\t\tresult[key] = sanitizeRecursive(val);\n\t\t}\n\t\treturn result;\n\t}\n\treturn value;\n}\n\n/**\n * Valid branch ID pattern: alphanumeric, hyphens, underscores only.\n * Prevents path traversal attacks by rejecting special characters like / . \\ etc.\n */\nconst BRANCH_ID_PATTERN = /^[a-zA-Z0-9_-]{1,64}$/;\n\n\n\n/**\n * Sanitizes and validates a branch ID to prevent path traversal attacks.\n *\n * @param branchId - The branch ID to sanitize\n * @returns The sanitized branch ID\n * @throws ValidationError if the branch ID contains invalid characters\n *\n * @example\n * ```typescript\n * sanitizeBranchId('my-branch_01'); // 'my-branch_01'\n * sanitizeBranchId('../etc/passwd'); // throws ValidationError\n * ```\n */\nexport function sanitizeBranchId(branchId: string): string {\n\t// Validate format\n\tif (!BRANCH_ID_PATTERN.test(branchId)) {\n\t\tthrow new ValidationError(\n\t\t\t'branch_id',\n\t\t\t'Invalid format - must be 1-64 alphanumeric characters, hyphens, or underscores only'\n\t\t);\n\t}\n\treturn branchId;\n}\n\n/**\n * Sanitizes and validates a session ID.\n *\n * @param sessionId - The session ID to sanitize\n * @returns The sanitized session ID, or undefined if invalid after sanitization\n *\n * @example\n * ```typescript\n * sanitizeSessionId('analysis-task-42'); // 'analysis-task-42'\n * sanitizeSessionId('bad session!'); // undefined (stripped)\n * ```\n */\nexport function sanitizeSessionId(sessionId: string): string | undefined {\n\t// First sanitize control characters\n\tconst cleaned = sanitizeString(sessionId);\n\t// Validate format after sanitization\n\tif (cleaned.length > MAX_SESSION_ID_LENGTH || !SESSION_ID_PATTERN.test(cleaned)) {\n\t\treturn undefined;\n\t}\n\treturn cleaned;\n}\n\n\n/**\n * Normalizes a recommendation object (tool or skill) with default values.\n *\n * Fills in sensible defaults for missing optional fields:\n * - `confidence`: 0.5\n * - `priority`: 999\n * - `rationale`: empty string\n *\n * @param rec - The recommendation object to normalize\n * @returns The normalized recommendation with defaults filled in\n *\n * @example\n * ```typescript\n * const input = { tool_name: 'Read', rationale: 'Read the file' };\n * const normalized = normalizeRecommendation(input);\n * // { tool_name: 'Read', rationale: 'Read the file', confidence: 0.5, priority: 999 }\n * ```\n */\nfunction normalizeRecommendation(rec: Record<string, unknown>): Record<string, unknown> {\n\tconst normalized: Record<string, unknown> = { ...rec };\n\n\t// Fill in default confidence if missing\n\tif (!('confidence' in normalized) || normalized.confidence === undefined) {\n\t\tnormalized.confidence = DEFAULT_RECOMMENDATION_CONFIDENCE;\n\t}\n\n\t// Fill in default priority if missing\n\tif (!('priority' in normalized) || normalized.priority === undefined) {\n\t\tnormalized.priority = DEFAULT_RECOMMENDATION_PRIORITY;\n\t}\n\n\t// Fill in default rationale if missing\n\tif (!('rationale' in normalized) || normalized.rationale === undefined) {\n\t\tnormalized.rationale = DEFAULT_RECOMMENDATION_RATIONALE;\n\t}\n\n\treturn normalized;\n}\n/**\n * Normalizes step recommendation objects.\n *\n * Handles common field name mistakes:\n * - `recommended_tool` (singular) → `recommended_tools` (plural)\n * - `recommended_skill` (singular) → `recommended_skills` (plural)\n *\n * Also normalizes tool recommendations within the step to fill in defaults.\n *\n * @param step - The step recommendation to normalize\n * @param lenient - Whether to use lenient mode (fill in defaults for missing fields)\n * @returns The normalized step recommendation\n *\n * @example\n * ```typescript\n * // Strict mode (for current_step)\n * const input = {\n * step_description: 'Analyze data',\n * recommended_tool: [{ tool_name: 'Read', confidence: 0.9, rationale: 'test', priority: 1 }],\n * expected_outcome: 'Data analyzed'\n * };\n * const normalized = normalizeStepRecommendation(input, false);\n * // normalized.recommended_tools exists (plural form)\n *\n * // Lenient mode (for previous_steps)\n * const partialInput = {\n * step_description: 'Read file',\n * recommended_tools: [{ tool_name: 'Read', rationale: 'Read file' }]\n * };\n * const normalized = normalizeStepRecommendation(partialInput, true);\n * // confidence: 0.5, priority: 999, expected_outcome: '' filled in\n * ```\n */\nfunction normalizeStepRecommendation(\n\tstep: Record<string, unknown>,\n\tlenient: boolean\n): Record<string, unknown> {\n\tconst normalized: Record<string, unknown> = { ...step };\n\n\t// Transform `recommended_tool` (singular) → `recommended_tools` (plural)\n\tif ('recommended_tool' in normalized && !('recommended_tools' in normalized)) {\n\t\tnormalized.recommended_tools = normalized.recommended_tool;\n\t\tdelete normalized.recommended_tool;\n\t}\n\n\t// Transform `recommended_skill` (singular) → `recommended_skills` (plural)\n\tif ('recommended_skill' in normalized && !('recommended_skills' in normalized)) {\n\t\tnormalized.recommended_skills = normalized.recommended_skill;\n\t\tdelete normalized.recommended_skill;\n\t}\n\n\t// Normalize recommended_tools array if present\n\tif (Array.isArray(normalized.recommended_tools)) {\n\t\tnormalized.recommended_tools = normalized.recommended_tools.map((tool) =>\n\t\t\ttypeof tool === 'object' && tool !== null\n\t\t\t\t? normalizeRecommendation(tool as Record<string, unknown>)\n\t\t\t\t: tool\n\t\t);\n\t}\n\n\t// Normalize recommended_skills array if present\n\tif (Array.isArray(normalized.recommended_skills)) {\n\t\tnormalized.recommended_skills = normalized.recommended_skills.map((skill) =>\n\t\t\ttypeof skill === 'object' && skill !== null\n\t\t\t\t? normalizeRecommendation(skill as Record<string, unknown>)\n\t\t\t\t: skill\n\t\t);\n\t}\n\t// In lenient mode, fill in default expected_outcome if missing\n\tif (lenient && !('expected_outcome' in normalized)) {\n\t\tnormalized.expected_outcome = DEFAULT_STEP_OUTCOME;\n\t}\n\n\treturn normalized;\n}\n\n\n/**\n * Normalizes reasoning-specific fields on a thought input object.\n * Always applies reasoning normalization — reasoning is the default pipeline.\n * Applies the following normalization rules:\n * - Defaults `thought_type` to `'regular'` if not provided\n * - Clamps `quality_score` to [0, 1] range\n * - Clamps `confidence` to [0, 1] range\n * - Sanitizes `hypothesis_id` using `sanitizeBranchId` pattern\n * - Filters `synthesis_sources` to positive integers only\n * - Filters `merge_from_thoughts` to positive integers only\n * - Sanitizes each entry in `merge_branch_ids`\n * - Defaults `reasoning_depth` to `'moderate'` for hypothesis/verification types\n *\n * @param input - The mutable normalized input object to apply reasoning defaults to\n *\n * @example\n * ```typescript\n * const input: Record<string, unknown> = { thought_type: 'hypothesis', quality_score: 1.5 };\n * normalizeReasoningFields(input);\n * // input.quality_score === 1, input.reasoning_depth === 'moderate'\n * ```\n */\nexport function normalizeReasoningFields(input: Record<string, unknown>): void {\n\t// Always apply reasoning field normalization — reasoning is the default pipeline\n\t// Default thought_type to 'regular'\n\n\tif (!('thought_type' in input) || input.thought_type === undefined) {\n\t\tinput.thought_type = 'regular';\n\t}\n\n\t// Clamp quality_score to [0, 1]\n\tif (typeof input.quality_score === 'number') {\n\t\tinput.quality_score = Math.max(0, Math.min(1, input.quality_score));\n\t}\n\n\t// Clamp confidence to [0, 1]\n\tif (typeof input.confidence === 'number') {\n\t\tinput.confidence = Math.max(0, Math.min(1, input.confidence));\n\t}\n\n\t// Sanitize hypothesis_id (same rules as branch_id)\n\tif (typeof input.hypothesis_id === 'string') {\n\t\tinput.hypothesis_id = sanitizeBranchId(input.hypothesis_id);\n\t}\n\n\t// Filter synthesis_sources to positive integers only\n\tif (Array.isArray(input.synthesis_sources)) {\n\t\tinput.synthesis_sources = input.synthesis_sources.filter(\n\t\t\t(v: unknown) => typeof v === 'number' && Number.isInteger(v) && v > 0\n\t\t);\n\t}\n\n\t// Filter merge_from_thoughts to positive integers only\n\tif (Array.isArray(input.merge_from_thoughts)) {\n\t\tinput.merge_from_thoughts = input.merge_from_thoughts.filter(\n\t\t\t(v: unknown) => typeof v === 'number' && Number.isInteger(v) && v > 0\n\t\t);\n\t}\n\n\t// Sanitize merge_branch_ids entries\n\tif (Array.isArray(input.merge_branch_ids)) {\n\t\tinput.merge_branch_ids = input.merge_branch_ids.map((id: unknown) => {\n\t\t\tif (typeof id === 'string') {\n\t\t\t\treturn sanitizeBranchId(id);\n\t\t\t}\n\t\t\treturn id;\n\t\t});\n\t}\n\n\t// Default reasoning_depth to 'moderate' for hypothesis/verification types\n\tif (\n\t\t(input.thought_type === 'hypothesis' || input.thought_type === 'verification') &&\n\t\t!('reasoning_depth' in input)\n\t) {\n\t\tinput.reasoning_depth = 'moderate';\n\t}\n}\n\n/**\n * Normalizes thought input data by fixing common LLM field name mistakes.\n *\n * This function handles cases where LLMs incorrectly use singular forms\n * of field names that should be plural. It applies normalization to both\n * `current_step` and `previous_steps` fields.\n *\n * The normalization is applied BEFORE schema validation, allowing the\n * strict Valibot schema to remain correct while still being tolerant\n * of common LLM mistakes.\n *\n * @param input - The raw thought input data to normalize\n * @returns Normalized thought data with correct field names\n *\n * @remarks\n * **Normalization Rules:**\n * - `recommended_tool` (singular) → `recommended_tools` (plural)\n * - `recommended_skill` (singular) → `recommended_skills` (plural)\n * - Applied to `current_step` if present (strict mode)\n * - Applied to all items in `previous_steps` if present (lenient mode with defaults)\n *\n * **Design Rationale:**\n * LLMs sometimes use singular field names even when the schema explicitly\n * defines plural forms. Rather than forcing the LLM to be perfect (which\n * leads to cryptic validation errors), we normalize the input to handle\n * these common mistakes gracefully.\n *\n * Additionally, LLMs naturally provide complete data for `current_step`\n * but only partial/skeletal data for `previous_steps` (historical context).\n * The lenient mode for `previous_steps` fills in sensible defaults:\n * - `confidence`: 0.5 for missing tool recommendation confidence\n * - `priority`: 999 for missing tool recommendation priority\n * - `rationale`: empty string for missing tool recommendation rationale\n * - `expected_outcome`: empty string for missing step expected outcome\n *\n * @example\n * ```typescript\n * const input = {\n * thought: 'I need to analyze the data',\n * thought_number: 1,\n * total_thoughts: 3,\n * next_thought_needed: true,\n * current_step: {\n * step_description: 'Read the data file',\n * recommended_tool: [{ tool_name: 'Read', confidence: 0.9, rationale: 'test', priority: 1 }],\n * expected_outcome: 'Data loaded'\n * },\n * previous_steps: [{\n * step_description: 'Previous step',\n * recommended_tools: [{ tool_name: 'Grep', rationale: 'Search code' }]\n * }]\n * };\n *\n * const normalized = normalizeInput(input);\n * // current_step: recommended_tools exists (plural form)\n * // previous_steps[0]: confidence=0.5, priority=999, expected_outcome='' filled in\n * ```\n */\nexport function normalizeInput(input: unknown): ThoughtData {\n\tif (typeof input !== 'object' || input === null) {\n\t\treturn input as ThoughtData;\n\t}\n\n\tconst normalized = { ...input } as Record<string, unknown>;\n\n\t// Normalize current_step if present (strict mode - no defaults)\n\tif (normalized.current_step && typeof normalized.current_step === 'object') {\n\t\tnormalized.current_step = normalizeStepRecommendation(\n\t\t\tnormalized.current_step as Record<string, unknown>,\n\t\t\tfalse // strict mode\n\t\t);\n\t}\n\n\n\t// Normalize all items in previous_steps if present (lenient mode - with defaults)\n\tif (Array.isArray(normalized.previous_steps) && normalized.previous_steps.length > 0) {\n\t\tnormalized.previous_steps = normalized.previous_steps.map((step) =>\n\t\t\ttypeof step === 'object' && step !== null\n\t\t\t\t? normalizeStepRecommendation(step as Record<string, unknown>, true) // lenient mode\n\t\t\t\t: step\n\t\t);\n\t}\n\n\t// Sanitize branch_id to prevent path traversal attacks\n\tif (typeof normalized.branch_id === 'string') {\n\t\tnormalized.branch_id = sanitizeBranchId(normalized.branch_id);\n\t}\n\n\t// Sanitize session_id (same pattern as branch_id but allows 1-100 chars)\n\tif (typeof normalized.session_id === 'string') {\n\t\tconst sanitized = sanitizeSessionId(normalized.session_id);\n\t\tif (sanitized === undefined) {\n\t\t\tdelete normalized.session_id;\n\t\t} else {\n\t\t\tnormalized.session_id = asSessionId(sanitized);\n\t\t}\n\t}\n\n\t// Auto-generate id if not provided (for DAG node identity)\n\tif (!normalized.id || typeof normalized.id !== 'string') {\n\t\tnormalized.id = generateThoughtId();\n\t}\n\n\t// Normalize reasoning fields\n\tnormalizeReasoningFields(normalized);\n\n\n\t// Sanitize all free-text string fields recursively (dangerous HTML tags + null bytes)\n\t// This was moved from schema transforms because v.transform() cannot be converted to JSON Schema\n\tconst sanitized = sanitizeRecursive(normalized);\n\n\treturn sanitized as ThoughtData;\n}\n"],"names":["DEFAULT_RECOMMENDATION_CONFIDENCE","DEFAULT_RECOMMENDATION_PRIORITY","DEFAULT_RECOMMENDATION_RATIONALE","DEFAULT_STEP_OUTCOME","sanitizeRecursive","value","sanitizeString","Array","item","proto","Object","result","key","val","BRANCH_ID_PATTERN","sanitizeBranchId","branchId","ValidationError","sanitizeSessionId","sessionId","cleaned","MAX_SESSION_ID_LENGTH","SESSION_ID_PATTERN","normalizeRecommendation","rec","normalized","undefined","normalizeStepRecommendation","step","lenient","tool","skill","normalizeReasoningFields","input","Math","v","Number","id","normalizeInput","sanitized","asSessionId","generateThoughtId"],"mappings":"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"}
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import type { IEdgeStore } from '../contracts/interfaces.js';
12
12
  import type { Logger } from '../logger/StructuredLogger.js';
13
- import type { PersistenceBackend } from '../persistence/PersistenceBackend.js';
13
+ import type { PersistenceBackend } from '../contracts/PersistenceBackend.js';
14
14
  import type { ThoughtData } from './thought.js';
15
15
  /** Minimal session view: anything that owns a `writeBuffer`. */
16
16
  export interface BufferedSession {
@@ -1 +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"}
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,oCAAoC,CAAC;AAC7E,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"}
@@ -1 +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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"}
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 '../contracts/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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"}
@@ -7,6 +7,7 @@
7
7
  *
8
8
  * @module formatter
9
9
  */
10
+ import type { IThoughtFormatter } from './IThoughtFormatter.js';
10
11
  import type { StepRecommendation } from './step.js';
11
12
  import type { ThoughtData } from './thought.js';
12
13
  /**
@@ -52,7 +53,7 @@ import type { ThoughtData } from './thought.js';
52
53
  * console.log(output);
53
54
  * ```
54
55
  */
55
- export declare class ThoughtFormatter {
56
+ export declare class ThoughtFormatter implements IThoughtFormatter {
56
57
  /**
57
58
  * Formats a step recommendation into a readable string.
58
59
  *
@@ -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;CAoGtD"}
1
+ {"version":3,"file":"ThoughtFormatter.d.ts","sourceRoot":"","sources":["../../src/core/ThoughtFormatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,qBAAa,gBAAiB,YAAW,iBAAiB;IACzD;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACI,oBAAoB,CAAC,IAAI,EAAE,kBAAkB,GAAG,MAAM;IAuB7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6CG;IACI,aAAa,CAAC,WAAW,EAAE,WAAW,GAAG,MAAM;CA0GtD"}
@@ -69,6 +69,9 @@ class ThoughtFormatter {
69
69
  icon = chalk.red('↩️');
70
70
  label = 'Backtrack';
71
71
  break;
72
+ case 'regular':
73
+ icon = chalk.blue('💭');
74
+ break;
72
75
  default:
73
76
  icon = chalk.blue('💭');
74
77
  break;
@@ -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 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":"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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 { IThoughtFormatter } from './IThoughtFormatter.js';\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 implements IThoughtFormatter {\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\tcase 'regular':\n\t\t\t\t\ticon = chalk.blue('💭');\n\t\t\t\t\tbreak;\n\t\t\t\tdefault: {\n\t\t\t\t\tconst _exhaust: never = thoughtType;\n\t\t\t\t\tvoid _exhaust;\n\t\t\t\t\ticon = chalk.blue('💭');\n\t\t\t\t\tbreak;\n\t\t\t\t}\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":"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"}
@@ -10,7 +10,7 @@
10
10
  import type { Logger } from '../logger/StructuredLogger.js';
11
11
  import type { ISuspensionStore } from '../contracts/suspension.js';
12
12
  import type { IReasoningStrategy } from '../contracts/strategy.js';
13
- import type { FeatureFlags } from '../ServerConfig.js';
13
+ import type { FeatureFlags } from '../contracts/features.js';
14
14
  import type { IHistoryManager } from './IHistoryManager.js';
15
15
  import type { ThoughtData } from './thought.js';
16
16
  import type { ThoughtEvaluator } from './ThoughtEvaluator.js';
@@ -165,7 +165,7 @@ export declare class ThoughtProcessor {
165
165
  * @private
166
166
  */
167
167
  private _findBranchRoot;
168
- /** Best-effort access to the EdgeStore via duck typing. @private */
168
+ /** Access the EdgeStore via IHistoryManager. @private */
169
169
  private _getEdgeStore;
170
170
  /**
171
171
  * Validates and normalizes thought input.
@@ -1 +1 @@
1
- {"version":3,"file":"ThoughtProcessor.d.ts","sourceRoot":"","sources":["../../src/core/ThoughtProcessor.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAE5D,OAAO,KAAK,EAAE,gBAAgB,EAAoB,MAAM,4BAA4B,CAAC;AACrF,OAAO,KAAK,EAAE,kBAAkB,EAAoB,MAAM,0BAA0B,CAAC;AACrF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAUvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAoB9E;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,cAAc;IAC9B,wEAAwE;IACxE,OAAO,EAAE,KAAK,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACb,CAAC,CAAC;IAEH,yDAAyD;IACzD,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,gBAAgB;IAwB3B,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,gBAAgB;IAGxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC;IACrC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS;IA9B3B,2CAA2C;IAC3C,OAAO,CAAC,OAAO,CAAS;IAExB,gDAAgD;IAChD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAmB;IAErD;;;OAGG;IACH,OAAO,CAAC,cAAc,CAA0C;IAEhE;;;;;;;;;OASG;gBAEM,cAAc,EAAE,eAAe,EAC/B,gBAAgB,EAAE,gBAAgB,EAC1C,gBAAgB,EAAE,gBAAgB,EAClC,MAAM,CAAC,EAAE,MAAM,EACE,QAAQ,GAAE,kBAA6C,EACvD,mBAAmB,CAAC,EAAE,kBAAkB,YAAA,EACxC,gBAAgB,CAAC,EAAE,gBAAgB,YAAA,EACnC,SAAS,GAAE,YAA+B;IAM5D;;;;;OAKG;IACH,OAAO,CAAC,GAAG;IAIX;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAKpC;IAEF;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,cAAc;IAqCtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAwCG;IACU,OAAO,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,cAAc,CAAC;IAiIjE;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAiDpB;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAavB,oEAAoE;IACpE,OAAO,CAAC,aAAa;IAKrB;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,CAAC,aAAa;IAoBrB;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,wBAAwB;IAqGhC;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAgDzB;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAc5B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAWjC;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAiCvB;;;;OAIG;IACH,OAAO,CAAC,sBAAsB;CAY9B"}
1
+ {"version":3,"file":"ThoughtProcessor.d.ts","sourceRoot":"","sources":["../../src/core/ThoughtProcessor.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAG5D,OAAO,KAAK,EAAE,gBAAgB,EAAoB,MAAM,4BAA4B,CAAC;AACrF,OAAO,KAAK,EAAE,kBAAkB,EAAoB,MAAM,0BAA0B,CAAC;AACrF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAU7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAoB9E;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,cAAc;IAC9B,wEAAwE;IACxE,OAAO,EAAE,KAAK,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACb,CAAC,CAAC;IAEH,yDAAyD;IACzD,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,gBAAgB;IAwB3B,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,gBAAgB;IAGxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC;IACrC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS;IA9B3B,2CAA2C;IAC3C,OAAO,CAAC,OAAO,CAAS;IAExB,gDAAgD;IAChD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAmB;IAErD;;;OAGG;IACH,OAAO,CAAC,cAAc,CAA0C;IAEhE;;;;;;;;;OASG;gBAEM,cAAc,EAAE,eAAe,EAC/B,gBAAgB,EAAE,gBAAgB,EAC1C,gBAAgB,EAAE,gBAAgB,EAClC,MAAM,CAAC,EAAE,MAAM,EACE,QAAQ,GAAE,kBAA6C,EACvD,mBAAmB,CAAC,EAAE,kBAAkB,YAAA,EACxC,gBAAgB,CAAC,EAAE,gBAAgB,YAAA,EACnC,SAAS,GAAE,YAA+B;IAM5D;;;;;OAKG;IACH,OAAO,CAAC,GAAG;IAIX;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAKpC;IAEF;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,cAAc;IAqCtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAwCG;IACU,OAAO,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,cAAc,CAAC;IAiIjE;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAiDpB;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAavB,yDAAyD;IACzD,OAAO,CAAC,aAAa;IAIrB;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,CAAC,aAAa;IAoBrB;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,wBAAwB;IAqGhC;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAgDzB;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAc5B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAejC;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAiCvB;;;;OAIG;IACH,OAAO,CAAC,sBAAsB;CAY9B"}