tracelattice 1.3.4 → 1.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/HistoryManager.d.ts.map +1 -1
- package/dist/core/HistoryManager.js +1 -0
- package/dist/core/HistoryManager.js.map +1 -1
- package/dist/core/InputNormalizer.d.ts.map +1 -1
- package/dist/core/InputNormalizer.js +5 -1
- package/dist/core/InputNormalizer.js.map +1 -1
- package/dist/sanitize.d.ts +31 -0
- package/dist/sanitize.d.ts.map +1 -1
- package/dist/sanitize.js +12 -2
- package/dist/sanitize.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HistoryManager.d.ts","sourceRoot":"","sources":["../../src/core/HistoryManager.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"HistoryManager.d.ts","sourceRoot":"","sources":["../../src/core/HistoryManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -229,6 +229,7 @@ class HistoryManager {
|
|
|
229
229
|
return this._getSession(sessionId, this._getCurrentOwner()).branches[branchId];
|
|
230
230
|
}
|
|
231
231
|
clear(sessionId) {
|
|
232
|
+
if (void 0 !== sessionId) this._getSession(sessionId, this._getCurrentOwner());
|
|
232
233
|
if (this._edgeStore) if (void 0 !== sessionId) this._edgeStore.clearSession(sessionId);
|
|
233
234
|
else {
|
|
234
235
|
for (const sid of this._sessions.keys())this._edgeStore.clearSession(sid);
|
|
@@ -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/interfaces.js';\nimport type { IEdgeStore } from '../contracts/interfaces.js';\nimport type { ISummaryStore } from '../contracts/summary.js';\nimport { ValidationError, SessionAccessDeniedError, 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';\nimport { getOwner } from '../context/RequestContext.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\t/** Owner identifier set on first owner-aware access. Immutable thereafter. */\n\towner?: 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\t/** Maximum sessions per owner (per-owner LRU bucket). @default 50 */\n\tmaxSessionsPerOwner?: number;\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\tmaxSessionsPerOwner: config.maxSessionsPerOwner ?? 50,\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/** Reads owner from RequestContext (AsyncLocalStorage). Stdio path returns undefined. */\n\tprivate _getCurrentOwner(): string | undefined {\n\t\treturn getOwner();\n\t}\n\n\t/**\n\t * Gets or creates session state; updates lastAccessedAt.\n\t *\n\t * Ownership semantics:\n\t * - `owner === undefined` (stdio path): never rejects, never sets owner.\n\t * - `owner !== undefined`: if session has a different owner, throws\n\t * `SessionAccessDeniedError`. If session was created without an owner\n\t * (e.g. by stdio), the owner is set on first owner-aware access.\n\t */\n\tprivate _getSession(sessionId?: string, owner?: 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\towner,\n\t\t\t};\n\t\t\tthis._sessions.set(key, session);\n\t\t\tthis._sessionManager.evictExcessSessions(this._sessions);\n\t\t} else if (owner !== undefined) {\n\t\t\tif (session.owner !== undefined && session.owner !== owner) {\n\t\t\t\tthrow new SessionAccessDeniedError(key, session.owner, owner);\n\t\t\t}\n\t\t\tif (session.owner === undefined) {\n\t\t\t\t// First owner-aware access: bind owner. Acceptable promotion path\n\t\t\t\t// for sessions created by stdio that later receive an owner-bearing\n\t\t\t\t// access (single-user transition).\n\t\t\t\tsession.owner = owner;\n\t\t\t}\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, this._getCurrentOwner());\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, this._getCurrentOwner()).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, this._getCurrentOwner()).thought_history.length;\n\t}\n\n\tpublic getBranches(sessionId?: string): Record<string, ThoughtData[]> {\n\t\treturn this._getSession(sessionId, this._getCurrentOwner()).branches;\n\t}\n\n\tpublic getBranchIds(sessionId?: string): string[] {\n\t\tconst session = this._getSession(sessionId, this._getCurrentOwner());\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, this._getCurrentOwner());\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, this._getCurrentOwner());\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, this._getCurrentOwner()).availableMcpTools;\n\t}\n\n\tpublic getAvailableSkills(sessionId?: string): string[] | undefined {\n\t\treturn this._getSession(sessionId, this._getCurrentOwner()).availableSkills;\n\t}\n\n\tpublic getBranch(branchId: string, sessionId?: string): ThoughtData[] | undefined {\n\t\treturn this._getSession(sessionId, this._getCurrentOwner()).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","getOwner","sessionId","owner","key","session","undefined","SessionAccessDeniedError","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, SessionAccessDeniedError, 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';\nimport { getOwner } from '../context/RequestContext.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\t/** Owner identifier set on first owner-aware access. Immutable thereafter. */\n\towner?: 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\t/** Maximum sessions per owner (per-owner LRU bucket). @default 50 */\n\tmaxSessionsPerOwner?: number;\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\tmaxSessionsPerOwner: config.maxSessionsPerOwner ?? 50,\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/** Reads owner from RequestContext (AsyncLocalStorage). Stdio path returns undefined. */\n\tprivate _getCurrentOwner(): string | undefined {\n\t\treturn getOwner();\n\t}\n\n\t/**\n\t * Gets or creates session state; updates lastAccessedAt.\n\t *\n\t * Ownership semantics:\n\t * - `owner === undefined` (stdio path): never rejects, never sets owner.\n\t * - `owner !== undefined`: if session has a different owner, throws\n\t * `SessionAccessDeniedError`. If session was created without an owner\n\t * (e.g. by stdio), the owner is set on first owner-aware access.\n\t */\n\tprivate _getSession(sessionId?: string, owner?: 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\towner,\n\t\t\t};\n\t\t\tthis._sessions.set(key, session);\n\t\t\tthis._sessionManager.evictExcessSessions(this._sessions);\n\t\t} else if (owner !== undefined) {\n\t\t\tif (session.owner !== undefined && session.owner !== owner) {\n\t\t\t\tthrow new SessionAccessDeniedError(key, session.owner, owner);\n\t\t\t}\n\t\t\tif (session.owner === undefined) {\n\t\t\t\t// First owner-aware access: bind owner. Acceptable promotion path\n\t\t\t\t// for sessions created by stdio that later receive an owner-bearing\n\t\t\t\t// access (single-user transition).\n\t\t\t\tsession.owner = owner;\n\t\t\t}\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, this._getCurrentOwner());\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, this._getCurrentOwner()).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, this._getCurrentOwner()).thought_history.length;\n\t}\n\n\tpublic getBranches(sessionId?: string): Record<string, ThoughtData[]> {\n\t\treturn this._getSession(sessionId, this._getCurrentOwner()).branches;\n\t}\n\n\tpublic getBranchIds(sessionId?: string): string[] {\n\t\tconst session = this._getSession(sessionId, this._getCurrentOwner());\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, this._getCurrentOwner());\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, this._getCurrentOwner());\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, this._getCurrentOwner()).availableMcpTools;\n\t}\n\n\tpublic getAvailableSkills(sessionId?: string): string[] | undefined {\n\t\treturn this._getSession(sessionId, this._getCurrentOwner()).availableSkills;\n\t}\n\n\tpublic getBranch(branchId: string, sessionId?: string): ThoughtData[] | undefined {\n\t\treturn this._getSession(sessionId, this._getCurrentOwner()).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// Enforce ownership before clearing a specific session.\n\t\t// _getSession throws SessionAccessDeniedError on owner mismatch.\n\t\tif (sessionId !== undefined) {\n\t\t\tthis._getSession(sessionId, this._getCurrentOwner());\n\t\t}\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","getOwner","sessionId","owner","key","session","undefined","SessionAccessDeniedError","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 +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;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;
|
|
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;AA+ID;;;;;;;;;;;;;;;;;;;;;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,CA2D1D"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ValidationError } from "../errors.js";
|
|
2
|
-
import { sanitizeRationale, sanitizeString, sanitizeSuggestedInputs } from "../sanitize.js";
|
|
2
|
+
import { sanitizeRationale, sanitizeStepField, sanitizeString, sanitizeSuggestedInputs } from "../sanitize.js";
|
|
3
3
|
import { MAX_SESSION_ID_LENGTH, SESSION_ID_PATTERN } from "./ids.js";
|
|
4
4
|
import { asSessionId, generateThoughtId } from "../contracts/ids.js";
|
|
5
5
|
const DEFAULT_RECOMMENDATION_CONFIDENCE = 0.5;
|
|
@@ -55,6 +55,9 @@ function normalizeStepRecommendation(step, lenient) {
|
|
|
55
55
|
}
|
|
56
56
|
if (Array.isArray(normalized.recommended_tools)) normalized.recommended_tools = normalized.recommended_tools.map((tool)=>'object' == typeof tool && null !== tool ? normalizeRecommendation(tool) : tool);
|
|
57
57
|
if (Array.isArray(normalized.recommended_skills)) normalized.recommended_skills = normalized.recommended_skills.map((skill)=>'object' == typeof skill && null !== skill ? normalizeRecommendation(skill) : skill);
|
|
58
|
+
if ('string' == typeof normalized.step_description) normalized.step_description = sanitizeStepField(normalized.step_description);
|
|
59
|
+
if ('string' == typeof normalized.expected_outcome) normalized.expected_outcome = sanitizeStepField(normalized.expected_outcome);
|
|
60
|
+
if (Array.isArray(normalized.next_step_conditions)) normalized.next_step_conditions = normalized.next_step_conditions.map((cond)=>'string' == typeof cond ? sanitizeStepField(cond) : cond);
|
|
58
61
|
if (lenient && !('expected_outcome' in normalized)) normalized.expected_outcome = DEFAULT_STEP_OUTCOME;
|
|
59
62
|
return normalized;
|
|
60
63
|
}
|
|
@@ -86,6 +89,7 @@ function normalizeInput(input) {
|
|
|
86
89
|
}
|
|
87
90
|
if (!normalized.id || 'string' != typeof normalized.id) normalized.id = generateThoughtId();
|
|
88
91
|
normalizeReasoningFields(normalized);
|
|
92
|
+
if ('string' == typeof normalized.meta_observation) normalized.meta_observation = sanitizeStepField(normalized.meta_observation);
|
|
89
93
|
const sanitized = sanitizeRecursive(normalized);
|
|
90
94
|
return sanitized;
|
|
91
95
|
}
|
|
@@ -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, sanitizeRationale, sanitizeSuggestedInputs } 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\t// Fill in default rationale if missing, otherwise sanitize urgency phrases + cap length\n\tif (!('rationale' in normalized) || normalized.rationale === undefined) {\n\t\tnormalized.rationale = DEFAULT_RECOMMENDATION_RATIONALE;\n\t} else if (typeof normalized.rationale === 'string') {\n\t\tnormalized.rationale = sanitizeRationale(normalized.rationale);\n\t}\n\t// Sanitize suggested_inputs: enforce flat primitives, key cap, value-length cap\n\tif (\n\t\tnormalized.suggested_inputs &&\n\t\ttypeof normalized.suggested_inputs === 'object' &&\n\t\t!Array.isArray(normalized.suggested_inputs)\n\t) {\n\t\tnormalized.suggested_inputs = sanitizeSuggestedInputs(\n\t\t\tnormalized.suggested_inputs as Record<string, unknown>,\n\t\t);\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","sanitizeRationale","sanitizeSuggestedInputs","normalizeStepRecommendation","step","lenient","tool","skill","normalizeReasoningFields","input","Math","v","Number","id","normalizeInput","sanitized","asSessionId","generateThoughtId"],"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, sanitizeRationale, sanitizeStepField, sanitizeSuggestedInputs } 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\t// Fill in default rationale if missing, otherwise sanitize urgency phrases + cap length\n\tif (!('rationale' in normalized) || normalized.rationale === undefined) {\n\t\tnormalized.rationale = DEFAULT_RECOMMENDATION_RATIONALE;\n\t} else if (typeof normalized.rationale === 'string') {\n\t\tnormalized.rationale = sanitizeRationale(normalized.rationale);\n\t}\n\t// Sanitize suggested_inputs: enforce flat primitives, key cap, value-length cap\n\tif (\n\t\tnormalized.suggested_inputs &&\n\t\ttypeof normalized.suggested_inputs === 'object' &&\n\t\t!Array.isArray(normalized.suggested_inputs)\n\t) {\n\t\tnormalized.suggested_inputs = sanitizeSuggestedInputs(\n\t\t\tnormalized.suggested_inputs as Record<string, unknown>,\n\t\t);\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// Sanitize step-level string fields for prompt injection prevention\n\tif (typeof normalized.step_description === 'string') {\n\t\tnormalized.step_description = sanitizeStepField(normalized.step_description);\n\t}\n\tif (typeof normalized.expected_outcome === 'string') {\n\t\tnormalized.expected_outcome = sanitizeStepField(normalized.expected_outcome);\n\t}\n\tif (Array.isArray(normalized.next_step_conditions)) {\n\t\tnormalized.next_step_conditions = normalized.next_step_conditions.map(\n\t\t\t(cond: unknown) => (typeof cond === 'string' ? sanitizeStepField(cond) : cond),\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\t// Sanitize meta_observation for prompt injection prevention\n\tif (typeof normalized.meta_observation === 'string') {\n\t\tnormalized.meta_observation = sanitizeStepField(normalized.meta_observation);\n\t}\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","sanitizeRationale","sanitizeSuggestedInputs","normalizeStepRecommendation","step","lenient","tool","skill","sanitizeStepField","cond","normalizeReasoningFields","input","Math","v","Number","id","normalizeInput","sanitized","asSessionId","generateThoughtId"],"mappings":"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"}
|
package/dist/sanitize.d.ts
CHANGED
|
@@ -92,6 +92,20 @@ export declare class JsonShapeError extends Error {
|
|
|
92
92
|
* ```
|
|
93
93
|
*/
|
|
94
94
|
export declare function enforceJsonShape(value: unknown, opts?: EnforceJsonShapeOptions): void;
|
|
95
|
+
/**
|
|
96
|
+
* Strip urgency/imperative phrases from a string.
|
|
97
|
+
* These phrases could be used for prompt injection when reflected to a host LLM.
|
|
98
|
+
*
|
|
99
|
+
* @param input - The string to strip urgency phrases from
|
|
100
|
+
* @returns The string with urgency phrases replaced by '[redacted-urgency]'
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* stripUrgencyPhrases('URGENT: do this'); // '[redacted-urgency] do this'
|
|
105
|
+
* stripUrgencyPhrases('Best for web search'); // 'Best for web search'
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export declare function stripUrgencyPhrases(input: string): string;
|
|
95
109
|
/**
|
|
96
110
|
* Sanitize a rationale string by stripping urgency phrases, capping length,
|
|
97
111
|
* and applying standard string sanitization.
|
|
@@ -112,6 +126,23 @@ export declare function enforceJsonShape(value: unknown, opts?: EnforceJsonShape
|
|
|
112
126
|
export declare function sanitizeRationale(input: string, truncated?: {
|
|
113
127
|
value: boolean;
|
|
114
128
|
}): string;
|
|
129
|
+
/**
|
|
130
|
+
* Sanitize a step-level string field by stripping urgency phrases, capping length,
|
|
131
|
+
* and applying standard string sanitization.
|
|
132
|
+
*
|
|
133
|
+
* Applied to `step_description`, `expected_outcome`, `meta_observation`, and
|
|
134
|
+
* `next_step_conditions` items to prevent prompt injection through urgency language.
|
|
135
|
+
*
|
|
136
|
+
* @param input - The string to sanitize
|
|
137
|
+
* @returns The sanitized string
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```ts
|
|
141
|
+
* sanitizeStepField('You MUST RUN this tool'); // 'You [redacted-urgency] this tool'
|
|
142
|
+
* sanitizeStepField('Analyze the code'); // 'Analyze the code' (unchanged)
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
export declare function sanitizeStepField(input: string): string;
|
|
115
146
|
/**
|
|
116
147
|
* Sanitize suggested_inputs: cap string value lengths, strip control chars and dangerous tags.
|
|
117
148
|
*
|
package/dist/sanitize.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAiBH;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAgBD,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,cAAe,SAAQ,KAAK;IACxC,SAAgB,MAAM,EAAE,MAAM,CAAC;gBACnB,MAAM,EAAE,MAAM;CAK1B;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,GAAE,uBAA4B,GAAG,IAAI,CAoDzF;
|
|
1
|
+
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAiBH;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAgBD,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,cAAe,SAAQ,KAAK;IACxC,SAAgB,MAAM,EAAE,MAAM,CAAC;gBACnB,MAAM,EAAE,MAAM;CAK1B;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,GAAE,uBAA4B,GAAG,IAAI,CAoDzF;AASD;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEzD;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM,CAQvF;AAOD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOvD;AAYD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,uBAAuB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,CAuBlD"}
|
package/dist/sanitize.js
CHANGED
|
@@ -60,16 +60,26 @@ function enforceJsonShape(value, opts = {}) {
|
|
|
60
60
|
if (bytes > maxBytes) throw new JsonShapeError(`exceeds max serialized size of ${maxBytes} bytes (got ${bytes})`);
|
|
61
61
|
}
|
|
62
62
|
const URGENCY_PHRASES = /\b(URGENT(?:LY)?|IMMEDIATELY|MUST\s+RUN|CRITICAL:|ACTION\s+REQUIRED|DO\s+NOT\s+IGNORE|EXECUTE\s+NOW|RUN\s+THIS\s+NOW)/gi;
|
|
63
|
+
function stripUrgencyPhrases(input) {
|
|
64
|
+
return input.replace(URGENCY_PHRASES, '[redacted-urgency]');
|
|
65
|
+
}
|
|
63
66
|
const MAX_RATIONALE_LENGTH = 2000;
|
|
64
67
|
function sanitizeRationale(input, truncated) {
|
|
65
68
|
let result = sanitizeString(input);
|
|
66
|
-
result = result
|
|
69
|
+
result = stripUrgencyPhrases(result);
|
|
67
70
|
if (result.length > MAX_RATIONALE_LENGTH) {
|
|
68
71
|
result = result.slice(0, MAX_RATIONALE_LENGTH);
|
|
69
72
|
if (truncated) truncated.value = true;
|
|
70
73
|
}
|
|
71
74
|
return result;
|
|
72
75
|
}
|
|
76
|
+
const MAX_STEP_FIELD_LENGTH = 4000;
|
|
77
|
+
function sanitizeStepField(input) {
|
|
78
|
+
let result = sanitizeString(input);
|
|
79
|
+
result = stripUrgencyPhrases(result);
|
|
80
|
+
if (result.length > MAX_STEP_FIELD_LENGTH) result = result.slice(0, MAX_STEP_FIELD_LENGTH);
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
73
83
|
const MAX_SUGGESTED_INPUTS_KEYS = 32;
|
|
74
84
|
const MAX_SUGGESTED_INPUT_VALUE_LENGTH = 512;
|
|
75
85
|
function sanitizeSuggestedInputs(inputs) {
|
|
@@ -85,6 +95,6 @@ function sanitizeSuggestedInputs(inputs) {
|
|
|
85
95
|
}
|
|
86
96
|
return result;
|
|
87
97
|
}
|
|
88
|
-
export { JsonShapeError, enforceJsonShape, sanitizeRationale, sanitizeString, sanitizeSuggestedInputs, stripControlChars, stripDangerousTags };
|
|
98
|
+
export { JsonShapeError, enforceJsonShape, sanitizeRationale, sanitizeStepField, sanitizeString, sanitizeSuggestedInputs, stripControlChars, stripDangerousTags, stripUrgencyPhrases };
|
|
89
99
|
|
|
90
100
|
//# sourceMappingURL=sanitize.js.map
|
package/dist/sanitize.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanitize.js","sources":["../src/sanitize.ts"],"sourcesContent":["/**\n * Input sanitization for the sequential thinking MCP tool.\n *\n * Provides pure functions for stripping dangerous content from free-text fields.\n * Uses a targeted blocklist approach: only removes HTML tags that can execute code,\n * while preserving generic angle-bracket content like TypeScript generics (`Array<string>`),\n * mathematical comparisons (`x < 5`), and markdown formatting.\n *\n * @module sanitize\n */\n\n/**\n * Regex matching dangerous HTML tags that can execute JavaScript or load external resources.\n * Targets: script, iframe, img, style, svg, embed, object, link, meta, base, form.\n * Preserves: `Array<string>`, `x < 5 && y > 3`, `<details>`, `<code>`, `<pre>`, etc.\n */\nconst DANGEROUS_TAG_REGEX =\n\t/<\\/?(script|iframe|img|style|svg|embed|object|link|meta|base|form)(\\s[^>]*)?\\s*\\/?>/gi;\n\n/**\n * Null bytes and C0 control characters (except tab \\t, newline \\n, carriage return \\r).\n * These can cause truncation in C bindings, file I/O, and some databases.\n */\n// eslint-disable-next-line no-control-regex -- intentional: matches C0 control chars to strip them\nconst CONTROL_CHAR_REGEX = /[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F]/g;\n\n/**\n * Strip dangerous HTML tags that can execute JavaScript or load external resources.\n *\n * Uses a targeted blocklist to remove only tags known to be dangerous (script, iframe,\n * img, style, svg, embed, object, link, meta, base, form) while preserving safe\n * angle-bracket content like TypeScript generics and mathematical comparisons.\n *\n * @param input - The string to sanitize\n * @returns The input with dangerous HTML tags removed\n *\n * @example\n * ```ts\n * stripDangerousTags('<script>alert(1)</script>hello'); // 'hello'\n * stripDangerousTags('Array<string>'); // 'Array<string>' (preserved)\n * stripDangerousTags('x < 5 && y > 3'); // 'x < 5 && y > 3' (preserved)\n * ```\n */\nexport function stripDangerousTags(input: string): string {\n\treturn input.replace(DANGEROUS_TAG_REGEX, '');\n}\n\n/**\n * Strip null bytes and C0 control characters from a string.\n *\n * Removes characters in the range U+0000–U+0008, U+000B, U+000C, U+000E–U+001F.\n * Preserves tab (`\\t`, U+0009), newline (`\\n`, U+000A), and carriage return (`\\r`, U+000D)\n * as these are commonly used in thought content.\n *\n * @param input - The string to sanitize\n * @returns The input with control characters removed\n *\n * @example\n * ```ts\n * stripControlChars('a\\x00b'); // 'ab'\n * stripControlChars('a\\tb\\nc'); // 'a\\tb\\nc' (tab and newline preserved)\n * ```\n */\nexport function stripControlChars(input: string): string {\n\treturn input.replace(CONTROL_CHAR_REGEX, '');\n}\n\n/**\n * Sanitize a string by stripping both control characters and dangerous HTML tags.\n *\n * Composes {@link stripControlChars} and {@link stripDangerousTags} in sequence.\n * Does not trim whitespace — thought content may depend on leading/trailing spaces.\n * Always returns a string, even if the input is empty.\n *\n * @param input - The string to sanitize\n * @returns The fully sanitized string\n *\n * @example\n * ```ts\n * sanitizeString('<script>alert(1)</script>hello\\x00world'); // 'helloworld'\n * sanitizeString('Array<string>\\x00'); // 'Array<string>'\n * ```\n */\nexport function sanitizeString(input: string): string {\n\treturn stripDangerousTags(stripControlChars(input));\n}\n\n\n/**\n * Forbidden object keys that enable prototype pollution.\n * These keys allow attackers to inject properties onto Object.prototype,\n * affecting all subsequent objects in the runtime.\n */\nconst FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\n\n/**\n * Default options for {@link enforceJsonShape}.\n */\nconst DEFAULT_MAX_DEPTH = 8;\nconst DEFAULT_MAX_BYTES = 16384;\n\nexport interface EnforceJsonShapeOptions {\n\tmaxDepth?: number;\n\tmaxBytes?: number;\n}\n\nexport class JsonShapeError extends Error {\n\tpublic readonly reason: string;\n\tconstructor(reason: string) {\n\t\tsuper(reason);\n\t\tthis.name = 'JsonShapeError';\n\t\tthis.reason = reason;\n\t}\n}\n\n/**\n * Enforce safety constraints on a JSON-shaped value.\n *\n * Rejects:\n * - Prototype-pollution keys (`__proto__`, `constructor`, `prototype`) at any depth\n * - Nesting deeper than `maxDepth` (default 8)\n * - Serialized JSON byte length exceeding `maxBytes` (default 16384)\n * - Functions, symbols, and other non-JSON-safe values\n *\n * Used as a defense-in-depth gate for untrusted structured input such as\n * `tool_arguments` from an LLM.\n *\n * @param value - The value to validate\n * @param opts - Optional limits\n * @throws {JsonShapeError} when any constraint is violated\n *\n * @example\n * ```ts\n * enforceJsonShape({ q: 'hello' }); // ok\n * enforceJsonShape({ __proto__: { polluted: true } }); // throws\n * enforceJsonShape({ a: { b: { c: { d: { e: { f: { g: { h: { i: 1 } } } } } } } } }); // throws (depth)\n * ```\n */\nexport function enforceJsonShape(value: unknown, opts: EnforceJsonShapeOptions = {}): void {\n\tconst maxDepth = opts.maxDepth ?? DEFAULT_MAX_DEPTH;\n\tconst maxBytes = opts.maxBytes ?? DEFAULT_MAX_BYTES;\n\n\tconst seen = new WeakSet<object>();\n\n\tconst walk = (node: unknown, depth: number): void => {\n\t\tif (depth > maxDepth) {\n\t\t\tthrow new JsonShapeError(`exceeds max depth of ${maxDepth}`);\n\t\t}\n\t\tif (node === null) return;\n\t\tconst type = typeof node;\n\t\tif (type === 'string' || type === 'number' || type === 'boolean') return;\n\t\tif (type === 'undefined') return;\n\t\tif (type === 'function' || type === 'symbol' || type === 'bigint') {\n\t\t\tthrow new JsonShapeError(`unsupported value type '${type}'`);\n\t\t}\n\t\tif (type !== 'object') {\n\t\t\tthrow new JsonShapeError(`unsupported value type '${type}'`);\n\t\t}\n\t\tconst obj = node as object;\n\t\tif (seen.has(obj)) {\n\t\t\tthrow new JsonShapeError('circular reference detected');\n\t\t}\n\t\tseen.add(obj);\n\n\t\tif (Array.isArray(node)) {\n\t\t\tfor (const item of node) walk(item, depth + 1);\n\t\t\treturn;\n\t\t}\n\n\t\tfor (const key of Object.keys(obj)) {\n\t\t\tif (FORBIDDEN_KEYS.has(key)) {\n\t\t\t\tthrow new JsonShapeError(`forbidden key '${key}'`);\n\t\t\t}\n\t\t\twalk((obj as Record<string, unknown>)[key], depth + 1);\n\t\t}\n\t};\n\n\twalk(value, 0);\n\n\tlet serialized: string;\n\ttry {\n\t\tserialized = JSON.stringify(value);\n\t} catch {\n\t\tthrow new JsonShapeError('value is not JSON-serializable');\n\t}\n\tif (serialized === undefined) return;\n\tconst bytes = Buffer.byteLength(serialized, 'utf8');\n\tif (bytes > maxBytes) {\n\t\tthrow new JsonShapeError(`exceeds max serialized size of ${maxBytes} bytes (got ${bytes})`);\n\t}\n}\n\n/**\n * Urgency/imperative phrases that could be used for prompt injection.\n * Matched case-insensitively and replaced with [redacted-urgency].\n */\nconst URGENCY_PHRASES =\n\t/\\b(URGENT(?:LY)?|IMMEDIATELY|MUST\\s+RUN|CRITICAL:|ACTION\\s+REQUIRED|DO\\s+NOT\\s+IGNORE|EXECUTE\\s+NOW|RUN\\s+THIS\\s+NOW)/gi;\n\n/**\n * Maximum allowed length for rationale strings.\n */\nconst MAX_RATIONALE_LENGTH = 2000;\n\n/**\n * Sanitize a rationale string by stripping urgency phrases, capping length,\n * and applying standard string sanitization.\n *\n * Applied to `recommended_tools[].rationale` and `recommended_skills[].rationale`\n * to prevent prompt-injection via urgency language.\n *\n * @param input - The rationale string to sanitize\n * @param truncated - Optional object to receive truncation signal (sets `.value = true` if truncated)\n * @returns The sanitized rationale\n *\n * @example\n * ```ts\n * sanitizeRationale('URGENT: run this now'); // '[redacted-urgency] run this now'\n * sanitizeRationale('Best for web search'); // 'Best for web search' (unchanged)\n * ```\n */\nexport function sanitizeRationale(input: string, truncated?: { value: boolean }): string {\n\tlet result = sanitizeString(input);\n\tresult = result.replace(URGENCY_PHRASES, '[redacted-urgency]');\n\tif (result.length > MAX_RATIONALE_LENGTH) {\n\t\tresult = result.slice(0, MAX_RATIONALE_LENGTH);\n\t\tif (truncated) truncated.value = true;\n\t}\n\treturn result;\n}\n\n/**\n * Maximum number of keys allowed in suggested_inputs.\n */\nconst MAX_SUGGESTED_INPUTS_KEYS = 32;\n\n/**\n * Maximum string value length in suggested_inputs.\n */\nconst MAX_SUGGESTED_INPUT_VALUE_LENGTH = 512;\n\n/**\n * Sanitize suggested_inputs: cap string value lengths, strip control chars and dangerous tags.\n *\n * Only processes flat primitive values (string | number | boolean | null).\n * Non-primitive values (nested objects, arrays) are silently skipped — schema validation\n * is expected to reject them upstream.\n *\n * @param inputs - The suggested_inputs record to sanitize\n * @returns The sanitized record with cleaned string values\n * @throws {Error} if string value exceeds max length, or if more than 32 keys are present\n *\n * @example\n * ```ts\n * sanitizeSuggestedInputs({ url: 'x', limit: 5 }); // { url: 'x', limit: 5 }\n * sanitizeSuggestedInputs({ url: '<script>alert(1)</script>' }); // { url: 'alert(1)' }\n * ```\n */\nexport function sanitizeSuggestedInputs(\n\tinputs: Record<string, unknown>,\n): Record<string, string | number | boolean | null> {\n\tconst keys = Object.keys(inputs);\n\tif (keys.length > MAX_SUGGESTED_INPUTS_KEYS) {\n\t\tthrow new Error(\n\t\t\t`suggested_inputs exceeds max keys of ${MAX_SUGGESTED_INPUTS_KEYS} (got ${keys.length})`,\n\t\t);\n\t}\n\tconst result: Record<string, string | number | boolean | null> = {};\n\tfor (const key of keys) {\n\t\tconst value = inputs[key];\n\t\tif (typeof value === 'string') {\n\t\t\tif (value.length > MAX_SUGGESTED_INPUT_VALUE_LENGTH) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`suggested_inputs value for key '${key}' exceeds max length of ${MAX_SUGGESTED_INPUT_VALUE_LENGTH}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tresult[key] = sanitizeString(value);\n\t\t} else if (typeof value === 'number' || typeof value === 'boolean' || value === null) {\n\t\t\tresult[key] = value;\n\t\t}\n\t\t// Skip other types (shouldn't reach here if schema validates first)\n\t}\n\treturn result;\n}"],"names":["DANGEROUS_TAG_REGEX","CONTROL_CHAR_REGEX","stripDangerousTags","input","stripControlChars","sanitizeString","FORBIDDEN_KEYS","Set","DEFAULT_MAX_DEPTH","DEFAULT_MAX_BYTES","JsonShapeError","Error","reason","enforceJsonShape","value","opts","maxDepth","maxBytes","seen","WeakSet","walk","node","depth","type","obj","Array","item","key","Object","serialized","JSON","undefined","bytes","Buffer","URGENCY_PHRASES","MAX_RATIONALE_LENGTH","sanitizeRationale","truncated","result","MAX_SUGGESTED_INPUTS_KEYS","MAX_SUGGESTED_INPUT_VALUE_LENGTH","sanitizeSuggestedInputs","inputs","keys"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sanitize.js","sources":["../src/sanitize.ts"],"sourcesContent":["/**\n * Input sanitization for the sequential thinking MCP tool.\n *\n * Provides pure functions for stripping dangerous content from free-text fields.\n * Uses a targeted blocklist approach: only removes HTML tags that can execute code,\n * while preserving generic angle-bracket content like TypeScript generics (`Array<string>`),\n * mathematical comparisons (`x < 5`), and markdown formatting.\n *\n * @module sanitize\n */\n\n/**\n * Regex matching dangerous HTML tags that can execute JavaScript or load external resources.\n * Targets: script, iframe, img, style, svg, embed, object, link, meta, base, form.\n * Preserves: `Array<string>`, `x < 5 && y > 3`, `<details>`, `<code>`, `<pre>`, etc.\n */\nconst DANGEROUS_TAG_REGEX =\n\t/<\\/?(script|iframe|img|style|svg|embed|object|link|meta|base|form)(\\s[^>]*)?\\s*\\/?>/gi;\n\n/**\n * Null bytes and C0 control characters (except tab \\t, newline \\n, carriage return \\r).\n * These can cause truncation in C bindings, file I/O, and some databases.\n */\n// eslint-disable-next-line no-control-regex -- intentional: matches C0 control chars to strip them\nconst CONTROL_CHAR_REGEX = /[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F]/g;\n\n/**\n * Strip dangerous HTML tags that can execute JavaScript or load external resources.\n *\n * Uses a targeted blocklist to remove only tags known to be dangerous (script, iframe,\n * img, style, svg, embed, object, link, meta, base, form) while preserving safe\n * angle-bracket content like TypeScript generics and mathematical comparisons.\n *\n * @param input - The string to sanitize\n * @returns The input with dangerous HTML tags removed\n *\n * @example\n * ```ts\n * stripDangerousTags('<script>alert(1)</script>hello'); // 'hello'\n * stripDangerousTags('Array<string>'); // 'Array<string>' (preserved)\n * stripDangerousTags('x < 5 && y > 3'); // 'x < 5 && y > 3' (preserved)\n * ```\n */\nexport function stripDangerousTags(input: string): string {\n\treturn input.replace(DANGEROUS_TAG_REGEX, '');\n}\n\n/**\n * Strip null bytes and C0 control characters from a string.\n *\n * Removes characters in the range U+0000–U+0008, U+000B, U+000C, U+000E–U+001F.\n * Preserves tab (`\\t`, U+0009), newline (`\\n`, U+000A), and carriage return (`\\r`, U+000D)\n * as these are commonly used in thought content.\n *\n * @param input - The string to sanitize\n * @returns The input with control characters removed\n *\n * @example\n * ```ts\n * stripControlChars('a\\x00b'); // 'ab'\n * stripControlChars('a\\tb\\nc'); // 'a\\tb\\nc' (tab and newline preserved)\n * ```\n */\nexport function stripControlChars(input: string): string {\n\treturn input.replace(CONTROL_CHAR_REGEX, '');\n}\n\n/**\n * Sanitize a string by stripping both control characters and dangerous HTML tags.\n *\n * Composes {@link stripControlChars} and {@link stripDangerousTags} in sequence.\n * Does not trim whitespace — thought content may depend on leading/trailing spaces.\n * Always returns a string, even if the input is empty.\n *\n * @param input - The string to sanitize\n * @returns The fully sanitized string\n *\n * @example\n * ```ts\n * sanitizeString('<script>alert(1)</script>hello\\x00world'); // 'helloworld'\n * sanitizeString('Array<string>\\x00'); // 'Array<string>'\n * ```\n */\nexport function sanitizeString(input: string): string {\n\treturn stripDangerousTags(stripControlChars(input));\n}\n\n\n/**\n * Forbidden object keys that enable prototype pollution.\n * These keys allow attackers to inject properties onto Object.prototype,\n * affecting all subsequent objects in the runtime.\n */\nconst FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\n\n/**\n * Default options for {@link enforceJsonShape}.\n */\nconst DEFAULT_MAX_DEPTH = 8;\nconst DEFAULT_MAX_BYTES = 16384;\n\nexport interface EnforceJsonShapeOptions {\n\tmaxDepth?: number;\n\tmaxBytes?: number;\n}\n\nexport class JsonShapeError extends Error {\n\tpublic readonly reason: string;\n\tconstructor(reason: string) {\n\t\tsuper(reason);\n\t\tthis.name = 'JsonShapeError';\n\t\tthis.reason = reason;\n\t}\n}\n\n/**\n * Enforce safety constraints on a JSON-shaped value.\n *\n * Rejects:\n * - Prototype-pollution keys (`__proto__`, `constructor`, `prototype`) at any depth\n * - Nesting deeper than `maxDepth` (default 8)\n * - Serialized JSON byte length exceeding `maxBytes` (default 16384)\n * - Functions, symbols, and other non-JSON-safe values\n *\n * Used as a defense-in-depth gate for untrusted structured input such as\n * `tool_arguments` from an LLM.\n *\n * @param value - The value to validate\n * @param opts - Optional limits\n * @throws {JsonShapeError} when any constraint is violated\n *\n * @example\n * ```ts\n * enforceJsonShape({ q: 'hello' }); // ok\n * enforceJsonShape({ __proto__: { polluted: true } }); // throws\n * enforceJsonShape({ a: { b: { c: { d: { e: { f: { g: { h: { i: 1 } } } } } } } } }); // throws (depth)\n * ```\n */\nexport function enforceJsonShape(value: unknown, opts: EnforceJsonShapeOptions = {}): void {\n\tconst maxDepth = opts.maxDepth ?? DEFAULT_MAX_DEPTH;\n\tconst maxBytes = opts.maxBytes ?? DEFAULT_MAX_BYTES;\n\n\tconst seen = new WeakSet<object>();\n\n\tconst walk = (node: unknown, depth: number): void => {\n\t\tif (depth > maxDepth) {\n\t\t\tthrow new JsonShapeError(`exceeds max depth of ${maxDepth}`);\n\t\t}\n\t\tif (node === null) return;\n\t\tconst type = typeof node;\n\t\tif (type === 'string' || type === 'number' || type === 'boolean') return;\n\t\tif (type === 'undefined') return;\n\t\tif (type === 'function' || type === 'symbol' || type === 'bigint') {\n\t\t\tthrow new JsonShapeError(`unsupported value type '${type}'`);\n\t\t}\n\t\tif (type !== 'object') {\n\t\t\tthrow new JsonShapeError(`unsupported value type '${type}'`);\n\t\t}\n\t\tconst obj = node as object;\n\t\tif (seen.has(obj)) {\n\t\t\tthrow new JsonShapeError('circular reference detected');\n\t\t}\n\t\tseen.add(obj);\n\n\t\tif (Array.isArray(node)) {\n\t\t\tfor (const item of node) walk(item, depth + 1);\n\t\t\treturn;\n\t\t}\n\n\t\tfor (const key of Object.keys(obj)) {\n\t\t\tif (FORBIDDEN_KEYS.has(key)) {\n\t\t\t\tthrow new JsonShapeError(`forbidden key '${key}'`);\n\t\t\t}\n\t\t\twalk((obj as Record<string, unknown>)[key], depth + 1);\n\t\t}\n\t};\n\n\twalk(value, 0);\n\n\tlet serialized: string;\n\ttry {\n\t\tserialized = JSON.stringify(value);\n\t} catch {\n\t\tthrow new JsonShapeError('value is not JSON-serializable');\n\t}\n\tif (serialized === undefined) return;\n\tconst bytes = Buffer.byteLength(serialized, 'utf8');\n\tif (bytes > maxBytes) {\n\t\tthrow new JsonShapeError(`exceeds max serialized size of ${maxBytes} bytes (got ${bytes})`);\n\t}\n}\n\n/**\n * Urgency/imperative phrases that could be used for prompt injection.\n * Matched case-insensitively and replaced with [redacted-urgency].\n */\nconst URGENCY_PHRASES =\n\t/\\b(URGENT(?:LY)?|IMMEDIATELY|MUST\\s+RUN|CRITICAL:|ACTION\\s+REQUIRED|DO\\s+NOT\\s+IGNORE|EXECUTE\\s+NOW|RUN\\s+THIS\\s+NOW)/gi;\n\n/**\n * Strip urgency/imperative phrases from a string.\n * These phrases could be used for prompt injection when reflected to a host LLM.\n *\n * @param input - The string to strip urgency phrases from\n * @returns The string with urgency phrases replaced by '[redacted-urgency]'\n *\n * @example\n * ```ts\n * stripUrgencyPhrases('URGENT: do this'); // '[redacted-urgency] do this'\n * stripUrgencyPhrases('Best for web search'); // 'Best for web search'\n * ```\n */\nexport function stripUrgencyPhrases(input: string): string {\n\treturn input.replace(URGENCY_PHRASES, '[redacted-urgency]');\n}\n/**\n * Maximum allowed length for rationale strings.\n */\nconst MAX_RATIONALE_LENGTH = 2000;\n\n/**\n * Sanitize a rationale string by stripping urgency phrases, capping length,\n * and applying standard string sanitization.\n *\n * Applied to `recommended_tools[].rationale` and `recommended_skills[].rationale`\n * to prevent prompt-injection via urgency language.\n *\n * @param input - The rationale string to sanitize\n * @param truncated - Optional object to receive truncation signal (sets `.value = true` if truncated)\n * @returns The sanitized rationale\n *\n * @example\n * ```ts\n * sanitizeRationale('URGENT: run this now'); // '[redacted-urgency] run this now'\n * sanitizeRationale('Best for web search'); // 'Best for web search' (unchanged)\n * ```\n */\nexport function sanitizeRationale(input: string, truncated?: { value: boolean }): string {\n\tlet result = sanitizeString(input);\n\tresult = stripUrgencyPhrases(result);\n\tif (result.length > MAX_RATIONALE_LENGTH) {\n\t\tresult = result.slice(0, MAX_RATIONALE_LENGTH);\n\t\tif (truncated) truncated.value = true;\n\t}\n\treturn result;\n}\n\n/**\n * Maximum allowed length for step-level string fields (step_description, expected_outcome, meta_observation).\n */\nconst MAX_STEP_FIELD_LENGTH = 4000;\n\n/**\n * Sanitize a step-level string field by stripping urgency phrases, capping length,\n * and applying standard string sanitization.\n *\n * Applied to `step_description`, `expected_outcome`, `meta_observation`, and\n * `next_step_conditions` items to prevent prompt injection through urgency language.\n *\n * @param input - The string to sanitize\n * @returns The sanitized string\n *\n * @example\n * ```ts\n * sanitizeStepField('You MUST RUN this tool'); // 'You [redacted-urgency] this tool'\n * sanitizeStepField('Analyze the code'); // 'Analyze the code' (unchanged)\n * ```\n */\nexport function sanitizeStepField(input: string): string {\n\tlet result = sanitizeString(input);\n\tresult = stripUrgencyPhrases(result);\n\tif (result.length > MAX_STEP_FIELD_LENGTH) {\n\t\tresult = result.slice(0, MAX_STEP_FIELD_LENGTH);\n\t}\n\treturn result;\n}\n\n/**\n * Maximum number of keys allowed in suggested_inputs.\n */\nconst MAX_SUGGESTED_INPUTS_KEYS = 32;\n\n/**\n * Maximum string value length in suggested_inputs.\n */\nconst MAX_SUGGESTED_INPUT_VALUE_LENGTH = 512;\n\n/**\n * Sanitize suggested_inputs: cap string value lengths, strip control chars and dangerous tags.\n *\n * Only processes flat primitive values (string | number | boolean | null).\n * Non-primitive values (nested objects, arrays) are silently skipped — schema validation\n * is expected to reject them upstream.\n *\n * @param inputs - The suggested_inputs record to sanitize\n * @returns The sanitized record with cleaned string values\n * @throws {Error} if string value exceeds max length, or if more than 32 keys are present\n *\n * @example\n * ```ts\n * sanitizeSuggestedInputs({ url: 'x', limit: 5 }); // { url: 'x', limit: 5 }\n * sanitizeSuggestedInputs({ url: '<script>alert(1)</script>' }); // { url: 'alert(1)' }\n * ```\n */\nexport function sanitizeSuggestedInputs(\n\tinputs: Record<string, unknown>,\n): Record<string, string | number | boolean | null> {\n\tconst keys = Object.keys(inputs);\n\tif (keys.length > MAX_SUGGESTED_INPUTS_KEYS) {\n\t\tthrow new Error(\n\t\t\t`suggested_inputs exceeds max keys of ${MAX_SUGGESTED_INPUTS_KEYS} (got ${keys.length})`,\n\t\t);\n\t}\n\tconst result: Record<string, string | number | boolean | null> = {};\n\tfor (const key of keys) {\n\t\tconst value = inputs[key];\n\t\tif (typeof value === 'string') {\n\t\t\tif (value.length > MAX_SUGGESTED_INPUT_VALUE_LENGTH) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`suggested_inputs value for key '${key}' exceeds max length of ${MAX_SUGGESTED_INPUT_VALUE_LENGTH}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tresult[key] = sanitizeString(value);\n\t\t} else if (typeof value === 'number' || typeof value === 'boolean' || value === null) {\n\t\t\tresult[key] = value;\n\t\t}\n\t\t// Skip other types (shouldn't reach here if schema validates first)\n\t}\n\treturn result;\n}"],"names":["DANGEROUS_TAG_REGEX","CONTROL_CHAR_REGEX","stripDangerousTags","input","stripControlChars","sanitizeString","FORBIDDEN_KEYS","Set","DEFAULT_MAX_DEPTH","DEFAULT_MAX_BYTES","JsonShapeError","Error","reason","enforceJsonShape","value","opts","maxDepth","maxBytes","seen","WeakSet","walk","node","depth","type","obj","Array","item","key","Object","serialized","JSON","undefined","bytes","Buffer","URGENCY_PHRASES","stripUrgencyPhrases","MAX_RATIONALE_LENGTH","sanitizeRationale","truncated","result","MAX_STEP_FIELD_LENGTH","sanitizeStepField","MAX_SUGGESTED_INPUTS_KEYS","MAX_SUGGESTED_INPUT_VALUE_LENGTH","sanitizeSuggestedInputs","inputs","keys"],"mappings":"AAgBA,MAAMA,sBACL;AAOD,MAAMC,qBAAqB;AAmBpB,SAASC,mBAAmBC,KAAa;IAC/C,OAAOA,MAAM,OAAO,CAACH,qBAAqB;AAC3C;AAkBO,SAASI,kBAAkBD,KAAa;IAC9C,OAAOA,MAAM,OAAO,CAACF,oBAAoB;AAC1C;AAkBO,SAASI,eAAeF,KAAa;IAC3C,OAAOD,mBAAmBE,kBAAkBD;AAC7C;AAQA,MAAMG,iBAAiB,IAAIC,IAAI;IAAC;IAAa;IAAe;CAAY;AAKxE,MAAMC,oBAAoB;AAC1B,MAAMC,oBAAoB;AAOnB,MAAMC,uBAAuBC;IACnB,OAAe;IAC/B,YAAYC,MAAc,CAAE;QAC3B,KAAK,CAACA;QACN,IAAI,CAAC,IAAI,GAAG;QACZ,IAAI,CAAC,MAAM,GAAGA;IACf;AACD;AAyBO,SAASC,iBAAiBC,KAAc,EAAEC,OAAgC,CAAC,CAAC;IAClF,MAAMC,WAAWD,KAAK,QAAQ,IAAIP;IAClC,MAAMS,WAAWF,KAAK,QAAQ,IAAIN;IAElC,MAAMS,OAAO,IAAIC;IAEjB,MAAMC,OAAO,CAACC,MAAeC;QAC5B,IAAIA,QAAQN,UACX,MAAM,IAAIN,eAAe,CAAC,qBAAqB,EAAEM,UAAU;QAE5D,IAAIK,AAAS,SAATA,MAAe;QACnB,MAAME,OAAO,OAAOF;QACpB,IAAIE,AAAS,aAATA,QAAqBA,AAAS,aAATA,QAAqBA,AAAS,cAATA,MAAoB;QAClE,IAAIA,AAAS,gBAATA,MAAsB;QAC1B,IAAIA,AAAS,eAATA,QAAuBA,AAAS,aAATA,QAAqBA,AAAS,aAATA,MAC/C,MAAM,IAAIb,eAAe,CAAC,wBAAwB,EAAEa,KAAK,CAAC,CAAC;QAE5D,IAAIA,AAAS,aAATA,MACH,MAAM,IAAIb,eAAe,CAAC,wBAAwB,EAAEa,KAAK,CAAC,CAAC;QAE5D,MAAMC,MAAMH;QACZ,IAAIH,KAAK,GAAG,CAACM,MACZ,MAAM,IAAId,eAAe;QAE1BQ,KAAK,GAAG,CAACM;QAET,IAAIC,MAAM,OAAO,CAACJ,OAAO;YACxB,KAAK,MAAMK,QAAQL,KAAMD,KAAKM,MAAMJ,QAAQ;YAC5C;QACD;QAEA,KAAK,MAAMK,OAAOC,OAAO,IAAI,CAACJ,KAAM;YACnC,IAAIlB,eAAe,GAAG,CAACqB,MACtB,MAAM,IAAIjB,eAAe,CAAC,eAAe,EAAEiB,IAAI,CAAC,CAAC;YAElDP,KAAMI,GAA+B,CAACG,IAAI,EAAEL,QAAQ;QACrD;IACD;IAEAF,KAAKN,OAAO;IAEZ,IAAIe;IACJ,IAAI;QACHA,aAAaC,KAAK,SAAS,CAAChB;IAC7B,EAAE,OAAM;QACP,MAAM,IAAIJ,eAAe;IAC1B;IACA,IAAImB,AAAeE,WAAfF,YAA0B;IAC9B,MAAMG,QAAQC,OAAO,UAAU,CAACJ,YAAY;IAC5C,IAAIG,QAAQf,UACX,MAAM,IAAIP,eAAe,CAAC,+BAA+B,EAAEO,SAAS,YAAY,EAAEe,MAAM,CAAC,CAAC;AAE5F;AAMA,MAAME,kBACL;AAeM,SAASC,oBAAoBhC,KAAa;IAChD,OAAOA,MAAM,OAAO,CAAC+B,iBAAiB;AACvC;AAIA,MAAME,uBAAuB;AAmBtB,SAASC,kBAAkBlC,KAAa,EAAEmC,SAA8B;IAC9E,IAAIC,SAASlC,eAAeF;IAC5BoC,SAASJ,oBAAoBI;IAC7B,IAAIA,OAAO,MAAM,GAAGH,sBAAsB;QACzCG,SAASA,OAAO,KAAK,CAAC,GAAGH;QACzB,IAAIE,WAAWA,UAAU,KAAK,GAAG;IAClC;IACA,OAAOC;AACR;AAKA,MAAMC,wBAAwB;AAkBvB,SAASC,kBAAkBtC,KAAa;IAC9C,IAAIoC,SAASlC,eAAeF;IAC5BoC,SAASJ,oBAAoBI;IAC7B,IAAIA,OAAO,MAAM,GAAGC,uBACnBD,SAASA,OAAO,KAAK,CAAC,GAAGC;IAE1B,OAAOD;AACR;AAKA,MAAMG,4BAA4B;AAKlC,MAAMC,mCAAmC;AAmBlC,SAASC,wBACfC,MAA+B;IAE/B,MAAMC,OAAOlB,OAAO,IAAI,CAACiB;IACzB,IAAIC,KAAK,MAAM,GAAGJ,2BACjB,MAAM,IAAI/B,MACT,CAAC,qCAAqC,EAAE+B,0BAA0B,MAAM,EAAEI,KAAK,MAAM,CAAC,CAAC,CAAC;IAG1F,MAAMP,SAA2D,CAAC;IAClE,KAAK,MAAMZ,OAAOmB,KAAM;QACvB,MAAMhC,QAAQ+B,MAAM,CAAClB,IAAI;QACzB,IAAI,AAAiB,YAAjB,OAAOb,OAAoB;YAC9B,IAAIA,MAAM,MAAM,GAAG6B,kCAClB,MAAM,IAAIhC,MACT,CAAC,gCAAgC,EAAEgB,IAAI,wBAAwB,EAAEgB,kCAAkC;YAGrGJ,MAAM,CAACZ,IAAI,GAAGtB,eAAeS;QAC9B,OAAO,IAAI,AAAiB,YAAjB,OAAOA,SAAsB,AAAiB,aAAjB,OAAOA,SAAuBA,AAAU,SAAVA,OACrEyB,MAAM,CAACZ,IAAI,GAAGb;IAGhB;IACA,OAAOyB;AACR"}
|