@stackmemoryai/stackmemory 0.5.34 → 0.5.36

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/core/context/frame-stack.ts"],
4
- "sourcesContent": ["/**\n * Frame Stack Management\n * Handles the call stack of active frames\n */\n\nimport { Frame, FrameContext, FrameType } from './frame-types.js';\nimport { FrameDatabase } from './frame-database.js';\nimport { logger } from '../monitoring/logger.js';\nimport { FrameError, ErrorCode } from '../errors/index.js';\nimport { FrameQueryMode } from '../session/index.js';\n\nexport class FrameStack {\n private activeStack: string[] = [];\n private queryMode: FrameQueryMode = FrameQueryMode.PROJECT_ACTIVE;\n\n constructor(\n private frameDb: FrameDatabase,\n private projectId: string,\n private runId: string\n ) {}\n\n /**\n * Initialize stack by loading active frames\n */\n async initialize(): Promise<void> {\n try {\n const activeFrames = this.frameDb.getFramesByProject(\n this.projectId,\n 'active'\n );\n\n // Rebuild stack from database\n this.activeStack = this.buildStackFromFrames(activeFrames);\n\n logger.info('Frame stack initialized', {\n stackDepth: this.activeStack.length,\n projectId: this.projectId,\n });\n } catch (error: unknown) {\n logger.error('Failed to initialize frame stack', {\n error: error instanceof Error ? error.message : String(error),\n projectId: this.projectId,\n runId: this.runId,\n });\n throw new FrameError(\n 'Failed to initialize frame stack',\n ErrorCode.FRAME_INIT_FAILED,\n {\n projectId: this.projectId,\n runId: this.runId,\n originalError: error instanceof Error ? error.message : String(error),\n }\n );\n }\n }\n\n /**\n * Push new frame onto stack\n */\n pushFrame(frameId: string): void {\n if (this.activeStack.includes(frameId)) {\n logger.warn('Frame already on stack', { frameId });\n return;\n }\n\n this.activeStack.push(frameId);\n\n logger.debug('Pushed frame to stack', {\n frameId,\n stackDepth: this.activeStack.length,\n });\n }\n\n /**\n * Pop frame from stack\n */\n popFrame(frameId?: string): string | undefined {\n if (this.activeStack.length === 0) {\n return undefined;\n }\n\n let poppedFrameId: string | undefined;\n\n if (frameId) {\n // Pop specific frame (and all frames above it)\n const index = this.activeStack.indexOf(frameId);\n if (index === -1) {\n logger.warn('Frame not found on stack', { frameId });\n return undefined;\n }\n\n // Remove the target frame and all frames above it\n const removed = this.activeStack.splice(index);\n poppedFrameId = removed[0];\n\n if (removed.length > 1) {\n logger.info('Popped multiple frames due to stack unwinding', {\n targetFrame: frameId,\n removedFrames: removed,\n });\n }\n } else {\n // Pop top frame\n poppedFrameId = this.activeStack.pop();\n }\n\n if (poppedFrameId) {\n logger.debug('Popped frame from stack', {\n frameId: poppedFrameId,\n stackDepth: this.activeStack.length,\n });\n }\n\n return poppedFrameId;\n }\n\n /**\n * Get current (top) frame ID\n */\n getCurrentFrameId(): string | undefined {\n return this.activeStack[this.activeStack.length - 1];\n }\n\n /**\n * Get stack depth\n */\n getDepth(): number {\n return this.activeStack.length;\n }\n\n /**\n * Get complete stack\n */\n getStack(): string[] {\n return [...this.activeStack];\n }\n\n /**\n * Get stack as frame objects\n */\n getStackFrames(): Frame[] {\n return this.activeStack\n .map((frameId: any) => this.frameDb.getFrame(frameId))\n .filter(Boolean) as Frame[];\n }\n\n /**\n * Get frame context for the hot stack\n */\n getHotStackContext(maxEvents: number = 20): FrameContext[] {\n return this.activeStack\n .map((frameId: any) => this.buildFrameContext(frameId, maxEvents))\n .filter(Boolean) as FrameContext[];\n }\n\n /**\n * Check if frame is on stack\n */\n isFrameActive(frameId: string): boolean {\n return this.activeStack.includes(frameId);\n }\n\n /**\n * Get parent frame ID for current frame\n */\n getParentFrameId(): string | undefined {\n if (this.activeStack.length < 2) {\n return undefined;\n }\n return this.activeStack[this.activeStack.length - 2];\n }\n\n /**\n * Get frame depth on stack (0-based)\n */\n getFrameStackDepth(frameId: string): number {\n return this.activeStack.indexOf(frameId);\n }\n\n /**\n * Clear entire stack\n */\n clear(): void {\n const previousDepth = this.activeStack.length;\n this.activeStack = [];\n\n logger.info('Cleared frame stack', { previousDepth });\n }\n\n /**\n * Set query mode and reinitialize stack\n */\n setQueryMode(mode: FrameQueryMode): void {\n this.queryMode = mode;\n // Reinitialize with new query mode\n this.initialize().catch((error) => {\n logger.warn('Failed to reinitialize stack with new query mode', {\n mode,\n error,\n });\n });\n }\n\n /**\n * Remove a specific frame from the stack without popping frames above it\n */\n removeFrame(frameId: string): boolean {\n const index = this.activeStack.indexOf(frameId);\n if (index === -1) {\n return false;\n }\n\n this.activeStack.splice(index, 1);\n\n logger.debug('Removed frame from stack', {\n frameId,\n stackDepth: this.activeStack.length,\n });\n\n return true;\n }\n\n /**\n * Validate stack consistency\n */\n validateStack(): { isValid: boolean; errors: string[] } {\n const errors: string[] = [];\n\n // Check if all frames in stack exist and are active\n for (const frameId of this.activeStack) {\n const frame = this.frameDb.getFrame(frameId);\n\n if (!frame) {\n errors.push(`Frame not found in database: ${frameId}`);\n continue;\n }\n\n if (frame.state !== 'active') {\n errors.push(\n `Frame on stack is not active: ${frameId} (state: ${frame.state})`\n );\n }\n\n if (frame.project_id !== this.projectId) {\n errors.push(`Frame belongs to different project: ${frameId}`);\n }\n }\n\n // Check for parent-child consistency\n for (let i = 1; i < this.activeStack.length; i++) {\n const currentFrameId = this.activeStack[i];\n const expectedParentId = this.activeStack[i - 1];\n const currentFrame = this.frameDb.getFrame(currentFrameId);\n\n if (currentFrame?.parent_frame_id !== expectedParentId) {\n errors.push(\n `Frame parent mismatch: ${currentFrameId} parent should be ${expectedParentId} but is ${currentFrame?.parent_frame_id}`\n );\n }\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n };\n }\n\n /**\n * Build frame context for a specific frame\n */\n private buildFrameContext(\n frameId: string,\n maxEvents: number\n ): FrameContext | null {\n try {\n const frame = this.frameDb.getFrame(frameId);\n if (!frame) {\n logger.warn('Frame not found for context building', { frameId });\n return null;\n }\n\n const anchors = this.frameDb.getFrameAnchors(frameId);\n const recentEvents = this.frameDb.getFrameEvents(frameId, maxEvents);\n const activeArtifacts = this.extractActiveArtifacts(recentEvents);\n\n return {\n frameId,\n header: {\n goal: frame.name,\n constraints: this.extractConstraints(frame.inputs),\n definitions: frame.inputs.definitions,\n },\n anchors,\n recentEvents,\n activeArtifacts,\n };\n } catch (error: unknown) {\n logger.warn('Failed to build frame context', { frameId, error });\n return null;\n }\n }\n\n /**\n * Extract constraints from frame inputs\n */\n private extractConstraints(inputs: Record<string, any>): string[] {\n const constraints: string[] = [];\n\n if (inputs.constraints && Array.isArray(inputs.constraints)) {\n constraints.push(...inputs.constraints);\n }\n\n return constraints;\n }\n\n /**\n * Extract active artifacts from events\n */\n private extractActiveArtifacts(events: any[]): string[] {\n const artifacts: string[] = [];\n\n for (const event of events) {\n if (event.event_type === 'artifact' && event.payload?.path) {\n artifacts.push(event.payload.path);\n }\n }\n\n // Return unique artifacts\n return [...new Set(artifacts)];\n }\n\n /**\n * Build stack order from database frames\n */\n private buildStackFromFrames(frames: Frame[]): string[] {\n if (frames.length === 0) {\n return [];\n }\n\n // Create parent-child map\n const parentMap = new Map<string, string>();\n const frameMap = new Map<string, Frame>();\n\n for (const frame of frames) {\n frameMap.set(frame.frame_id, frame);\n if (frame.parent_frame_id) {\n parentMap.set(frame.frame_id, frame.parent_frame_id);\n }\n }\n\n // Find root frames (no parent or parent not in active set)\n const rootFrames = frames.filter(\n (f: any) => !f.parent_frame_id || !frameMap.has(f.parent_frame_id)\n );\n\n if (rootFrames.length === 0) {\n logger.warn('No root frames found in active set');\n return [];\n }\n\n if (rootFrames.length > 1) {\n logger.warn('Multiple root frames found, using most recent', {\n rootFrames: rootFrames.map((f: any) => f.frame_id),\n });\n }\n\n // Build stack from root to leaves\n const stack: string[] = [];\n let currentFrame = rootFrames.sort(\n (a, b) => a.created_at - b.created_at\n )[0];\n\n while (currentFrame) {\n stack.push(currentFrame.frame_id);\n\n // Find child frame\n const childFrame = frames.find(\n (f: any) => f.parent_frame_id === currentFrame.frame_id\n );\n if (childFrame) {\n currentFrame = childFrame;\n } else {\n break;\n }\n }\n\n return stack;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Frame Stack Management\n * Handles the call stack of active frames\n */\n\nimport { Frame, FrameContext, FrameType } from './frame-types.js';\nimport { FrameDatabase } from './frame-database.js';\nimport { logger } from '../monitoring/logger.js';\nimport { FrameError, ErrorCode } from '../errors/index.js';\nimport { FrameQueryMode } from '../session/index.js';\n\nexport class FrameStack {\n private activeStack: string[] = [];\n private queryMode: FrameQueryMode = FrameQueryMode.PROJECT_ACTIVE;\n\n constructor(\n private frameDb: FrameDatabase,\n private projectId: string,\n private runId: string\n ) {}\n\n /**\n * Initialize stack by loading active frames\n */\n async initialize(): Promise<void> {\n try {\n const activeFrames = this.frameDb.getFramesByProject(\n this.projectId,\n 'active'\n );\n\n // Rebuild stack from database\n this.activeStack = this.buildStackFromFrames(activeFrames);\n\n logger.info('Frame stack initialized', {\n stackDepth: this.activeStack.length,\n projectId: this.projectId,\n });\n } catch (error: unknown) {\n logger.error('Failed to initialize frame stack', {\n error: error instanceof Error ? error.message : String(error),\n projectId: this.projectId,\n runId: this.runId,\n });\n throw new FrameError(\n 'Failed to initialize frame stack',\n ErrorCode.FRAME_INIT_FAILED,\n {\n projectId: this.projectId,\n runId: this.runId,\n originalError: error instanceof Error ? error.message : String(error),\n }\n );\n }\n }\n\n /**\n * Push new frame onto stack\n */\n pushFrame(frameId: string): void {\n if (this.activeStack.includes(frameId)) {\n logger.warn('Frame already on stack', { frameId });\n return;\n }\n\n this.activeStack.push(frameId);\n\n logger.debug('Pushed frame to stack', {\n frameId,\n stackDepth: this.activeStack.length,\n });\n }\n\n /**\n * Pop frame from stack\n */\n popFrame(frameId?: string): string | undefined {\n if (this.activeStack.length === 0) {\n return undefined;\n }\n\n let poppedFrameId: string | undefined;\n\n if (frameId) {\n // Pop specific frame (and all frames above it)\n const index = this.activeStack.indexOf(frameId);\n if (index === -1) {\n logger.warn('Frame not found on stack', { frameId });\n return undefined;\n }\n\n // Remove the target frame and all frames above it\n const removed = this.activeStack.splice(index);\n poppedFrameId = removed[0];\n\n if (removed.length > 1) {\n logger.info('Popped multiple frames due to stack unwinding', {\n targetFrame: frameId,\n removedFrames: removed,\n });\n }\n } else {\n // Pop top frame\n poppedFrameId = this.activeStack.pop();\n }\n\n if (poppedFrameId) {\n logger.debug('Popped frame from stack', {\n frameId: poppedFrameId,\n stackDepth: this.activeStack.length,\n });\n }\n\n return poppedFrameId;\n }\n\n /**\n * Get current (top) frame ID\n */\n getCurrentFrameId(): string | undefined {\n return this.activeStack[this.activeStack.length - 1];\n }\n\n /**\n * Get stack depth\n */\n getDepth(): number {\n return this.activeStack.length;\n }\n\n /**\n * Get complete stack\n */\n getStack(): string[] {\n return [...this.activeStack];\n }\n\n /**\n * Get stack as frame objects\n */\n getStackFrames(): Frame[] {\n return this.activeStack\n .map((frameId) => this.frameDb.getFrame(frameId))\n .filter((f): f is Frame => f !== undefined);\n }\n\n /**\n * Get frame context for the hot stack\n */\n getHotStackContext(maxEvents: number = 20): FrameContext[] {\n return this.activeStack\n .map((frameId) => this.buildFrameContext(frameId, maxEvents))\n .filter((ctx): ctx is FrameContext => ctx !== null);\n }\n\n /**\n * Check if frame is on stack\n */\n isFrameActive(frameId: string): boolean {\n return this.activeStack.includes(frameId);\n }\n\n /**\n * Get parent frame ID for current frame\n */\n getParentFrameId(): string | undefined {\n if (this.activeStack.length < 2) {\n return undefined;\n }\n return this.activeStack[this.activeStack.length - 2];\n }\n\n /**\n * Get frame depth on stack (0-based)\n */\n getFrameStackDepth(frameId: string): number {\n return this.activeStack.indexOf(frameId);\n }\n\n /**\n * Clear entire stack\n */\n clear(): void {\n const previousDepth = this.activeStack.length;\n this.activeStack = [];\n\n logger.info('Cleared frame stack', { previousDepth });\n }\n\n /**\n * Set query mode and reinitialize stack\n */\n setQueryMode(mode: FrameQueryMode): void {\n this.queryMode = mode;\n // Reinitialize with new query mode\n this.initialize().catch((error) => {\n logger.warn('Failed to reinitialize stack with new query mode', {\n mode,\n error,\n });\n });\n }\n\n /**\n * Remove a specific frame from the stack without popping frames above it\n */\n removeFrame(frameId: string): boolean {\n const index = this.activeStack.indexOf(frameId);\n if (index === -1) {\n return false;\n }\n\n this.activeStack.splice(index, 1);\n\n logger.debug('Removed frame from stack', {\n frameId,\n stackDepth: this.activeStack.length,\n });\n\n return true;\n }\n\n /**\n * Validate stack consistency\n */\n validateStack(): { isValid: boolean; errors: string[] } {\n const errors: string[] = [];\n\n // Check if all frames in stack exist and are active\n for (const frameId of this.activeStack) {\n const frame = this.frameDb.getFrame(frameId);\n\n if (!frame) {\n errors.push(`Frame not found in database: ${frameId}`);\n continue;\n }\n\n if (frame.state !== 'active') {\n errors.push(\n `Frame on stack is not active: ${frameId} (state: ${frame.state})`\n );\n }\n\n if (frame.project_id !== this.projectId) {\n errors.push(`Frame belongs to different project: ${frameId}`);\n }\n }\n\n // Check for parent-child consistency\n for (let i = 1; i < this.activeStack.length; i++) {\n const currentFrameId = this.activeStack[i];\n const expectedParentId = this.activeStack[i - 1];\n const currentFrame = this.frameDb.getFrame(currentFrameId);\n\n if (currentFrame?.parent_frame_id !== expectedParentId) {\n errors.push(\n `Frame parent mismatch: ${currentFrameId} parent should be ${expectedParentId} but is ${currentFrame?.parent_frame_id}`\n );\n }\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n };\n }\n\n /**\n * Build frame context for a specific frame\n */\n private buildFrameContext(\n frameId: string,\n maxEvents: number\n ): FrameContext | null {\n try {\n const frame = this.frameDb.getFrame(frameId);\n if (!frame) {\n logger.warn('Frame not found for context building', { frameId });\n return null;\n }\n\n const anchors = this.frameDb.getFrameAnchors(frameId);\n const recentEvents = this.frameDb.getFrameEvents(frameId, maxEvents);\n const activeArtifacts = this.extractActiveArtifacts(recentEvents);\n\n return {\n frameId,\n header: {\n goal: frame.name,\n constraints: this.extractConstraints(frame.inputs),\n definitions: frame.inputs.definitions,\n },\n anchors,\n recentEvents,\n activeArtifacts,\n };\n } catch (error: unknown) {\n logger.warn('Failed to build frame context', { frameId, error });\n return null;\n }\n }\n\n /**\n * Extract constraints from frame inputs\n */\n private extractConstraints(inputs: Record<string, unknown>): string[] {\n const constraints: string[] = [];\n\n if (inputs.constraints && Array.isArray(inputs.constraints)) {\n constraints.push(...(inputs.constraints as string[]));\n }\n\n return constraints;\n }\n\n /**\n * Extract active artifacts from events\n */\n private extractActiveArtifacts(events: Event[]): string[] {\n const artifacts: string[] = [];\n\n for (const event of events) {\n const payload = event.payload as Record<string, unknown>;\n if (event.event_type === 'artifact' && payload?.path) {\n artifacts.push(payload.path as string);\n }\n }\n\n // Return unique artifacts\n return [...new Set(artifacts)];\n }\n\n /**\n * Build stack order from database frames\n */\n private buildStackFromFrames(frames: Frame[]): string[] {\n if (frames.length === 0) {\n return [];\n }\n\n // Create parent-child map\n const parentMap = new Map<string, string>();\n const frameMap = new Map<string, Frame>();\n\n for (const frame of frames) {\n frameMap.set(frame.frame_id, frame);\n if (frame.parent_frame_id) {\n parentMap.set(frame.frame_id, frame.parent_frame_id);\n }\n }\n\n // Find root frames (no parent or parent not in active set)\n const rootFrames = frames.filter(\n (f) => !f.parent_frame_id || !frameMap.has(f.parent_frame_id)\n );\n\n if (rootFrames.length === 0) {\n logger.warn('No root frames found in active set');\n return [];\n }\n\n if (rootFrames.length > 1) {\n logger.warn('Multiple root frames found, using most recent', {\n rootFrames: rootFrames.map((f) => f.frame_id),\n });\n }\n\n // Build stack from root to leaves\n const stack: string[] = [];\n let currentFrame: Frame | undefined = rootFrames.sort(\n (a, b) => a.created_at - b.created_at\n )[0];\n\n while (currentFrame) {\n stack.push(currentFrame.frame_id);\n\n // Find child frame\n const parentId = currentFrame.frame_id;\n const childFrame = frames.find((f) => f.parent_frame_id === parentId);\n if (childFrame) {\n currentFrame = childFrame;\n } else {\n break;\n }\n }\n\n return stack;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -369,9 +369,17 @@ class RefactoredFrameManager {
369
369
  return this.frameDb.getStatistics();
370
370
  }
371
371
  /**
372
- * Close all child frames recursively
372
+ * Close all child frames recursively with depth limit to prevent stack overflow
373
373
  */
374
- closeChildFrames(parentFrameId) {
374
+ closeChildFrames(parentFrameId, depth = 0) {
375
+ if (depth > this.maxFrameDepth) {
376
+ logger.warn("closeChildFrames depth limit exceeded", {
377
+ parentFrameId,
378
+ depth,
379
+ maxDepth: this.maxFrameDepth
380
+ });
381
+ return;
382
+ }
375
383
  try {
376
384
  const activeFrames = this.frameDb.getFramesByProject(
377
385
  this.projectId,
@@ -382,13 +390,32 @@ class RefactoredFrameManager {
382
390
  );
383
391
  for (const childFrame of childFrames) {
384
392
  if (this.frameStack.isFrameActive(childFrame.frame_id)) {
385
- this.closeFrame(childFrame.frame_id);
393
+ this.closeChildFrames(childFrame.frame_id, depth + 1);
394
+ this.closeFrameDirectly(childFrame.frame_id);
386
395
  }
387
396
  }
388
397
  } catch (error) {
389
398
  logger.warn("Failed to close child frames", { parentFrameId, error });
390
399
  }
391
400
  }
401
+ /**
402
+ * Close a frame directly without triggering child frame closure
403
+ * Used by closeChildFrames to avoid double recursion
404
+ */
405
+ closeFrameDirectly(frameId) {
406
+ const frame = this.frameDb.getFrame(frameId);
407
+ if (!frame || frame.state === "closed") return;
408
+ const digest = this.digestGenerator.generateDigest(frameId);
409
+ this.frameDb.updateFrame(frameId, {
410
+ state: "closed",
411
+ outputs: digest.structured,
412
+ digest_text: digest.text,
413
+ digest_json: digest.structured,
414
+ closed_at: Math.floor(Date.now() / 1e3)
415
+ });
416
+ this.frameStack.popFrame(frameId);
417
+ logger.debug("Closed child frame directly", { frameId });
418
+ }
392
419
  /**
393
420
  * Extract active artifacts from frame events
394
421
  */
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/core/context/refactored-frame-manager.ts"],
4
- "sourcesContent": ["/**\n * Refactored Frame Manager - Modular Implementation\n * Main orchestrator that uses focused modules for frame management\n */\n\nimport Database from 'better-sqlite3';\nimport { v4 as uuidv4 } from 'uuid';\nimport { logger } from '../monitoring/logger.js';\nimport { trace } from '../trace/index.js';\nimport {\n FrameError,\n SystemError,\n ErrorCode,\n wrapError,\n createErrorHandler,\n} from '../errors/index.js';\nimport { retry, withTimeout } from '../errors/recovery.js';\nimport { sessionManager, FrameQueryMode } from '../session/index.js';\nimport { frameLifecycleHooks } from './frame-lifecycle-hooks.js';\n\n// Constants for frame validation\nconst MAX_FRAME_DEPTH = 100; // Maximum allowed frame depth\nconst DEFAULT_MAX_DEPTH = 100; // Default if not configured\n\n// Import refactored modules\nimport {\n Frame,\n FrameContext,\n Anchor,\n Event,\n FrameType,\n FrameState,\n FrameCreationOptions,\n FrameManagerConfig,\n DigestResult,\n} from './frame-types.js';\nimport { FrameDatabase } from './frame-database.js';\nimport { FrameStack } from './frame-stack.js';\nimport { FrameDigestGenerator } from './frame-digest.js';\n\nexport class RefactoredFrameManager {\n private frameDb: FrameDatabase;\n private frameStack: FrameStack;\n private digestGenerator: FrameDigestGenerator;\n\n private currentRunId: string;\n private sessionId: string;\n private projectId: string;\n private queryMode: FrameQueryMode = FrameQueryMode.PROJECT_ACTIVE;\n private config: FrameManagerConfig;\n private maxFrameDepth: number = DEFAULT_MAX_DEPTH;\n\n constructor(\n db: Database.Database,\n projectId: string,\n config?: Partial<FrameManagerConfig>\n ) {\n this.projectId = projectId;\n this.config = {\n projectId,\n runId: config?.runId || uuidv4(),\n sessionId: config?.sessionId || uuidv4(),\n maxStackDepth: config?.maxStackDepth || 50,\n };\n \n // Set max frame depth from config if provided\n this.maxFrameDepth = config?.maxStackDepth || DEFAULT_MAX_DEPTH;\n\n this.currentRunId = this.config.runId!;\n this.sessionId = this.config.sessionId!;\n\n // Initialize modules\n this.frameDb = new FrameDatabase(db);\n this.frameStack = new FrameStack(\n this.frameDb,\n projectId,\n this.currentRunId\n );\n this.digestGenerator = new FrameDigestGenerator(this.frameDb);\n\n // Initialize database schema\n this.frameDb.initSchema();\n\n logger.info('RefactoredFrameManager initialized', {\n projectId: this.projectId,\n runId: this.currentRunId,\n sessionId: this.sessionId,\n });\n }\n\n /**\n * Initialize the frame manager\n */\n async initialize(): Promise<void> {\n try {\n await this.frameStack.initialize();\n\n logger.info('Frame manager initialization completed', {\n stackDepth: this.frameStack.getDepth(),\n });\n } catch (error: unknown) {\n throw new SystemError(\n 'Failed to initialize frame manager',\n ErrorCode.SYSTEM_INIT_FAILED,\n { projectId: this.projectId },\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Create a new frame\n */\n createFrame(options: FrameCreationOptions): string;\n createFrame(\n type: FrameType,\n name: string,\n inputs?: Record<string, any>,\n parentFrameId?: string\n ): string;\n createFrame(\n typeOrOptions: FrameType | FrameCreationOptions,\n name?: string,\n inputs?: Record<string, any>,\n parentFrameId?: string\n ): string {\n return trace.traceSync(\n 'function',\n 'FrameManager.createFrame',\n { typeOrOptions, name },\n () => this._createFrame(typeOrOptions, name, inputs, parentFrameId)\n );\n }\n\n private _createFrame(\n typeOrOptions: FrameType | FrameCreationOptions,\n name?: string,\n inputs?: Record<string, any>,\n parentFrameId?: string\n ): string {\n let frameOptions: FrameCreationOptions;\n\n // Handle both function signatures\n if (typeof typeOrOptions === 'string') {\n frameOptions = {\n type: typeOrOptions,\n name: name!,\n inputs: inputs || {},\n parentFrameId,\n };\n } else {\n frameOptions = typeOrOptions;\n }\n\n // Validate inputs\n if (!frameOptions.name || frameOptions.name.trim().length === 0) {\n throw new FrameError(\n 'Frame name is required',\n ErrorCode.FRAME_INVALID_INPUT,\n { frameOptions }\n );\n }\n\n // Check stack depth limit\n if (this.frameStack.getDepth() >= this.config.maxStackDepth!) {\n throw new FrameError(\n `Maximum stack depth reached: ${this.config.maxStackDepth}`,\n ErrorCode.FRAME_STACK_OVERFLOW,\n { currentDepth: this.frameStack.getDepth() }\n );\n }\n\n // Determine parent frame\n const resolvedParentId =\n frameOptions.parentFrameId || this.frameStack.getCurrentFrameId();\n \n // Get depth from parent frame, not from stack position\n let depth = 0;\n if (resolvedParentId) {\n const parentFrame = this.frameDb.getFrame(resolvedParentId);\n depth = parentFrame ? parentFrame.depth + 1 : 0;\n }\n\n // Check for depth limit\n if (depth > this.maxFrameDepth) {\n throw new FrameError(\n `Maximum frame depth exceeded: ${depth} > ${this.maxFrameDepth}`,\n ErrorCode.FRAME_STACK_OVERFLOW,\n {\n currentDepth: depth,\n maxDepth: this.maxFrameDepth,\n frameName: frameOptions.name,\n parentFrameId: resolvedParentId,\n }\n );\n }\n\n // Check for circular reference before creating frame\n if (resolvedParentId) {\n const cycle = this.detectCycle(uuidv4(), resolvedParentId);\n if (cycle) {\n throw new FrameError(\n `Circular reference detected in frame hierarchy`,\n ErrorCode.FRAME_CYCLE_DETECTED,\n {\n parentFrameId: resolvedParentId,\n cycle,\n frameName: frameOptions.name,\n }\n );\n }\n }\n\n // Create frame data\n const frameId = uuidv4();\n const frame: Omit<Frame, 'created_at' | 'closed_at'> = {\n frame_id: frameId,\n run_id: this.currentRunId,\n project_id: this.projectId,\n parent_frame_id: resolvedParentId,\n depth,\n type: frameOptions.type,\n name: frameOptions.name,\n state: 'active',\n inputs: frameOptions.inputs || {},\n outputs: {},\n digest_json: {},\n };\n\n // Insert into database\n const createdFrame = this.frameDb.insertFrame(frame);\n\n // Add to stack\n this.frameStack.pushFrame(frameId);\n\n logger.info('Created frame', {\n frameId,\n name: frameOptions.name,\n type: frameOptions.type,\n parentFrameId: resolvedParentId,\n stackDepth: this.frameStack.getDepth(),\n });\n\n return frameId;\n }\n\n /**\n * Close a frame and generate digest\n */\n closeFrame(frameId?: string, outputs?: Record<string, any>): void {\n trace.traceSync(\n 'function',\n 'FrameManager.closeFrame',\n { frameId, outputs },\n () => this._closeFrame(frameId, outputs)\n );\n }\n\n private _closeFrame(frameId?: string, outputs?: Record<string, any>): void {\n const targetFrameId = frameId || this.frameStack.getCurrentFrameId();\n if (!targetFrameId) {\n throw new FrameError(\n 'No active frame to close',\n ErrorCode.FRAME_INVALID_STATE,\n {\n operation: 'closeFrame',\n stackDepth: this.frameStack.getDepth(),\n }\n );\n }\n\n // Get frame details\n const frame = this.frameDb.getFrame(targetFrameId);\n if (!frame) {\n throw new FrameError(\n `Frame not found: ${targetFrameId}`,\n ErrorCode.FRAME_NOT_FOUND,\n {\n frameId: targetFrameId,\n operation: 'closeFrame',\n runId: this.currentRunId,\n }\n );\n }\n\n if (frame.state === 'closed') {\n logger.warn('Attempted to close already closed frame', {\n frameId: targetFrameId,\n });\n return;\n }\n\n // Generate digest before closing\n const digest = this.digestGenerator.generateDigest(targetFrameId);\n const finalOutputs = { ...outputs, ...digest.structured };\n\n // Update frame to closed state\n this.frameDb.updateFrame(targetFrameId, {\n state: 'closed',\n outputs: finalOutputs,\n digest_text: digest.text,\n digest_json: digest.structured,\n closed_at: Math.floor(Date.now() / 1000),\n });\n\n // Remove from stack (this will also remove any child frames)\n this.frameStack.popFrame(targetFrameId);\n\n // Close all child frames recursively\n this.closeChildFrames(targetFrameId);\n\n // Trigger lifecycle hooks (fire and forget)\n const events = this.frameDb.getFrameEvents(targetFrameId);\n const anchors = this.frameDb.getFrameAnchors(targetFrameId);\n frameLifecycleHooks\n .triggerClose({ frame: { ...frame, state: 'closed' }, events, anchors })\n .catch(() => {\n // Silently ignore errors - hooks are non-critical\n });\n\n logger.info('Closed frame', {\n frameId: targetFrameId,\n name: frame.name,\n duration: Math.floor(Date.now() / 1000) - frame.created_at,\n digestLength: digest.text.length,\n stackDepth: this.frameStack.getDepth(),\n });\n }\n\n /**\n * Add an event to the current frame\n */\n addEvent(\n eventType: Event['event_type'],\n payload: Record<string, any>,\n frameId?: string\n ): string {\n return trace.traceSync(\n 'function',\n 'FrameManager.addEvent',\n { eventType, frameId },\n () => this._addEvent(eventType, payload, frameId)\n );\n }\n\n private _addEvent(\n eventType: Event['event_type'],\n payload: Record<string, any>,\n frameId?: string\n ): string {\n const targetFrameId = frameId || this.frameStack.getCurrentFrameId();\n if (!targetFrameId) {\n throw new FrameError(\n 'No active frame for event',\n ErrorCode.FRAME_INVALID_STATE,\n {\n eventType,\n operation: 'addEvent',\n }\n );\n }\n\n const eventId = uuidv4();\n const sequence = this.frameDb.getNextEventSequence(targetFrameId);\n\n const event: Omit<Event, 'ts'> = {\n event_id: eventId,\n frame_id: targetFrameId,\n run_id: this.currentRunId,\n seq: sequence,\n event_type: eventType,\n payload,\n };\n\n const createdEvent = this.frameDb.insertEvent(event);\n\n logger.debug('Added event', {\n eventId,\n frameId: targetFrameId,\n eventType,\n sequence,\n });\n\n return eventId;\n }\n\n /**\n * Add an anchor (important fact) to current frame\n */\n addAnchor(\n type: Anchor['type'],\n text: string,\n priority: number = 5,\n metadata: Record<string, any> = {},\n frameId?: string\n ): string {\n return trace.traceSync(\n 'function',\n 'FrameManager.addAnchor',\n { type, frameId },\n () => this._addAnchor(type, text, priority, metadata, frameId)\n );\n }\n\n private _addAnchor(\n type: Anchor['type'],\n text: string,\n priority: number,\n metadata: Record<string, any>,\n frameId?: string\n ): string {\n const targetFrameId = frameId || this.frameStack.getCurrentFrameId();\n if (!targetFrameId) {\n throw new FrameError(\n 'No active frame for anchor',\n ErrorCode.FRAME_INVALID_STATE,\n {\n anchorType: type,\n operation: 'addAnchor',\n }\n );\n }\n\n const anchorId = uuidv4();\n const anchor: Omit<Anchor, 'created_at'> = {\n anchor_id: anchorId,\n frame_id: targetFrameId,\n type,\n text,\n priority,\n metadata,\n };\n\n const createdAnchor = this.frameDb.insertAnchor(anchor);\n\n logger.debug('Added anchor', {\n anchorId,\n frameId: targetFrameId,\n type,\n priority,\n });\n\n return anchorId;\n }\n\n /**\n * Get hot stack context\n */\n getHotStackContext(maxEvents: number = 20): FrameContext[] {\n return this.frameStack.getHotStackContext(maxEvents);\n }\n\n /**\n * Get active frame path (root to current)\n */\n getActiveFramePath(): Frame[] {\n return this.frameStack.getStackFrames();\n }\n\n /**\n * Get current frame ID\n */\n getCurrentFrameId(): string | undefined {\n return this.frameStack.getCurrentFrameId();\n }\n\n /**\n * Get stack depth\n */\n getStackDepth(): number {\n return this.frameStack.getDepth();\n }\n\n /**\n * Get frame by ID\n */\n getFrame(frameId: string): Frame | undefined {\n return this.frameDb.getFrame(frameId);\n }\n\n /**\n * Get frame events\n */\n getFrameEvents(frameId: string, limit?: number): Event[] {\n return this.frameDb.getFrameEvents(frameId, limit);\n }\n\n /**\n * Get frame anchors\n */\n getFrameAnchors(frameId: string): Anchor[] {\n return this.frameDb.getFrameAnchors(frameId);\n }\n\n /**\n * Generate digest for a frame\n */\n generateDigest(frameId: string): DigestResult {\n return this.digestGenerator.generateDigest(frameId);\n }\n\n /**\n * Validate stack consistency\n */\n validateStack(): { isValid: boolean; errors: string[] } {\n return this.frameStack.validateStack();\n }\n\n /**\n * Get database statistics\n */\n getStatistics(): Record<string, number> {\n return this.frameDb.getStatistics();\n }\n\n /**\n * Close all child frames recursively\n */\n private closeChildFrames(parentFrameId: string): void {\n try {\n const activeFrames = this.frameDb.getFramesByProject(\n this.projectId,\n 'active'\n );\n const childFrames = activeFrames.filter(\n (f: any) => f.parent_frame_id === parentFrameId\n );\n\n for (const childFrame of childFrames) {\n if (this.frameStack.isFrameActive(childFrame.frame_id)) {\n this.closeFrame(childFrame.frame_id);\n }\n }\n } catch (error: unknown) {\n logger.warn('Failed to close child frames', { parentFrameId, error });\n }\n }\n\n /**\n * Extract active artifacts from frame events\n */\n getActiveArtifacts(frameId: string): string[] {\n const events = this.frameDb.getFrameEvents(frameId);\n const artifacts: string[] = [];\n\n for (const event of events) {\n if (event.event_type === 'artifact' && event.payload.path) {\n artifacts.push(event.payload.path);\n }\n }\n\n return [...new Set(artifacts)];\n }\n\n /**\n * Extract constraints from frame inputs\n */\n extractConstraints(inputs: Record<string, any>): string[] {\n const constraints: string[] = [];\n\n if (inputs.constraints && Array.isArray(inputs.constraints)) {\n constraints.push(...inputs.constraints);\n }\n\n if (inputs.requirements && Array.isArray(inputs.requirements)) {\n constraints.push(...inputs.requirements);\n }\n\n if (inputs.limitations && Array.isArray(inputs.limitations)) {\n constraints.push(...inputs.limitations);\n }\n\n return constraints;\n }\n\n /**\n * Detect if setting a parent frame would create a cycle in the frame hierarchy.\n * Returns the cycle path if detected, or null if no cycle.\n * @param childFrameId - The frame that would be the child\n * @param parentFrameId - The proposed parent frame\n * @returns Array of frame IDs forming the cycle, or null if no cycle\n */\n private detectCycle(\n childFrameId: string,\n parentFrameId: string\n ): string[] | null {\n const visited = new Set<string>();\n const path: string[] = [];\n\n // Start from the proposed parent and traverse up the hierarchy\n let currentId: string | undefined = parentFrameId;\n \n while (currentId) {\n // If we've seen this frame before, we have a cycle\n if (visited.has(currentId)) {\n // Build the cycle path\n const cycleStart = path.indexOf(currentId);\n return path.slice(cycleStart).concat(currentId);\n }\n\n // If the current frame is the child we're trying to add, it's a cycle\n if (currentId === childFrameId) {\n return path.concat([currentId, childFrameId]);\n }\n\n visited.add(currentId);\n path.push(currentId);\n\n // Move to the parent of current frame\n const frame = this.frameDb.getFrame(currentId);\n if (!frame) {\n // Frame not found, no cycle possible through this path\n break;\n }\n currentId = frame.parent_frame_id;\n\n // Safety check: if we've traversed too many levels, something is wrong\n if (path.length > this.maxFrameDepth) {\n throw new FrameError(\n `Frame hierarchy traversal exceeded maximum depth during cycle detection`,\n ErrorCode.FRAME_STACK_OVERFLOW,\n {\n depth: path.length,\n maxDepth: this.maxFrameDepth,\n path,\n }\n );\n }\n }\n\n return null; // No cycle detected\n }\n\n /**\n * Update parent frame of an existing frame (with cycle detection)\n * @param frameId - The frame to update\n * @param newParentFrameId - The new parent frame ID (null to make it a root frame)\n */\n public updateParentFrame(frameId: string, newParentFrameId: string | null): void {\n // Check if frame exists\n const frame = this.frameDb.getFrame(frameId);\n if (!frame) {\n throw new FrameError(\n `Frame not found: ${frameId}`,\n ErrorCode.FRAME_NOT_FOUND,\n { frameId }\n );\n }\n\n // If setting a parent, validate and check for cycles\n if (newParentFrameId) {\n // Verify the new parent exists\n const newParentFrame = this.frameDb.getFrame(newParentFrameId);\n if (!newParentFrame) {\n throw new FrameError(\n `Parent frame not found: ${newParentFrameId}`,\n ErrorCode.FRAME_NOT_FOUND,\n { frameId, newParentFrameId }\n );\n }\n\n const cycle = this.detectCycle(frameId, newParentFrameId);\n if (cycle) {\n throw new FrameError(\n `Cannot set parent: would create circular reference`,\n ErrorCode.FRAME_CYCLE_DETECTED,\n {\n frameId,\n newParentFrameId,\n cycle,\n currentParentId: frame.parent_frame_id,\n }\n );\n }\n\n // Check depth after parent change\n const newDepth = newParentFrame.depth + 1;\n if (newDepth > this.maxFrameDepth) {\n throw new FrameError(\n `Cannot set parent: would exceed maximum frame depth`,\n ErrorCode.FRAME_STACK_OVERFLOW,\n {\n frameId,\n newParentFrameId,\n newDepth,\n maxDepth: this.maxFrameDepth,\n }\n );\n }\n }\n\n // Calculate new depth based on parent\n let newDepth = 0;\n if (newParentFrameId) {\n const newParentFrame = this.frameDb.getFrame(newParentFrameId);\n if (newParentFrame) {\n newDepth = newParentFrame.depth + 1;\n }\n }\n\n // Update the frame's parent and depth\n this.frameDb.updateFrame(frameId, {\n parent_frame_id: newParentFrameId,\n depth: newDepth,\n });\n\n logger.info('Updated parent frame', {\n frameId,\n oldParentId: frame.parent_frame_id,\n newParentId: newParentFrameId,\n });\n }\n\n /**\n * Validate the entire frame hierarchy for cycles and depth violations\n * @returns Validation result with any detected issues\n */\n public validateFrameHierarchy(): {\n isValid: boolean;\n errors: string[];\n warnings: string[];\n } {\n const errors: string[] = [];\n const warnings: string[] = [];\n const allFrames = this.frameDb.getFramesByProject(this.projectId);\n \n // Check each frame for depth violations\n for (const frame of allFrames) {\n if (frame.depth > this.maxFrameDepth) {\n errors.push(\n `Frame ${frame.frame_id} exceeds max depth: ${frame.depth} > ${this.maxFrameDepth}`\n );\n }\n \n // Warn about deep frames approaching the limit\n if (frame.depth > this.maxFrameDepth * 0.8) {\n warnings.push(\n `Frame ${frame.frame_id} is deep in hierarchy: ${frame.depth}/${this.maxFrameDepth}`\n );\n }\n }\n \n // Check for cycles by traversing from each root\n const rootFrames = allFrames.filter(f => !f.parent_frame_id);\n const visited = new Set<string>();\n const visiting = new Set<string>();\n \n const checkForCycle = (frameId: string): boolean => {\n if (visiting.has(frameId)) {\n errors.push(`Cycle detected involving frame ${frameId}`);\n return true;\n }\n \n if (visited.has(frameId)) {\n return false;\n }\n \n visiting.add(frameId);\n \n // Check all children\n const children = allFrames.filter(f => f.parent_frame_id === frameId);\n for (const child of children) {\n if (checkForCycle(child.frame_id)) {\n return true;\n }\n }\n \n visiting.delete(frameId);\n visited.add(frameId);\n return false;\n };\n \n // Check from each root\n for (const root of rootFrames) {\n checkForCycle(root.frame_id);\n }\n \n return {\n isValid: errors.length === 0,\n errors,\n warnings,\n };\n }\n\n /**\n * Set query mode for frame retrieval\n */\n setQueryMode(mode: FrameQueryMode): void {\n this.queryMode = mode;\n // Reinitialize stack with new query mode\n this.frameStack.setQueryMode(mode);\n }\n\n /**\n * Get recent frames for context sharing\n */\n async getRecentFrames(limit: number = 100): Promise<Frame[]> {\n try {\n const frames = this.frameDb.getFramesByProject(this.projectId);\n\n // Sort by created_at descending and limit\n return frames\n .sort((a, b) => (b.created_at || 0) - (a.created_at || 0))\n .slice(0, limit)\n .map((frame) => ({\n ...frame,\n // Add compatibility fields\n frameId: frame.frame_id,\n runId: frame.run_id,\n projectId: frame.project_id,\n parentFrameId: frame.parent_frame_id,\n title: frame.name,\n timestamp: frame.created_at,\n metadata: {\n tags: this.extractTagsFromFrame(frame),\n importance: this.calculateFrameImportance(frame),\n },\n data: {\n inputs: frame.inputs,\n outputs: frame.outputs,\n digest: frame.digest_json,\n },\n }));\n } catch (error: unknown) {\n logger.error('Failed to get recent frames', error as Error);\n return [];\n }\n }\n\n /**\n * Add context metadata to the current frame\n */\n async addContext(key: string, value: any): Promise<void> {\n const currentFrameId = this.frameStack.getCurrentFrameId();\n if (!currentFrameId) return;\n\n try {\n const frame = this.frameDb.getFrame(currentFrameId);\n if (!frame) return;\n\n const metadata = frame.outputs || {};\n metadata[key] = value;\n\n this.frameDb.updateFrame(currentFrameId, {\n outputs: metadata,\n });\n } catch (error: unknown) {\n logger.warn('Failed to add context to frame', { error, key });\n }\n }\n\n /**\n * Delete a frame completely from the database (used in handoffs)\n */\n deleteFrame(frameId: string): void {\n try {\n // Remove from active stack if present\n this.frameStack.removeFrame(frameId);\n\n // Delete the frame and related data (cascades via FrameDatabase)\n this.frameDb.deleteFrame(frameId);\n\n logger.debug('Deleted frame completely', { frameId });\n } catch (error: unknown) {\n logger.error('Failed to delete frame', { frameId, error });\n throw error;\n }\n }\n\n /**\n * Extract tags from frame for categorization\n */\n private extractTagsFromFrame(frame: Frame): string[] {\n const tags: string[] = [];\n\n if (frame.type) tags.push(frame.type);\n\n if (frame.name) {\n const nameLower = frame.name.toLowerCase();\n if (nameLower.includes('error')) tags.push('error');\n if (nameLower.includes('fix')) tags.push('resolution');\n if (nameLower.includes('decision')) tags.push('decision');\n if (nameLower.includes('milestone')) tags.push('milestone');\n }\n\n try {\n if (frame.digest_json && typeof frame.digest_json === 'object') {\n const digest = frame.digest_json as Record<string, unknown>;\n if (Array.isArray(digest.tags)) {\n tags.push(...(digest.tags as string[]));\n }\n }\n } catch {\n // Ignore parse errors\n }\n\n return [...new Set(tags)];\n }\n\n /**\n * Calculate frame importance for prioritization\n */\n private calculateFrameImportance(frame: Frame): 'high' | 'medium' | 'low' {\n if (frame.type === 'milestone' || frame.name?.includes('decision')) {\n return 'high';\n }\n\n if (frame.type === 'error' || frame.type === 'resolution') {\n return 'medium';\n }\n\n if (frame.closed_at && frame.created_at) {\n const duration = frame.closed_at - frame.created_at;\n if (duration > 300) return 'medium';\n }\n\n return 'low';\n }\n}\n\n// Re-export types for compatibility (type-only, no runtime value)\nexport type {\n Frame,\n FrameContext,\n Anchor,\n Event,\n FrameType,\n FrameState,\n FrameCreationOptions,\n FrameManagerConfig,\n DigestResult,\n} from './frame-types.js';\n"],
5
- "mappings": ";;;;AAMA,SAAS,MAAM,cAAc;AAC7B,SAAS,cAAc;AACvB,SAAS,aAAa;AACtB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAEP,SAAyB,sBAAsB;AAC/C,SAAS,2BAA2B;AAGpC,MAAM,kBAAkB;AACxB,MAAM,oBAAoB;AAc1B,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB;AAC3B,SAAS,4BAA4B;AAE9B,MAAM,uBAAuB;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAA4B,eAAe;AAAA,EAC3C;AAAA,EACA,gBAAwB;AAAA,EAEhC,YACE,IACA,WACA,QACA;AACA,SAAK,YAAY;AACjB,SAAK,SAAS;AAAA,MACZ;AAAA,MACA,OAAO,QAAQ,SAAS,OAAO;AAAA,MAC/B,WAAW,QAAQ,aAAa,OAAO;AAAA,MACvC,eAAe,QAAQ,iBAAiB;AAAA,IAC1C;AAGA,SAAK,gBAAgB,QAAQ,iBAAiB;AAE9C,SAAK,eAAe,KAAK,OAAO;AAChC,SAAK,YAAY,KAAK,OAAO;AAG7B,SAAK,UAAU,IAAI,cAAc,EAAE;AACnC,SAAK,aAAa,IAAI;AAAA,MACpB,KAAK;AAAA,MACL;AAAA,MACA,KAAK;AAAA,IACP;AACA,SAAK,kBAAkB,IAAI,qBAAqB,KAAK,OAAO;AAG5D,SAAK,QAAQ,WAAW;AAExB,WAAO,KAAK,sCAAsC;AAAA,MAChD,WAAW,KAAK;AAAA,MAChB,OAAO,KAAK;AAAA,MACZ,WAAW,KAAK;AAAA,IAClB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAA4B;AAChC,QAAI;AACF,YAAM,KAAK,WAAW,WAAW;AAEjC,aAAO,KAAK,0CAA0C;AAAA,QACpD,YAAY,KAAK,WAAW,SAAS;AAAA,MACvC,CAAC;AAAA,IACH,SAAS,OAAgB;AACvB,YAAM,IAAI;AAAA,QACR;AAAA,QACA,UAAU;AAAA,QACV,EAAE,WAAW,KAAK,UAAU;AAAA,QAC5B,iBAAiB,QAAQ,QAAQ;AAAA,MACnC;AAAA,IACF;AAAA,EACF;AAAA,EAYA,YACE,eACA,MACA,QACA,eACQ;AACR,WAAO,MAAM;AAAA,MACX;AAAA,MACA;AAAA,MACA,EAAE,eAAe,KAAK;AAAA,MACtB,MAAM,KAAK,aAAa,eAAe,MAAM,QAAQ,aAAa;AAAA,IACpE;AAAA,EACF;AAAA,EAEQ,aACN,eACA,MACA,QACA,eACQ;AACR,QAAI;AAGJ,QAAI,OAAO,kBAAkB,UAAU;AACrC,qBAAe;AAAA,QACb,MAAM;AAAA,QACN;AAAA,QACA,QAAQ,UAAU,CAAC;AAAA,QACnB;AAAA,MACF;AAAA,IACF,OAAO;AACL,qBAAe;AAAA,IACjB;AAGA,QAAI,CAAC,aAAa,QAAQ,aAAa,KAAK,KAAK,EAAE,WAAW,GAAG;AAC/D,YAAM,IAAI;AAAA,QACR;AAAA,QACA,UAAU;AAAA,QACV,EAAE,aAAa;AAAA,MACjB;AAAA,IACF;AAGA,QAAI,KAAK,WAAW,SAAS,KAAK,KAAK,OAAO,eAAgB;AAC5D,YAAM,IAAI;AAAA,QACR,gCAAgC,KAAK,OAAO,aAAa;AAAA,QACzD,UAAU;AAAA,QACV,EAAE,cAAc,KAAK,WAAW,SAAS,EAAE;AAAA,MAC7C;AAAA,IACF;AAGA,UAAM,mBACJ,aAAa,iBAAiB,KAAK,WAAW,kBAAkB;AAGlE,QAAI,QAAQ;AACZ,QAAI,kBAAkB;AACpB,YAAM,cAAc,KAAK,QAAQ,SAAS,gBAAgB;AAC1D,cAAQ,cAAc,YAAY,QAAQ,IAAI;AAAA,IAChD;AAGA,QAAI,QAAQ,KAAK,eAAe;AAC9B,YAAM,IAAI;AAAA,QACR,iCAAiC,KAAK,MAAM,KAAK,aAAa;AAAA,QAC9D,UAAU;AAAA,QACV;AAAA,UACE,cAAc;AAAA,UACd,UAAU,KAAK;AAAA,UACf,WAAW,aAAa;AAAA,UACxB,eAAe;AAAA,QACjB;AAAA,MACF;AAAA,IACF;AAGA,QAAI,kBAAkB;AACpB,YAAM,QAAQ,KAAK,YAAY,OAAO,GAAG,gBAAgB;AACzD,UAAI,OAAO;AACT,cAAM,IAAI;AAAA,UACR;AAAA,UACA,UAAU;AAAA,UACV;AAAA,YACE,eAAe;AAAA,YACf;AAAA,YACA,WAAW,aAAa;AAAA,UAC1B;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,UAAM,UAAU,OAAO;AACvB,UAAM,QAAiD;AAAA,MACrD,UAAU;AAAA,MACV,QAAQ,KAAK;AAAA,MACb,YAAY,KAAK;AAAA,MACjB,iBAAiB;AAAA,MACjB;AAAA,MACA,MAAM,aAAa;AAAA,MACnB,MAAM,aAAa;AAAA,MACnB,OAAO;AAAA,MACP,QAAQ,aAAa,UAAU,CAAC;AAAA,MAChC,SAAS,CAAC;AAAA,MACV,aAAa,CAAC;AAAA,IAChB;AAGA,UAAM,eAAe,KAAK,QAAQ,YAAY,KAAK;AAGnD,SAAK,WAAW,UAAU,OAAO;AAEjC,WAAO,KAAK,iBAAiB;AAAA,MAC3B;AAAA,MACA,MAAM,aAAa;AAAA,MACnB,MAAM,aAAa;AAAA,MACnB,eAAe;AAAA,MACf,YAAY,KAAK,WAAW,SAAS;AAAA,IACvC,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,SAAkB,SAAqC;AAChE,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA,EAAE,SAAS,QAAQ;AAAA,MACnB,MAAM,KAAK,YAAY,SAAS,OAAO;AAAA,IACzC;AAAA,EACF;AAAA,EAEQ,YAAY,SAAkB,SAAqC;AACzE,UAAM,gBAAgB,WAAW,KAAK,WAAW,kBAAkB;AACnE,QAAI,CAAC,eAAe;AAClB,YAAM,IAAI;AAAA,QACR;AAAA,QACA,UAAU;AAAA,QACV;AAAA,UACE,WAAW;AAAA,UACX,YAAY,KAAK,WAAW,SAAS;AAAA,QACvC;AAAA,MACF;AAAA,IACF;AAGA,UAAM,QAAQ,KAAK,QAAQ,SAAS,aAAa;AACjD,QAAI,CAAC,OAAO;AACV,YAAM,IAAI;AAAA,QACR,oBAAoB,aAAa;AAAA,QACjC,UAAU;AAAA,QACV;AAAA,UACE,SAAS;AAAA,UACT,WAAW;AAAA,UACX,OAAO,KAAK;AAAA,QACd;AAAA,MACF;AAAA,IACF;AAEA,QAAI,MAAM,UAAU,UAAU;AAC5B,aAAO,KAAK,2CAA2C;AAAA,QACrD,SAAS;AAAA,MACX,CAAC;AACD;AAAA,IACF;AAGA,UAAM,SAAS,KAAK,gBAAgB,eAAe,aAAa;AAChE,UAAM,eAAe,EAAE,GAAG,SAAS,GAAG,OAAO,WAAW;AAGxD,SAAK,QAAQ,YAAY,eAAe;AAAA,MACtC,OAAO;AAAA,MACP,SAAS;AAAA,MACT,aAAa,OAAO;AAAA,MACpB,aAAa,OAAO;AAAA,MACpB,WAAW,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,IACzC,CAAC;AAGD,SAAK,WAAW,SAAS,aAAa;AAGtC,SAAK,iBAAiB,aAAa;AAGnC,UAAM,SAAS,KAAK,QAAQ,eAAe,aAAa;AACxD,UAAM,UAAU,KAAK,QAAQ,gBAAgB,aAAa;AAC1D,wBACG,aAAa,EAAE,OAAO,EAAE,GAAG,OAAO,OAAO,SAAS,GAAG,QAAQ,QAAQ,CAAC,EACtE,MAAM,MAAM;AAAA,IAEb,CAAC;AAEH,WAAO,KAAK,gBAAgB;AAAA,MAC1B,SAAS;AAAA,MACT,MAAM,MAAM;AAAA,MACZ,UAAU,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,IAAI,MAAM;AAAA,MAChD,cAAc,OAAO,KAAK;AAAA,MAC1B,YAAY,KAAK,WAAW,SAAS;AAAA,IACvC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,SACE,WACA,SACA,SACQ;AACR,WAAO,MAAM;AAAA,MACX;AAAA,MACA;AAAA,MACA,EAAE,WAAW,QAAQ;AAAA,MACrB,MAAM,KAAK,UAAU,WAAW,SAAS,OAAO;AAAA,IAClD;AAAA,EACF;AAAA,EAEQ,UACN,WACA,SACA,SACQ;AACR,UAAM,gBAAgB,WAAW,KAAK,WAAW,kBAAkB;AACnE,QAAI,CAAC,eAAe;AAClB,YAAM,IAAI;AAAA,QACR;AAAA,QACA,UAAU;AAAA,QACV;AAAA,UACE;AAAA,UACA,WAAW;AAAA,QACb;AAAA,MACF;AAAA,IACF;AAEA,UAAM,UAAU,OAAO;AACvB,UAAM,WAAW,KAAK,QAAQ,qBAAqB,aAAa;AAEhE,UAAM,QAA2B;AAAA,MAC/B,UAAU;AAAA,MACV,UAAU;AAAA,MACV,QAAQ,KAAK;AAAA,MACb,KAAK;AAAA,MACL,YAAY;AAAA,MACZ;AAAA,IACF;AAEA,UAAM,eAAe,KAAK,QAAQ,YAAY,KAAK;AAEnD,WAAO,MAAM,eAAe;AAAA,MAC1B;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA;AAAA,IACF,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,UACE,MACA,MACA,WAAmB,GACnB,WAAgC,CAAC,GACjC,SACQ;AACR,WAAO,MAAM;AAAA,MACX;AAAA,MACA;AAAA,MACA,EAAE,MAAM,QAAQ;AAAA,MAChB,MAAM,KAAK,WAAW,MAAM,MAAM,UAAU,UAAU,OAAO;AAAA,IAC/D;AAAA,EACF;AAAA,EAEQ,WACN,MACA,MACA,UACA,UACA,SACQ;AACR,UAAM,gBAAgB,WAAW,KAAK,WAAW,kBAAkB;AACnE,QAAI,CAAC,eAAe;AAClB,YAAM,IAAI;AAAA,QACR;AAAA,QACA,UAAU;AAAA,QACV;AAAA,UACE,YAAY;AAAA,UACZ,WAAW;AAAA,QACb;AAAA,MACF;AAAA,IACF;AAEA,UAAM,WAAW,OAAO;AACxB,UAAM,SAAqC;AAAA,MACzC,WAAW;AAAA,MACX,UAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,UAAM,gBAAgB,KAAK,QAAQ,aAAa,MAAM;AAEtD,WAAO,MAAM,gBAAgB;AAAA,MAC3B;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA;AAAA,IACF,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAmB,YAAoB,IAAoB;AACzD,WAAO,KAAK,WAAW,mBAAmB,SAAS;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA,EAKA,qBAA8B;AAC5B,WAAO,KAAK,WAAW,eAAe;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAwC;AACtC,WAAO,KAAK,WAAW,kBAAkB;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAwB;AACtB,WAAO,KAAK,WAAW,SAAS;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,SAAoC;AAC3C,WAAO,KAAK,QAAQ,SAAS,OAAO;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,SAAiB,OAAyB;AACvD,WAAO,KAAK,QAAQ,eAAe,SAAS,KAAK;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,SAA2B;AACzC,WAAO,KAAK,QAAQ,gBAAgB,OAAO;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,SAA+B;AAC5C,WAAO,KAAK,gBAAgB,eAAe,OAAO;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAwD;AACtD,WAAO,KAAK,WAAW,cAAc;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAwC;AACtC,WAAO,KAAK,QAAQ,cAAc;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAiB,eAA6B;AACpD,QAAI;AACF,YAAM,eAAe,KAAK,QAAQ;AAAA,QAChC,KAAK;AAAA,QACL;AAAA,MACF;AACA,YAAM,cAAc,aAAa;AAAA,QAC/B,CAAC,MAAW,EAAE,oBAAoB;AAAA,MACpC;AAEA,iBAAW,cAAc,aAAa;AACpC,YAAI,KAAK,WAAW,cAAc,WAAW,QAAQ,GAAG;AACtD,eAAK,WAAW,WAAW,QAAQ;AAAA,QACrC;AAAA,MACF;AAAA,IACF,SAAS,OAAgB;AACvB,aAAO,KAAK,gCAAgC,EAAE,eAAe,MAAM,CAAC;AAAA,IACtE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAmB,SAA2B;AAC5C,UAAM,SAAS,KAAK,QAAQ,eAAe,OAAO;AAClD,UAAM,YAAsB,CAAC;AAE7B,eAAW,SAAS,QAAQ;AAC1B,UAAI,MAAM,eAAe,cAAc,MAAM,QAAQ,MAAM;AACzD,kBAAU,KAAK,MAAM,QAAQ,IAAI;AAAA,MACnC;AAAA,IACF;AAEA,WAAO,CAAC,GAAG,IAAI,IAAI,SAAS,CAAC;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAmB,QAAuC;AACxD,UAAM,cAAwB,CAAC;AAE/B,QAAI,OAAO,eAAe,MAAM,QAAQ,OAAO,WAAW,GAAG;AAC3D,kBAAY,KAAK,GAAG,OAAO,WAAW;AAAA,IACxC;AAEA,QAAI,OAAO,gBAAgB,MAAM,QAAQ,OAAO,YAAY,GAAG;AAC7D,kBAAY,KAAK,GAAG,OAAO,YAAY;AAAA,IACzC;AAEA,QAAI,OAAO,eAAe,MAAM,QAAQ,OAAO,WAAW,GAAG;AAC3D,kBAAY,KAAK,GAAG,OAAO,WAAW;AAAA,IACxC;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,YACN,cACA,eACiB;AACjB,UAAM,UAAU,oBAAI,IAAY;AAChC,UAAM,OAAiB,CAAC;AAGxB,QAAI,YAAgC;AAEpC,WAAO,WAAW;AAEhB,UAAI,QAAQ,IAAI,SAAS,GAAG;AAE1B,cAAM,aAAa,KAAK,QAAQ,SAAS;AACzC,eAAO,KAAK,MAAM,UAAU,EAAE,OAAO,SAAS;AAAA,MAChD;AAGA,UAAI,cAAc,cAAc;AAC9B,eAAO,KAAK,OAAO,CAAC,WAAW,YAAY,CAAC;AAAA,MAC9C;AAEA,cAAQ,IAAI,SAAS;AACrB,WAAK,KAAK,SAAS;AAGnB,YAAM,QAAQ,KAAK,QAAQ,SAAS,SAAS;AAC7C,UAAI,CAAC,OAAO;AAEV;AAAA,MACF;AACA,kBAAY,MAAM;AAGlB,UAAI,KAAK,SAAS,KAAK,eAAe;AACpC,cAAM,IAAI;AAAA,UACR;AAAA,UACA,UAAU;AAAA,UACV;AAAA,YACE,OAAO,KAAK;AAAA,YACZ,UAAU,KAAK;AAAA,YACf;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOO,kBAAkB,SAAiB,kBAAuC;AAE/E,UAAM,QAAQ,KAAK,QAAQ,SAAS,OAAO;AAC3C,QAAI,CAAC,OAAO;AACV,YAAM,IAAI;AAAA,QACR,oBAAoB,OAAO;AAAA,QAC3B,UAAU;AAAA,QACV,EAAE,QAAQ;AAAA,MACZ;AAAA,IACF;AAGA,QAAI,kBAAkB;AAEpB,YAAM,iBAAiB,KAAK,QAAQ,SAAS,gBAAgB;AAC7D,UAAI,CAAC,gBAAgB;AACnB,cAAM,IAAI;AAAA,UACR,2BAA2B,gBAAgB;AAAA,UAC3C,UAAU;AAAA,UACV,EAAE,SAAS,iBAAiB;AAAA,QAC9B;AAAA,MACF;AAEA,YAAM,QAAQ,KAAK,YAAY,SAAS,gBAAgB;AACxD,UAAI,OAAO;AACT,cAAM,IAAI;AAAA,UACR;AAAA,UACA,UAAU;AAAA,UACV;AAAA,YACE;AAAA,YACA;AAAA,YACA;AAAA,YACA,iBAAiB,MAAM;AAAA,UACzB;AAAA,QACF;AAAA,MACF;AAGA,YAAMA,YAAW,eAAe,QAAQ;AACxC,UAAIA,YAAW,KAAK,eAAe;AACjC,cAAM,IAAI;AAAA,UACR;AAAA,UACA,UAAU;AAAA,UACV;AAAA,YACE;AAAA,YACA;AAAA,YACA,UAAAA;AAAA,YACA,UAAU,KAAK;AAAA,UACjB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,QAAI,WAAW;AACf,QAAI,kBAAkB;AACpB,YAAM,iBAAiB,KAAK,QAAQ,SAAS,gBAAgB;AAC7D,UAAI,gBAAgB;AAClB,mBAAW,eAAe,QAAQ;AAAA,MACpC;AAAA,IACF;AAGA,SAAK,QAAQ,YAAY,SAAS;AAAA,MAChC,iBAAiB;AAAA,MACjB,OAAO;AAAA,IACT,CAAC;AAED,WAAO,KAAK,wBAAwB;AAAA,MAClC;AAAA,MACA,aAAa,MAAM;AAAA,MACnB,aAAa;AAAA,IACf,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,yBAIL;AACA,UAAM,SAAmB,CAAC;AAC1B,UAAM,WAAqB,CAAC;AAC5B,UAAM,YAAY,KAAK,QAAQ,mBAAmB,KAAK,SAAS;AAGhE,eAAW,SAAS,WAAW;AAC7B,UAAI,MAAM,QAAQ,KAAK,eAAe;AACpC,eAAO;AAAA,UACL,SAAS,MAAM,QAAQ,uBAAuB,MAAM,KAAK,MAAM,KAAK,aAAa;AAAA,QACnF;AAAA,MACF;AAGA,UAAI,MAAM,QAAQ,KAAK,gBAAgB,KAAK;AAC1C,iBAAS;AAAA,UACP,SAAS,MAAM,QAAQ,0BAA0B,MAAM,KAAK,IAAI,KAAK,aAAa;AAAA,QACpF;AAAA,MACF;AAAA,IACF;AAGA,UAAM,aAAa,UAAU,OAAO,OAAK,CAAC,EAAE,eAAe;AAC3D,UAAM,UAAU,oBAAI,IAAY;AAChC,UAAM,WAAW,oBAAI,IAAY;AAEjC,UAAM,gBAAgB,CAAC,YAA6B;AAClD,UAAI,SAAS,IAAI,OAAO,GAAG;AACzB,eAAO,KAAK,kCAAkC,OAAO,EAAE;AACvD,eAAO;AAAA,MACT;AAEA,UAAI,QAAQ,IAAI,OAAO,GAAG;AACxB,eAAO;AAAA,MACT;AAEA,eAAS,IAAI,OAAO;AAGpB,YAAM,WAAW,UAAU,OAAO,OAAK,EAAE,oBAAoB,OAAO;AACpE,iBAAW,SAAS,UAAU;AAC5B,YAAI,cAAc,MAAM,QAAQ,GAAG;AACjC,iBAAO;AAAA,QACT;AAAA,MACF;AAEA,eAAS,OAAO,OAAO;AACvB,cAAQ,IAAI,OAAO;AACnB,aAAO;AAAA,IACT;AAGA,eAAW,QAAQ,YAAY;AAC7B,oBAAc,KAAK,QAAQ;AAAA,IAC7B;AAEA,WAAO;AAAA,MACL,SAAS,OAAO,WAAW;AAAA,MAC3B;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,MAA4B;AACvC,SAAK,YAAY;AAEjB,SAAK,WAAW,aAAa,IAAI;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,gBAAgB,QAAgB,KAAuB;AAC3D,QAAI;AACF,YAAM,SAAS,KAAK,QAAQ,mBAAmB,KAAK,SAAS;AAG7D,aAAO,OACJ,KAAK,CAAC,GAAG,OAAO,EAAE,cAAc,MAAM,EAAE,cAAc,EAAE,EACxD,MAAM,GAAG,KAAK,EACd,IAAI,CAAC,WAAW;AAAA,QACf,GAAG;AAAA;AAAA,QAEH,SAAS,MAAM;AAAA,QACf,OAAO,MAAM;AAAA,QACb,WAAW,MAAM;AAAA,QACjB,eAAe,MAAM;AAAA,QACrB,OAAO,MAAM;AAAA,QACb,WAAW,MAAM;AAAA,QACjB,UAAU;AAAA,UACR,MAAM,KAAK,qBAAqB,KAAK;AAAA,UACrC,YAAY,KAAK,yBAAyB,KAAK;AAAA,QACjD;AAAA,QACA,MAAM;AAAA,UACJ,QAAQ,MAAM;AAAA,UACd,SAAS,MAAM;AAAA,UACf,QAAQ,MAAM;AAAA,QAChB;AAAA,MACF,EAAE;AAAA,IACN,SAAS,OAAgB;AACvB,aAAO,MAAM,+BAA+B,KAAc;AAC1D,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAW,KAAa,OAA2B;AACvD,UAAM,iBAAiB,KAAK,WAAW,kBAAkB;AACzD,QAAI,CAAC,eAAgB;AAErB,QAAI;AACF,YAAM,QAAQ,KAAK,QAAQ,SAAS,cAAc;AAClD,UAAI,CAAC,MAAO;AAEZ,YAAM,WAAW,MAAM,WAAW,CAAC;AACnC,eAAS,GAAG,IAAI;AAEhB,WAAK,QAAQ,YAAY,gBAAgB;AAAA,QACvC,SAAS;AAAA,MACX,CAAC;AAAA,IACH,SAAS,OAAgB;AACvB,aAAO,KAAK,kCAAkC,EAAE,OAAO,IAAI,CAAC;AAAA,IAC9D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,SAAuB;AACjC,QAAI;AAEF,WAAK,WAAW,YAAY,OAAO;AAGnC,WAAK,QAAQ,YAAY,OAAO;AAEhC,aAAO,MAAM,4BAA4B,EAAE,QAAQ,CAAC;AAAA,IACtD,SAAS,OAAgB;AACvB,aAAO,MAAM,0BAA0B,EAAE,SAAS,MAAM,CAAC;AACzD,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,qBAAqB,OAAwB;AACnD,UAAM,OAAiB,CAAC;AAExB,QAAI,MAAM,KAAM,MAAK,KAAK,MAAM,IAAI;AAEpC,QAAI,MAAM,MAAM;AACd,YAAM,YAAY,MAAM,KAAK,YAAY;AACzC,UAAI,UAAU,SAAS,OAAO,EAAG,MAAK,KAAK,OAAO;AAClD,UAAI,UAAU,SAAS,KAAK,EAAG,MAAK,KAAK,YAAY;AACrD,UAAI,UAAU,SAAS,UAAU,EAAG,MAAK,KAAK,UAAU;AACxD,UAAI,UAAU,SAAS,WAAW,EAAG,MAAK,KAAK,WAAW;AAAA,IAC5D;AAEA,QAAI;AACF,UAAI,MAAM,eAAe,OAAO,MAAM,gBAAgB,UAAU;AAC9D,cAAM,SAAS,MAAM;AACrB,YAAI,MAAM,QAAQ,OAAO,IAAI,GAAG;AAC9B,eAAK,KAAK,GAAI,OAAO,IAAiB;AAAA,QACxC;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAEA,WAAO,CAAC,GAAG,IAAI,IAAI,IAAI,CAAC;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKQ,yBAAyB,OAAyC;AACxE,QAAI,MAAM,SAAS,eAAe,MAAM,MAAM,SAAS,UAAU,GAAG;AAClE,aAAO;AAAA,IACT;AAEA,QAAI,MAAM,SAAS,WAAW,MAAM,SAAS,cAAc;AACzD,aAAO;AAAA,IACT;AAEA,QAAI,MAAM,aAAa,MAAM,YAAY;AACvC,YAAM,WAAW,MAAM,YAAY,MAAM;AACzC,UAAI,WAAW,IAAK,QAAO;AAAA,IAC7B;AAEA,WAAO;AAAA,EACT;AACF;",
4
+ "sourcesContent": ["/**\n * Refactored Frame Manager - Modular Implementation\n * Main orchestrator that uses focused modules for frame management\n */\n\nimport Database from 'better-sqlite3';\nimport { v4 as uuidv4 } from 'uuid';\nimport { logger } from '../monitoring/logger.js';\nimport { trace } from '../trace/index.js';\nimport {\n FrameError,\n SystemError,\n ErrorCode,\n wrapError,\n createErrorHandler,\n} from '../errors/index.js';\nimport { retry, withTimeout } from '../errors/recovery.js';\nimport { sessionManager, FrameQueryMode } from '../session/index.js';\nimport { frameLifecycleHooks } from './frame-lifecycle-hooks.js';\n\n// Constants for frame validation\nconst MAX_FRAME_DEPTH = 100; // Maximum allowed frame depth\nconst DEFAULT_MAX_DEPTH = 100; // Default if not configured\n\n// Import refactored modules\nimport {\n Frame,\n FrameContext,\n Anchor,\n Event,\n FrameType,\n FrameState,\n FrameCreationOptions,\n FrameManagerConfig,\n DigestResult,\n} from './frame-types.js';\nimport { FrameDatabase } from './frame-database.js';\nimport { FrameStack } from './frame-stack.js';\nimport { FrameDigestGenerator } from './frame-digest.js';\n\nexport class RefactoredFrameManager {\n private frameDb: FrameDatabase;\n private frameStack: FrameStack;\n private digestGenerator: FrameDigestGenerator;\n\n private currentRunId: string;\n private sessionId: string;\n private projectId: string;\n private queryMode: FrameQueryMode = FrameQueryMode.PROJECT_ACTIVE;\n private config: FrameManagerConfig;\n private maxFrameDepth: number = DEFAULT_MAX_DEPTH;\n\n constructor(\n db: Database.Database,\n projectId: string,\n config?: Partial<FrameManagerConfig>\n ) {\n this.projectId = projectId;\n this.config = {\n projectId,\n runId: config?.runId || uuidv4(),\n sessionId: config?.sessionId || uuidv4(),\n maxStackDepth: config?.maxStackDepth || 50,\n };\n\n // Set max frame depth from config if provided\n this.maxFrameDepth = config?.maxStackDepth || DEFAULT_MAX_DEPTH;\n\n this.currentRunId = this.config.runId!;\n this.sessionId = this.config.sessionId!;\n\n // Initialize modules\n this.frameDb = new FrameDatabase(db);\n this.frameStack = new FrameStack(\n this.frameDb,\n projectId,\n this.currentRunId\n );\n this.digestGenerator = new FrameDigestGenerator(this.frameDb);\n\n // Initialize database schema\n this.frameDb.initSchema();\n\n logger.info('RefactoredFrameManager initialized', {\n projectId: this.projectId,\n runId: this.currentRunId,\n sessionId: this.sessionId,\n });\n }\n\n /**\n * Initialize the frame manager\n */\n async initialize(): Promise<void> {\n try {\n await this.frameStack.initialize();\n\n logger.info('Frame manager initialization completed', {\n stackDepth: this.frameStack.getDepth(),\n });\n } catch (error: unknown) {\n throw new SystemError(\n 'Failed to initialize frame manager',\n ErrorCode.SYSTEM_INIT_FAILED,\n { projectId: this.projectId },\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Create a new frame\n */\n createFrame(options: FrameCreationOptions): string;\n createFrame(\n type: FrameType,\n name: string,\n inputs?: Record<string, any>,\n parentFrameId?: string\n ): string;\n createFrame(\n typeOrOptions: FrameType | FrameCreationOptions,\n name?: string,\n inputs?: Record<string, any>,\n parentFrameId?: string\n ): string {\n return trace.traceSync(\n 'function',\n 'FrameManager.createFrame',\n { typeOrOptions, name },\n () => this._createFrame(typeOrOptions, name, inputs, parentFrameId)\n );\n }\n\n private _createFrame(\n typeOrOptions: FrameType | FrameCreationOptions,\n name?: string,\n inputs?: Record<string, any>,\n parentFrameId?: string\n ): string {\n let frameOptions: FrameCreationOptions;\n\n // Handle both function signatures\n if (typeof typeOrOptions === 'string') {\n frameOptions = {\n type: typeOrOptions,\n name: name!,\n inputs: inputs || {},\n parentFrameId,\n };\n } else {\n frameOptions = typeOrOptions;\n }\n\n // Validate inputs\n if (!frameOptions.name || frameOptions.name.trim().length === 0) {\n throw new FrameError(\n 'Frame name is required',\n ErrorCode.FRAME_INVALID_INPUT,\n { frameOptions }\n );\n }\n\n // Check stack depth limit\n if (this.frameStack.getDepth() >= this.config.maxStackDepth!) {\n throw new FrameError(\n `Maximum stack depth reached: ${this.config.maxStackDepth}`,\n ErrorCode.FRAME_STACK_OVERFLOW,\n { currentDepth: this.frameStack.getDepth() }\n );\n }\n\n // Determine parent frame\n const resolvedParentId =\n frameOptions.parentFrameId || this.frameStack.getCurrentFrameId();\n\n // Get depth from parent frame, not from stack position\n let depth = 0;\n if (resolvedParentId) {\n const parentFrame = this.frameDb.getFrame(resolvedParentId);\n depth = parentFrame ? parentFrame.depth + 1 : 0;\n }\n\n // Check for depth limit\n if (depth > this.maxFrameDepth) {\n throw new FrameError(\n `Maximum frame depth exceeded: ${depth} > ${this.maxFrameDepth}`,\n ErrorCode.FRAME_STACK_OVERFLOW,\n {\n currentDepth: depth,\n maxDepth: this.maxFrameDepth,\n frameName: frameOptions.name,\n parentFrameId: resolvedParentId,\n }\n );\n }\n\n // Check for circular reference before creating frame\n if (resolvedParentId) {\n const cycle = this.detectCycle(uuidv4(), resolvedParentId);\n if (cycle) {\n throw new FrameError(\n `Circular reference detected in frame hierarchy`,\n ErrorCode.FRAME_CYCLE_DETECTED,\n {\n parentFrameId: resolvedParentId,\n cycle,\n frameName: frameOptions.name,\n }\n );\n }\n }\n\n // Create frame data\n const frameId = uuidv4();\n const frame: Omit<Frame, 'created_at' | 'closed_at'> = {\n frame_id: frameId,\n run_id: this.currentRunId,\n project_id: this.projectId,\n parent_frame_id: resolvedParentId,\n depth,\n type: frameOptions.type,\n name: frameOptions.name,\n state: 'active',\n inputs: frameOptions.inputs || {},\n outputs: {},\n digest_json: {},\n };\n\n // Insert into database\n const createdFrame = this.frameDb.insertFrame(frame);\n\n // Add to stack\n this.frameStack.pushFrame(frameId);\n\n logger.info('Created frame', {\n frameId,\n name: frameOptions.name,\n type: frameOptions.type,\n parentFrameId: resolvedParentId,\n stackDepth: this.frameStack.getDepth(),\n });\n\n return frameId;\n }\n\n /**\n * Close a frame and generate digest\n */\n closeFrame(frameId?: string, outputs?: Record<string, any>): void {\n trace.traceSync(\n 'function',\n 'FrameManager.closeFrame',\n { frameId, outputs },\n () => this._closeFrame(frameId, outputs)\n );\n }\n\n private _closeFrame(frameId?: string, outputs?: Record<string, any>): void {\n const targetFrameId = frameId || this.frameStack.getCurrentFrameId();\n if (!targetFrameId) {\n throw new FrameError(\n 'No active frame to close',\n ErrorCode.FRAME_INVALID_STATE,\n {\n operation: 'closeFrame',\n stackDepth: this.frameStack.getDepth(),\n }\n );\n }\n\n // Get frame details\n const frame = this.frameDb.getFrame(targetFrameId);\n if (!frame) {\n throw new FrameError(\n `Frame not found: ${targetFrameId}`,\n ErrorCode.FRAME_NOT_FOUND,\n {\n frameId: targetFrameId,\n operation: 'closeFrame',\n runId: this.currentRunId,\n }\n );\n }\n\n if (frame.state === 'closed') {\n logger.warn('Attempted to close already closed frame', {\n frameId: targetFrameId,\n });\n return;\n }\n\n // Generate digest before closing\n const digest = this.digestGenerator.generateDigest(targetFrameId);\n const finalOutputs = { ...outputs, ...digest.structured };\n\n // Update frame to closed state\n this.frameDb.updateFrame(targetFrameId, {\n state: 'closed',\n outputs: finalOutputs,\n digest_text: digest.text,\n digest_json: digest.structured,\n closed_at: Math.floor(Date.now() / 1000),\n });\n\n // Remove from stack (this will also remove any child frames)\n this.frameStack.popFrame(targetFrameId);\n\n // Close all child frames recursively\n this.closeChildFrames(targetFrameId);\n\n // Trigger lifecycle hooks (fire and forget)\n const events = this.frameDb.getFrameEvents(targetFrameId);\n const anchors = this.frameDb.getFrameAnchors(targetFrameId);\n frameLifecycleHooks\n .triggerClose({ frame: { ...frame, state: 'closed' }, events, anchors })\n .catch(() => {\n // Silently ignore errors - hooks are non-critical\n });\n\n logger.info('Closed frame', {\n frameId: targetFrameId,\n name: frame.name,\n duration: Math.floor(Date.now() / 1000) - frame.created_at,\n digestLength: digest.text.length,\n stackDepth: this.frameStack.getDepth(),\n });\n }\n\n /**\n * Add an event to the current frame\n */\n addEvent(\n eventType: Event['event_type'],\n payload: Record<string, any>,\n frameId?: string\n ): string {\n return trace.traceSync(\n 'function',\n 'FrameManager.addEvent',\n { eventType, frameId },\n () => this._addEvent(eventType, payload, frameId)\n );\n }\n\n private _addEvent(\n eventType: Event['event_type'],\n payload: Record<string, any>,\n frameId?: string\n ): string {\n const targetFrameId = frameId || this.frameStack.getCurrentFrameId();\n if (!targetFrameId) {\n throw new FrameError(\n 'No active frame for event',\n ErrorCode.FRAME_INVALID_STATE,\n {\n eventType,\n operation: 'addEvent',\n }\n );\n }\n\n const eventId = uuidv4();\n const sequence = this.frameDb.getNextEventSequence(targetFrameId);\n\n const event: Omit<Event, 'ts'> = {\n event_id: eventId,\n frame_id: targetFrameId,\n run_id: this.currentRunId,\n seq: sequence,\n event_type: eventType,\n payload,\n };\n\n const createdEvent = this.frameDb.insertEvent(event);\n\n logger.debug('Added event', {\n eventId,\n frameId: targetFrameId,\n eventType,\n sequence,\n });\n\n return eventId;\n }\n\n /**\n * Add an anchor (important fact) to current frame\n */\n addAnchor(\n type: Anchor['type'],\n text: string,\n priority: number = 5,\n metadata: Record<string, any> = {},\n frameId?: string\n ): string {\n return trace.traceSync(\n 'function',\n 'FrameManager.addAnchor',\n { type, frameId },\n () => this._addAnchor(type, text, priority, metadata, frameId)\n );\n }\n\n private _addAnchor(\n type: Anchor['type'],\n text: string,\n priority: number,\n metadata: Record<string, any>,\n frameId?: string\n ): string {\n const targetFrameId = frameId || this.frameStack.getCurrentFrameId();\n if (!targetFrameId) {\n throw new FrameError(\n 'No active frame for anchor',\n ErrorCode.FRAME_INVALID_STATE,\n {\n anchorType: type,\n operation: 'addAnchor',\n }\n );\n }\n\n const anchorId = uuidv4();\n const anchor: Omit<Anchor, 'created_at'> = {\n anchor_id: anchorId,\n frame_id: targetFrameId,\n type,\n text,\n priority,\n metadata,\n };\n\n const createdAnchor = this.frameDb.insertAnchor(anchor);\n\n logger.debug('Added anchor', {\n anchorId,\n frameId: targetFrameId,\n type,\n priority,\n });\n\n return anchorId;\n }\n\n /**\n * Get hot stack context\n */\n getHotStackContext(maxEvents: number = 20): FrameContext[] {\n return this.frameStack.getHotStackContext(maxEvents);\n }\n\n /**\n * Get active frame path (root to current)\n */\n getActiveFramePath(): Frame[] {\n return this.frameStack.getStackFrames();\n }\n\n /**\n * Get current frame ID\n */\n getCurrentFrameId(): string | undefined {\n return this.frameStack.getCurrentFrameId();\n }\n\n /**\n * Get stack depth\n */\n getStackDepth(): number {\n return this.frameStack.getDepth();\n }\n\n /**\n * Get frame by ID\n */\n getFrame(frameId: string): Frame | undefined {\n return this.frameDb.getFrame(frameId);\n }\n\n /**\n * Get frame events\n */\n getFrameEvents(frameId: string, limit?: number): Event[] {\n return this.frameDb.getFrameEvents(frameId, limit);\n }\n\n /**\n * Get frame anchors\n */\n getFrameAnchors(frameId: string): Anchor[] {\n return this.frameDb.getFrameAnchors(frameId);\n }\n\n /**\n * Generate digest for a frame\n */\n generateDigest(frameId: string): DigestResult {\n return this.digestGenerator.generateDigest(frameId);\n }\n\n /**\n * Validate stack consistency\n */\n validateStack(): { isValid: boolean; errors: string[] } {\n return this.frameStack.validateStack();\n }\n\n /**\n * Get database statistics\n */\n getStatistics(): Record<string, number> {\n return this.frameDb.getStatistics();\n }\n\n /**\n * Close all child frames recursively with depth limit to prevent stack overflow\n */\n private closeChildFrames(parentFrameId: string, depth: number = 0): void {\n // Prevent stack overflow with depth limit\n if (depth > this.maxFrameDepth) {\n logger.warn('closeChildFrames depth limit exceeded', {\n parentFrameId,\n depth,\n maxDepth: this.maxFrameDepth,\n });\n return;\n }\n\n try {\n const activeFrames = this.frameDb.getFramesByProject(\n this.projectId,\n 'active'\n );\n const childFrames = activeFrames.filter(\n (f) => f.parent_frame_id === parentFrameId\n );\n\n for (const childFrame of childFrames) {\n if (this.frameStack.isFrameActive(childFrame.frame_id)) {\n // Close child's children first (depth-first)\n this.closeChildFrames(childFrame.frame_id, depth + 1);\n // Then close the child frame directly without recursing through closeFrame\n this.closeFrameDirectly(childFrame.frame_id);\n }\n }\n } catch (error: unknown) {\n logger.warn('Failed to close child frames', { parentFrameId, error });\n }\n }\n\n /**\n * Close a frame directly without triggering child frame closure\n * Used by closeChildFrames to avoid double recursion\n */\n private closeFrameDirectly(frameId: string): void {\n const frame = this.frameDb.getFrame(frameId);\n if (!frame || frame.state === 'closed') return;\n\n const digest = this.digestGenerator.generateDigest(frameId);\n\n this.frameDb.updateFrame(frameId, {\n state: 'closed',\n outputs: digest.structured,\n digest_text: digest.text,\n digest_json: digest.structured,\n closed_at: Math.floor(Date.now() / 1000),\n });\n\n this.frameStack.popFrame(frameId);\n\n logger.debug('Closed child frame directly', { frameId });\n }\n\n /**\n * Extract active artifacts from frame events\n */\n getActiveArtifacts(frameId: string): string[] {\n const events = this.frameDb.getFrameEvents(frameId);\n const artifacts: string[] = [];\n\n for (const event of events) {\n if (event.event_type === 'artifact' && event.payload.path) {\n artifacts.push(event.payload.path);\n }\n }\n\n return [...new Set(artifacts)];\n }\n\n /**\n * Extract constraints from frame inputs\n */\n extractConstraints(inputs: Record<string, unknown>): string[] {\n const constraints: string[] = [];\n\n if (inputs.constraints && Array.isArray(inputs.constraints)) {\n constraints.push(...(inputs.constraints as string[]));\n }\n\n if (inputs.requirements && Array.isArray(inputs.requirements)) {\n constraints.push(...(inputs.requirements as string[]));\n }\n\n if (inputs.limitations && Array.isArray(inputs.limitations)) {\n constraints.push(...(inputs.limitations as string[]));\n }\n\n return constraints;\n }\n\n /**\n * Detect if setting a parent frame would create a cycle in the frame hierarchy.\n * Returns the cycle path if detected, or null if no cycle.\n * @param childFrameId - The frame that would be the child\n * @param parentFrameId - The proposed parent frame\n * @returns Array of frame IDs forming the cycle, or null if no cycle\n */\n private detectCycle(\n childFrameId: string,\n parentFrameId: string\n ): string[] | null {\n const visited = new Set<string>();\n const path: string[] = [];\n\n // Start from the proposed parent and traverse up the hierarchy\n let currentId: string | undefined = parentFrameId;\n\n while (currentId) {\n // If we've seen this frame before, we have a cycle\n if (visited.has(currentId)) {\n // Build the cycle path\n const cycleStart = path.indexOf(currentId);\n return path.slice(cycleStart).concat(currentId);\n }\n\n // If the current frame is the child we're trying to add, it's a cycle\n if (currentId === childFrameId) {\n return path.concat([currentId, childFrameId]);\n }\n\n visited.add(currentId);\n path.push(currentId);\n\n // Move to the parent of current frame\n const frame = this.frameDb.getFrame(currentId);\n if (!frame) {\n // Frame not found, no cycle possible through this path\n break;\n }\n currentId = frame.parent_frame_id;\n\n // Safety check: if we've traversed too many levels, something is wrong\n if (path.length > this.maxFrameDepth) {\n throw new FrameError(\n `Frame hierarchy traversal exceeded maximum depth during cycle detection`,\n ErrorCode.FRAME_STACK_OVERFLOW,\n {\n depth: path.length,\n maxDepth: this.maxFrameDepth,\n path,\n }\n );\n }\n }\n\n return null; // No cycle detected\n }\n\n /**\n * Update parent frame of an existing frame (with cycle detection)\n * @param frameId - The frame to update\n * @param newParentFrameId - The new parent frame ID (null to make it a root frame)\n */\n public updateParentFrame(\n frameId: string,\n newParentFrameId: string | null\n ): void {\n // Check if frame exists\n const frame = this.frameDb.getFrame(frameId);\n if (!frame) {\n throw new FrameError(\n `Frame not found: ${frameId}`,\n ErrorCode.FRAME_NOT_FOUND,\n { frameId }\n );\n }\n\n // If setting a parent, validate and check for cycles\n if (newParentFrameId) {\n // Verify the new parent exists\n const newParentFrame = this.frameDb.getFrame(newParentFrameId);\n if (!newParentFrame) {\n throw new FrameError(\n `Parent frame not found: ${newParentFrameId}`,\n ErrorCode.FRAME_NOT_FOUND,\n { frameId, newParentFrameId }\n );\n }\n\n const cycle = this.detectCycle(frameId, newParentFrameId);\n if (cycle) {\n throw new FrameError(\n `Cannot set parent: would create circular reference`,\n ErrorCode.FRAME_CYCLE_DETECTED,\n {\n frameId,\n newParentFrameId,\n cycle,\n currentParentId: frame.parent_frame_id,\n }\n );\n }\n\n // Check depth after parent change\n const newDepth = newParentFrame.depth + 1;\n if (newDepth > this.maxFrameDepth) {\n throw new FrameError(\n `Cannot set parent: would exceed maximum frame depth`,\n ErrorCode.FRAME_STACK_OVERFLOW,\n {\n frameId,\n newParentFrameId,\n newDepth,\n maxDepth: this.maxFrameDepth,\n }\n );\n }\n }\n\n // Calculate new depth based on parent\n let newDepth = 0;\n if (newParentFrameId) {\n const newParentFrame = this.frameDb.getFrame(newParentFrameId);\n if (newParentFrame) {\n newDepth = newParentFrame.depth + 1;\n }\n }\n\n // Update the frame's parent and depth\n this.frameDb.updateFrame(frameId, {\n parent_frame_id: newParentFrameId,\n depth: newDepth,\n });\n\n logger.info('Updated parent frame', {\n frameId,\n oldParentId: frame.parent_frame_id,\n newParentId: newParentFrameId,\n });\n }\n\n /**\n * Validate the entire frame hierarchy for cycles and depth violations\n * @returns Validation result with any detected issues\n */\n public validateFrameHierarchy(): {\n isValid: boolean;\n errors: string[];\n warnings: string[];\n } {\n const errors: string[] = [];\n const warnings: string[] = [];\n const allFrames = this.frameDb.getFramesByProject(this.projectId);\n\n // Check each frame for depth violations\n for (const frame of allFrames) {\n if (frame.depth > this.maxFrameDepth) {\n errors.push(\n `Frame ${frame.frame_id} exceeds max depth: ${frame.depth} > ${this.maxFrameDepth}`\n );\n }\n\n // Warn about deep frames approaching the limit\n if (frame.depth > this.maxFrameDepth * 0.8) {\n warnings.push(\n `Frame ${frame.frame_id} is deep in hierarchy: ${frame.depth}/${this.maxFrameDepth}`\n );\n }\n }\n\n // Check for cycles by traversing from each root\n const rootFrames = allFrames.filter((f) => !f.parent_frame_id);\n const visited = new Set<string>();\n const visiting = new Set<string>();\n\n const checkForCycle = (frameId: string): boolean => {\n if (visiting.has(frameId)) {\n errors.push(`Cycle detected involving frame ${frameId}`);\n return true;\n }\n\n if (visited.has(frameId)) {\n return false;\n }\n\n visiting.add(frameId);\n\n // Check all children\n const children = allFrames.filter((f) => f.parent_frame_id === frameId);\n for (const child of children) {\n if (checkForCycle(child.frame_id)) {\n return true;\n }\n }\n\n visiting.delete(frameId);\n visited.add(frameId);\n return false;\n };\n\n // Check from each root\n for (const root of rootFrames) {\n checkForCycle(root.frame_id);\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n warnings,\n };\n }\n\n /**\n * Set query mode for frame retrieval\n */\n setQueryMode(mode: FrameQueryMode): void {\n this.queryMode = mode;\n // Reinitialize stack with new query mode\n this.frameStack.setQueryMode(mode);\n }\n\n /**\n * Get recent frames for context sharing\n */\n async getRecentFrames(limit: number = 100): Promise<Frame[]> {\n try {\n const frames = this.frameDb.getFramesByProject(this.projectId);\n\n // Sort by created_at descending and limit\n return frames\n .sort((a, b) => (b.created_at || 0) - (a.created_at || 0))\n .slice(0, limit)\n .map((frame) => ({\n ...frame,\n // Add compatibility fields\n frameId: frame.frame_id,\n runId: frame.run_id,\n projectId: frame.project_id,\n parentFrameId: frame.parent_frame_id,\n title: frame.name,\n timestamp: frame.created_at,\n metadata: {\n tags: this.extractTagsFromFrame(frame),\n importance: this.calculateFrameImportance(frame),\n },\n data: {\n inputs: frame.inputs,\n outputs: frame.outputs,\n digest: frame.digest_json,\n },\n }));\n } catch (error: unknown) {\n logger.error('Failed to get recent frames', error as Error);\n return [];\n }\n }\n\n /**\n * Add context metadata to the current frame\n */\n async addContext(key: string, value: any): Promise<void> {\n const currentFrameId = this.frameStack.getCurrentFrameId();\n if (!currentFrameId) return;\n\n try {\n const frame = this.frameDb.getFrame(currentFrameId);\n if (!frame) return;\n\n const metadata = frame.outputs || {};\n metadata[key] = value;\n\n this.frameDb.updateFrame(currentFrameId, {\n outputs: metadata,\n });\n } catch (error: unknown) {\n logger.warn('Failed to add context to frame', { error, key });\n }\n }\n\n /**\n * Delete a frame completely from the database (used in handoffs)\n */\n deleteFrame(frameId: string): void {\n try {\n // Remove from active stack if present\n this.frameStack.removeFrame(frameId);\n\n // Delete the frame and related data (cascades via FrameDatabase)\n this.frameDb.deleteFrame(frameId);\n\n logger.debug('Deleted frame completely', { frameId });\n } catch (error: unknown) {\n logger.error('Failed to delete frame', { frameId, error });\n throw error;\n }\n }\n\n /**\n * Extract tags from frame for categorization\n */\n private extractTagsFromFrame(frame: Frame): string[] {\n const tags: string[] = [];\n\n if (frame.type) tags.push(frame.type);\n\n if (frame.name) {\n const nameLower = frame.name.toLowerCase();\n if (nameLower.includes('error')) tags.push('error');\n if (nameLower.includes('fix')) tags.push('resolution');\n if (nameLower.includes('decision')) tags.push('decision');\n if (nameLower.includes('milestone')) tags.push('milestone');\n }\n\n try {\n if (frame.digest_json && typeof frame.digest_json === 'object') {\n const digest = frame.digest_json as Record<string, unknown>;\n if (Array.isArray(digest.tags)) {\n tags.push(...(digest.tags as string[]));\n }\n }\n } catch {\n // Ignore parse errors\n }\n\n return [...new Set(tags)];\n }\n\n /**\n * Calculate frame importance for prioritization\n */\n private calculateFrameImportance(frame: Frame): 'high' | 'medium' | 'low' {\n if (frame.type === 'milestone' || frame.name?.includes('decision')) {\n return 'high';\n }\n\n if (frame.type === 'error' || frame.type === 'resolution') {\n return 'medium';\n }\n\n if (frame.closed_at && frame.created_at) {\n const duration = frame.closed_at - frame.created_at;\n if (duration > 300) return 'medium';\n }\n\n return 'low';\n }\n}\n\n// Re-export types for compatibility (type-only, no runtime value)\nexport type {\n Frame,\n FrameContext,\n Anchor,\n Event,\n FrameType,\n FrameState,\n FrameCreationOptions,\n FrameManagerConfig,\n DigestResult,\n} from './frame-types.js';\n"],
5
+ "mappings": "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",
6
6
  "names": ["newDepth"]
7
7
  }
@@ -107,13 +107,35 @@ class AnthropicLLMProvider {
107
107
  return new Promise((resolve) => setTimeout(resolve, ms));
108
108
  }
109
109
  }
110
+ class LocalFallbackProvider {
111
+ async analyze(prompt, maxTokens) {
112
+ const lines = prompt.split("\n").filter((l) => l.trim());
113
+ const contentStart = lines.findIndex((l) => l.includes("Content:"));
114
+ if (contentStart === -1 || lines.length < 3) {
115
+ return "Context summary not available (local mode)";
116
+ }
117
+ const content = lines.slice(contentStart + 1).join("\n");
118
+ const sentences = content.split(/[.!?]+/).filter((s) => s.trim().length > 10);
119
+ const maxChars = maxTokens * 4;
120
+ let summary = "";
121
+ for (const sentence of sentences.slice(0, 5)) {
122
+ if (summary.length + sentence.length > maxChars) break;
123
+ summary += sentence.trim() + ". ";
124
+ }
125
+ return summary.trim() || "Context available (use LLM API for detailed analysis)";
126
+ }
127
+ }
110
128
  function createLLMProvider() {
129
+ if (process.env["STACKMEMORY_LOCAL"] === "true" || process.env["LOCAL_ONLY"] === "true") {
130
+ logger.info("LOCAL mode - using heuristic summarization");
131
+ return new LocalFallbackProvider();
132
+ }
111
133
  const apiKey = process.env["ANTHROPIC_API_KEY"];
112
134
  if (!apiKey) {
113
135
  logger.info(
114
136
  "No ANTHROPIC_API_KEY found, LLM retrieval will use heuristics"
115
137
  );
116
- return void 0;
138
+ return new LocalFallbackProvider();
117
139
  }
118
140
  return new AnthropicLLMProvider({
119
141
  apiKey,
@@ -123,6 +145,7 @@ function createLLMProvider() {
123
145
  }
124
146
  export {
125
147
  AnthropicLLMProvider,
148
+ LocalFallbackProvider,
126
149
  createLLMProvider
127
150
  };
128
151
  //# sourceMappingURL=llm-provider.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/core/retrieval/llm-provider.ts"],
4
- "sourcesContent": ["/**\n * LLM Provider Implementation for Context Retrieval\n * Provides real Anthropic API integration for intelligent context analysis\n */\n\nimport Anthropic from '@anthropic-ai/sdk';\nimport { logger } from '../monitoring/logger.js';\n\n/**\n * LLM provider interface for context analysis\n */\nexport interface LLMProvider {\n analyze(prompt: string, maxTokens: number): Promise<string>;\n}\n\n/**\n * Configuration for Anthropic LLM provider\n */\nexport interface AnthropicProviderConfig {\n apiKey: string;\n model?: string;\n temperature?: number;\n maxRetries?: number;\n timeout?: number;\n}\n\n/**\n * Real Anthropic LLM provider using the official SDK\n */\nexport class AnthropicLLMProvider implements LLMProvider {\n private client: Anthropic;\n private model: string;\n private temperature: number;\n private maxRetries: number;\n private timeout: number;\n\n constructor(config: AnthropicProviderConfig) {\n this.client = new Anthropic({\n apiKey: config.apiKey,\n });\n this.model = config.model || 'claude-3-haiku-20240307';\n this.temperature = config.temperature ?? 0.3;\n this.maxRetries = config.maxRetries ?? 2;\n this.timeout = config.timeout ?? 30000;\n\n logger.info('AnthropicLLMProvider initialized', {\n model: this.model,\n temperature: this.temperature,\n });\n }\n\n /**\n * Analyze a prompt using the Anthropic API\n */\n async analyze(prompt: string, maxTokens: number): Promise<string> {\n const startTime = Date.now();\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n try {\n const response = await this.makeRequest(prompt, maxTokens);\n\n logger.debug('LLM analysis completed', {\n model: this.model,\n promptLength: prompt.length,\n responseLength: response.length,\n durationMs: Date.now() - startTime,\n attempt,\n });\n\n return response;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Check if retryable\n if (this.isRetryableError(error) && attempt < this.maxRetries) {\n const backoffMs = Math.pow(2, attempt) * 1000;\n logger.warn('LLM request failed, retrying', {\n attempt,\n backoffMs,\n error: lastError.message,\n });\n await this.sleep(backoffMs);\n continue;\n }\n\n break;\n }\n }\n\n logger.error('LLM analysis failed after retries', lastError!);\n throw lastError;\n }\n\n /**\n * Make the actual API request\n */\n private async makeRequest(\n prompt: string,\n maxTokens: number\n ): Promise<string> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await this.client.messages.create({\n model: this.model,\n max_tokens: maxTokens,\n temperature: this.temperature,\n messages: [\n {\n role: 'user',\n content: prompt,\n },\n ],\n });\n\n // Extract text from response\n const textContent = response.content.find((c) => c.type === 'text');\n if (!textContent || textContent.type !== 'text') {\n throw new Error('No text content in response');\n }\n\n return textContent.text;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Check if an error is retryable\n */\n private isRetryableError(error: unknown): boolean {\n if (error instanceof Anthropic.RateLimitError) {\n return true;\n }\n if (error instanceof Anthropic.APIConnectionError) {\n return true;\n }\n if (error instanceof Anthropic.InternalServerError) {\n return true;\n }\n // Timeout errors are retryable\n if (error instanceof Error && error.name === 'AbortError') {\n return true;\n }\n return false;\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n\n/**\n * Factory function to create an LLM provider based on environment\n */\nexport function createLLMProvider(): LLMProvider | undefined {\n const apiKey = process.env['ANTHROPIC_API_KEY'];\n\n if (!apiKey) {\n logger.info(\n 'No ANTHROPIC_API_KEY found, LLM retrieval will use heuristics'\n );\n return undefined;\n }\n\n return new AnthropicLLMProvider({\n apiKey,\n model: process.env['ANTHROPIC_MODEL'] || 'claude-3-haiku-20240307',\n temperature: parseFloat(process.env['ANTHROPIC_TEMPERATURE'] || '0.3'),\n });\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * LLM Provider Implementation for Context Retrieval\n * Provides real Anthropic API integration for intelligent context analysis\n */\n\nimport Anthropic from '@anthropic-ai/sdk';\nimport { logger } from '../monitoring/logger.js';\n\n/**\n * LLM provider interface for context analysis\n */\nexport interface LLMProvider {\n analyze(prompt: string, maxTokens: number): Promise<string>;\n}\n\n/**\n * Configuration for Anthropic LLM provider\n */\nexport interface AnthropicProviderConfig {\n apiKey: string;\n model?: string;\n temperature?: number;\n maxRetries?: number;\n timeout?: number;\n}\n\n/**\n * Real Anthropic LLM provider using the official SDK\n */\nexport class AnthropicLLMProvider implements LLMProvider {\n private client: Anthropic;\n private model: string;\n private temperature: number;\n private maxRetries: number;\n private timeout: number;\n\n constructor(config: AnthropicProviderConfig) {\n this.client = new Anthropic({\n apiKey: config.apiKey,\n });\n this.model = config.model || 'claude-3-haiku-20240307';\n this.temperature = config.temperature ?? 0.3;\n this.maxRetries = config.maxRetries ?? 2;\n this.timeout = config.timeout ?? 30000;\n\n logger.info('AnthropicLLMProvider initialized', {\n model: this.model,\n temperature: this.temperature,\n });\n }\n\n /**\n * Analyze a prompt using the Anthropic API\n */\n async analyze(prompt: string, maxTokens: number): Promise<string> {\n const startTime = Date.now();\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n try {\n const response = await this.makeRequest(prompt, maxTokens);\n\n logger.debug('LLM analysis completed', {\n model: this.model,\n promptLength: prompt.length,\n responseLength: response.length,\n durationMs: Date.now() - startTime,\n attempt,\n });\n\n return response;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Check if retryable\n if (this.isRetryableError(error) && attempt < this.maxRetries) {\n const backoffMs = Math.pow(2, attempt) * 1000;\n logger.warn('LLM request failed, retrying', {\n attempt,\n backoffMs,\n error: lastError.message,\n });\n await this.sleep(backoffMs);\n continue;\n }\n\n break;\n }\n }\n\n logger.error('LLM analysis failed after retries', lastError!);\n throw lastError;\n }\n\n /**\n * Make the actual API request\n */\n private async makeRequest(\n prompt: string,\n maxTokens: number\n ): Promise<string> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await this.client.messages.create({\n model: this.model,\n max_tokens: maxTokens,\n temperature: this.temperature,\n messages: [\n {\n role: 'user',\n content: prompt,\n },\n ],\n });\n\n // Extract text from response\n const textContent = response.content.find((c) => c.type === 'text');\n if (!textContent || textContent.type !== 'text') {\n throw new Error('No text content in response');\n }\n\n return textContent.text;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Check if an error is retryable\n */\n private isRetryableError(error: unknown): boolean {\n if (error instanceof Anthropic.RateLimitError) {\n return true;\n }\n if (error instanceof Anthropic.APIConnectionError) {\n return true;\n }\n if (error instanceof Anthropic.InternalServerError) {\n return true;\n }\n // Timeout errors are retryable\n if (error instanceof Error && error.name === 'AbortError') {\n return true;\n }\n return false;\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n\n/**\n * Local fallback LLM provider - uses heuristic summarization without external APIs\n * This ensures StackMemory works in LOCAL_ONLY mode\n */\nexport class LocalFallbackProvider implements LLMProvider {\n async analyze(prompt: string, maxTokens: number): Promise<string> {\n // Extract content from prompt and create a heuristic summary\n const lines = prompt.split('\\n').filter((l) => l.trim());\n const contentStart = lines.findIndex((l) => l.includes('Content:'));\n\n if (contentStart === -1 || lines.length < 3) {\n return 'Context summary not available (local mode)';\n }\n\n // Extract key information heuristically\n const content = lines.slice(contentStart + 1).join('\\n');\n const sentences = content\n .split(/[.!?]+/)\n .filter((s) => s.trim().length > 10);\n\n // Take first few sentences up to maxTokens (rough approximation: 4 chars = 1 token)\n const maxChars = maxTokens * 4;\n let summary = '';\n for (const sentence of sentences.slice(0, 5)) {\n if (summary.length + sentence.length > maxChars) break;\n summary += sentence.trim() + '. ';\n }\n\n return (\n summary.trim() || 'Context available (use LLM API for detailed analysis)'\n );\n }\n}\n\n/**\n * Factory function to create an LLM provider based on environment\n */\nexport function createLLMProvider(): LLMProvider | undefined {\n // Check for local-only mode\n if (\n process.env['STACKMEMORY_LOCAL'] === 'true' ||\n process.env['LOCAL_ONLY'] === 'true'\n ) {\n logger.info('LOCAL mode - using heuristic summarization');\n return new LocalFallbackProvider();\n }\n\n const apiKey = process.env['ANTHROPIC_API_KEY'];\n\n if (!apiKey) {\n logger.info(\n 'No ANTHROPIC_API_KEY found, LLM retrieval will use heuristics'\n );\n return new LocalFallbackProvider();\n }\n\n return new AnthropicLLMProvider({\n apiKey,\n model: process.env['ANTHROPIC_MODEL'] || 'claude-3-haiku-20240307',\n temperature: parseFloat(process.env['ANTHROPIC_TEMPERATURE'] || '0.3'),\n });\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -11,6 +11,45 @@ import { writeFileSecure, ensureSecureDir } from "./secure-fs.js";
11
11
  import { ActionQueueSchema, parseConfigSafe } from "./schemas.js";
12
12
  import { LinearClient } from "../integrations/linear/client.js";
13
13
  import { LinearAuthManager } from "../integrations/linear/auth.js";
14
+ function parseCommandArgs(command) {
15
+ const args = [];
16
+ let current = "";
17
+ let inSingleQuote = false;
18
+ let inDoubleQuote = false;
19
+ let escaped = false;
20
+ for (let i = 0; i < command.length; i++) {
21
+ const char = command[i];
22
+ if (escaped) {
23
+ current += char;
24
+ escaped = false;
25
+ continue;
26
+ }
27
+ if (char === "\\" && !inSingleQuote) {
28
+ escaped = true;
29
+ continue;
30
+ }
31
+ if (char === "'" && !inDoubleQuote) {
32
+ inSingleQuote = !inSingleQuote;
33
+ continue;
34
+ }
35
+ if (char === '"' && !inSingleQuote) {
36
+ inDoubleQuote = !inDoubleQuote;
37
+ continue;
38
+ }
39
+ if (char === " " && !inSingleQuote && !inDoubleQuote) {
40
+ if (current.length > 0) {
41
+ args.push(current);
42
+ current = "";
43
+ }
44
+ continue;
45
+ }
46
+ current += char;
47
+ }
48
+ if (current.length > 0) {
49
+ args.push(current);
50
+ }
51
+ return args;
52
+ }
14
53
  const SAFE_ACTION_PATTERNS = [
15
54
  // Git/GitHub CLI commands (limited to safe operations)
16
55
  { pattern: /^gh pr (view|list|status|checks) (\d+)$/ },
@@ -173,7 +212,10 @@ async function executeActionSafe(action, _response) {
173
212
  }
174
213
  try {
175
214
  console.log(`[sms-action] Executing safe action: ${action}`);
176
- const parts = action.split(" ");
215
+ const parts = parseCommandArgs(action);
216
+ if (parts.length === 0) {
217
+ return { success: false, error: "Empty command" };
218
+ }
177
219
  const cmd = parts[0];
178
220
  const args = parts.slice(1);
179
221
  const output = execFileSync(cmd, args, {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/hooks/sms-action-runner.ts"],
4
- "sourcesContent": ["/**\n * SMS Action Runner - Executes actions based on SMS responses\n * Bridges SMS responses to Claude Code actions\n *\n * Security: Uses allowlist-based action execution to prevent command injection\n */\n\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { execFileSync } from 'child_process';\nimport { randomBytes } from 'crypto';\nimport { writeFileSecure, ensureSecureDir } from './secure-fs.js';\nimport { ActionQueueSchema, parseConfigSafe } from './schemas.js';\nimport { LinearClient } from '../integrations/linear/client.js';\nimport { LinearAuthManager } from '../integrations/linear/auth.js';\n\n// Allowlist of safe action patterns\nconst SAFE_ACTION_PATTERNS: Array<{\n pattern: RegExp;\n validate?: (match: RegExpMatchArray) => boolean;\n}> = [\n // Git/GitHub CLI commands (limited to safe operations)\n { pattern: /^gh pr (view|list|status|checks) (\\d+)$/ },\n { pattern: /^gh pr review (\\d+) --approve$/ },\n { pattern: /^gh pr merge (\\d+) --squash$/ },\n { pattern: /^gh issue (view|list) (\\d+)?$/ },\n\n // NPM commands (limited to safe operations)\n { pattern: /^npm run (build|test|lint|lint:fix|test:run)$/ },\n { pattern: /^npm (test|run build)$/ },\n\n // StackMemory commands\n { pattern: /^stackmemory (status|notify check|context list)$/ },\n // Task start with optional --assign-me flag (Linear task ID is UUID format)\n {\n pattern: /^stackmemory task start ([a-f0-9-]{36})( --assign-me)?$/,\n },\n // Additional StackMemory commands for mobile/WhatsApp\n { pattern: /^stackmemory context show$/ },\n { pattern: /^stackmemory task list$/ },\n\n // Git commands\n { pattern: /^git (status|diff|log|branch)( --[a-z-]+)*$/ },\n { pattern: /^git add -A && git commit$/ },\n { pattern: /^gh pr create --fill$/ },\n // Git log with line limit for mobile-friendly output\n { pattern: /^git log --oneline -\\d{1,2}$/ },\n\n // WhatsApp/Mobile quick commands\n { pattern: /^status$/i },\n { pattern: /^tasks$/i },\n { pattern: /^context$/i },\n { pattern: /^help$/i },\n { pattern: /^sync$/i },\n\n // Claude Code launcher\n { pattern: /^claude-sm$/ },\n\n // Log viewing (safe read-only)\n { pattern: /^tail -\\d+ ~\\/\\.claude\\/logs\\/\\*\\.log$/ },\n\n // Custom aliases (cwm = claude worktree merge)\n { pattern: /^cwm$/ },\n\n // Simple echo/confirmation (no variables)\n {\n pattern:\n /^echo \"?(Done|OK|Confirmed|Acknowledged|Great work! Time for a coffee break\\.)\"?$/,\n },\n];\n\n/**\n * Check if an action is in the allowlist\n */\nfunction isActionAllowed(action: string): boolean {\n const trimmed = action.trim();\n return SAFE_ACTION_PATTERNS.some(({ pattern, validate }) => {\n const match = trimmed.match(pattern);\n if (!match) return false;\n if (validate && !validate(match)) return false;\n return true;\n });\n}\n\nexport interface PendingAction {\n id: string;\n promptId: string;\n response: string;\n action: string;\n timestamp: string;\n status: 'pending' | 'running' | 'completed' | 'failed';\n result?: string;\n error?: string;\n}\n\nexport interface ActionQueue {\n actions: PendingAction[];\n lastChecked: string;\n}\n\nconst QUEUE_PATH = join(homedir(), '.stackmemory', 'sms-action-queue.json');\n\nconst DEFAULT_QUEUE: ActionQueue = {\n actions: [],\n lastChecked: new Date().toISOString(),\n};\n\nexport function loadActionQueue(): ActionQueue {\n try {\n if (existsSync(QUEUE_PATH)) {\n const data = JSON.parse(readFileSync(QUEUE_PATH, 'utf8'));\n return parseConfigSafe(\n ActionQueueSchema,\n data,\n DEFAULT_QUEUE,\n 'action-queue'\n );\n }\n } catch {\n // Use defaults\n }\n return { ...DEFAULT_QUEUE, lastChecked: new Date().toISOString() };\n}\n\nexport function saveActionQueue(queue: ActionQueue): void {\n try {\n ensureSecureDir(join(homedir(), '.stackmemory'));\n writeFileSecure(QUEUE_PATH, JSON.stringify(queue, null, 2));\n } catch {\n // Silently fail\n }\n}\n\nexport function queueAction(\n promptId: string,\n response: string,\n action: string\n): string {\n const queue = loadActionQueue();\n // Use cryptographically secure random ID\n const id = randomBytes(8).toString('hex');\n\n queue.actions.push({\n id,\n promptId,\n response,\n action,\n timestamp: new Date().toISOString(),\n status: 'pending',\n });\n\n saveActionQueue(queue);\n return id;\n}\n\n/**\n * Get Linear client if available\n * Returns null if credentials are missing or invalid\n */\nfunction getLinearClient(): LinearClient | null {\n // Try API key first - must be valid format (lin_api_*)\n const apiKey = process.env['LINEAR_API_KEY'];\n if (apiKey && apiKey.startsWith('lin_api_')) {\n return new LinearClient({ apiKey });\n }\n\n try {\n const authManager = new LinearAuthManager();\n const tokens = authManager.loadTokens();\n if (tokens?.accessToken) {\n return new LinearClient({ accessToken: tokens.accessToken });\n }\n } catch {\n // Auth not available\n }\n\n return null;\n}\n\n/**\n * Handle special actions that require API calls instead of shell commands\n */\nasync function handleSpecialAction(action: string): Promise<{\n handled: boolean;\n success?: boolean;\n output?: string;\n error?: string;\n}> {\n // Handle stackmemory task start command\n const taskStartMatch = action.match(\n /^stackmemory task start ([a-f0-9-]{36})( --assign-me)?$/\n );\n if (taskStartMatch) {\n const issueId = taskStartMatch[1];\n const client = getLinearClient();\n\n if (!client) {\n return {\n handled: true,\n success: false,\n error:\n 'Linear not configured. Set LINEAR_API_KEY or run stackmemory linear setup.',\n };\n }\n\n try {\n const result = await client.startIssue(issueId);\n if (result.success && result.issue) {\n return {\n handled: true,\n success: true,\n output: `Started: ${result.issue.identifier} - ${result.issue.title}`,\n };\n }\n return {\n handled: true,\n success: false,\n error: result.error || 'Failed to start issue',\n };\n } catch (err) {\n return {\n handled: true,\n success: false,\n error: err instanceof Error ? err.message : 'Unknown error',\n };\n }\n }\n\n return { handled: false };\n}\n\n/**\n * Execute an action safely using allowlist validation\n * This prevents command injection by only allowing pre-approved commands\n */\nexport async function executeActionSafe(\n action: string,\n _response: string\n): Promise<{ success: boolean; output?: string; error?: string }> {\n // Check if action is in allowlist\n if (!isActionAllowed(action)) {\n console.error(`[sms-action] Action not in allowlist: ${action}`);\n return {\n success: false,\n error: `Action not allowed. Only pre-approved commands can be executed via SMS.`,\n };\n }\n\n // Check for special actions that need API calls\n const specialResult = await handleSpecialAction(action);\n if (specialResult.handled) {\n return {\n success: specialResult.success || false,\n output: specialResult.output,\n error: specialResult.error,\n };\n }\n\n try {\n console.log(`[sms-action] Executing safe action: ${action}`);\n\n // Parse the action into command and args\n const parts = action.split(' ');\n const cmd = parts[0];\n const args = parts.slice(1);\n\n // Use execFileSync for commands without shell interpretation\n // This prevents shell injection even if the allowlist is somehow bypassed\n const output = execFileSync(cmd, args, {\n encoding: 'utf8',\n timeout: 60000,\n stdio: ['pipe', 'pipe', 'pipe'],\n shell: false, // Explicitly disable shell\n });\n\n return { success: true, output };\n } catch (err) {\n const error = err instanceof Error ? err.message : String(err);\n return { success: false, error };\n }\n}\n\nexport function getPendingActions(): PendingAction[] {\n const queue = loadActionQueue();\n return queue.actions.filter((a) => a.status === 'pending');\n}\n\nexport function markActionRunning(id: string): void {\n const queue = loadActionQueue();\n const action = queue.actions.find((a) => a.id === id);\n if (action) {\n action.status = 'running';\n saveActionQueue(queue);\n }\n}\n\nexport function markActionCompleted(\n id: string,\n result?: string,\n error?: string\n): void {\n const queue = loadActionQueue();\n const action = queue.actions.find((a) => a.id === id);\n if (action) {\n action.status = error ? 'failed' : 'completed';\n action.result = result;\n action.error = error;\n saveActionQueue(queue);\n }\n}\n\nexport async function executeAction(action: PendingAction): Promise<{\n success: boolean;\n output?: string;\n error?: string;\n}> {\n markActionRunning(action.id);\n\n // Use the safe execution path to prevent command injection\n const result = await executeActionSafe(action.action, action.response);\n\n if (result.success) {\n markActionCompleted(action.id, result.output);\n } else {\n markActionCompleted(action.id, undefined, result.error);\n }\n\n return result;\n}\n\nexport async function processAllPendingActions(): Promise<{\n processed: number;\n succeeded: number;\n failed: number;\n}> {\n const pending = getPendingActions();\n let succeeded = 0;\n let failed = 0;\n\n for (const action of pending) {\n const result = await executeAction(action);\n if (result.success) {\n succeeded++;\n } else {\n failed++;\n }\n }\n\n return { processed: pending.length, succeeded, failed };\n}\n\n// Clean up old completed actions (keep last 50)\nexport function cleanupOldActions(): number {\n const queue = loadActionQueue();\n const completed = queue.actions.filter(\n (a) => a.status === 'completed' || a.status === 'failed'\n );\n\n if (completed.length > 50) {\n const toRemove = completed.slice(0, completed.length - 50);\n queue.actions = queue.actions.filter(\n (a) => !toRemove.find((r) => r.id === a.id)\n );\n saveActionQueue(queue);\n return toRemove.length;\n }\n\n return 0;\n}\n\n/**\n * Action Templates - Common actions for SMS responses\n *\n * SECURITY NOTE: These templates return command strings that must be\n * validated against SAFE_ACTION_PATTERNS before execution.\n * Templates that accept user input are removed to prevent injection.\n */\nexport const ACTION_TEMPLATES = {\n // Git/PR actions (PR numbers must be validated as integers)\n approvePR: (prNumber: string) => {\n // Validate PR number is numeric only\n if (!/^\\d+$/.test(prNumber)) {\n throw new Error('Invalid PR number');\n }\n return `gh pr review ${prNumber} --approve`;\n },\n mergePR: (prNumber: string) => {\n if (!/^\\d+$/.test(prNumber)) {\n throw new Error('Invalid PR number');\n }\n return `gh pr merge ${prNumber} --squash`;\n },\n viewPR: (prNumber: string) => {\n if (!/^\\d+$/.test(prNumber)) {\n throw new Error('Invalid PR number');\n }\n return `gh pr view ${prNumber}`;\n },\n\n // Build actions (no user input)\n rebuild: () => `npm run build`,\n retest: () => `npm run test:run`,\n lint: () => `npm run lint:fix`,\n\n // Status actions (no user input)\n status: () => `stackmemory status`,\n checkNotifications: () => `stackmemory notify check`,\n\n // REMOVED for security - these templates allowed arbitrary user input:\n // - requestChanges (allowed arbitrary message)\n // - closePR (could be used maliciously)\n // - deploy/rollback (too dangerous for SMS)\n // - verifyDeployment (allowed arbitrary URL)\n // - notifySlack (allowed arbitrary message - command injection)\n // - notifyTeam (allowed arbitrary message - command injection)\n};\n\n/**\n * Create action string from template\n */\nexport function createAction(\n template: keyof typeof ACTION_TEMPLATES,\n ...args: string[]\n): string {\n const fn = ACTION_TEMPLATES[template];\n if (typeof fn === 'function') {\n return (fn as (...args: string[]) => string)(...args);\n }\n return fn;\n}\n\n/**\n * Watch for new actions and execute them\n */\nexport function startActionWatcher(intervalMs: number = 5000): NodeJS.Timeout {\n console.log(\n `[sms-action] Starting action watcher (interval: ${intervalMs}ms)`\n );\n\n return setInterval(() => {\n const pending = getPendingActions();\n if (pending.length > 0) {\n console.log(`[sms-action] Found ${pending.length} pending action(s)`);\n processAllPendingActions();\n }\n }, intervalMs);\n}\n\n/**\n * Integration with SMS webhook - queue action when response received\n */\nexport function handleSMSResponse(\n promptId: string,\n response: string,\n action?: string\n): void {\n if (action) {\n const actionId = queueAction(promptId, response, action);\n console.log(`[sms-action] Queued action ${actionId}: ${action}`);\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * SMS Action Runner - Executes actions based on SMS responses\n * Bridges SMS responses to Claude Code actions\n *\n * Security: Uses allowlist-based action execution to prevent command injection\n */\n\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { execFileSync } from 'child_process';\nimport { randomBytes } from 'crypto';\nimport { writeFileSecure, ensureSecureDir } from './secure-fs.js';\nimport { ActionQueueSchema, parseConfigSafe } from './schemas.js';\nimport { LinearClient } from '../integrations/linear/client.js';\nimport { LinearAuthManager } from '../integrations/linear/auth.js';\n\n/**\n * Parse a command string into an array of arguments, respecting quotes\n * Handles single quotes, double quotes, and escaped characters\n *\n * Examples:\n * 'echo \"hello world\"' -> ['echo', 'hello world']\n * \"git commit -m 'fix bug'\" -> ['git', 'commit', '-m', 'fix bug']\n * 'npm run build' -> ['npm', 'run', 'build']\n */\nfunction parseCommandArgs(command: string): string[] {\n const args: string[] = [];\n let current = '';\n let inSingleQuote = false;\n let inDoubleQuote = false;\n let escaped = false;\n\n for (let i = 0; i < command.length; i++) {\n const char = command[i];\n\n if (escaped) {\n current += char;\n escaped = false;\n continue;\n }\n\n if (char === '\\\\' && !inSingleQuote) {\n escaped = true;\n continue;\n }\n\n if (char === \"'\" && !inDoubleQuote) {\n inSingleQuote = !inSingleQuote;\n continue;\n }\n\n if (char === '\"' && !inSingleQuote) {\n inDoubleQuote = !inDoubleQuote;\n continue;\n }\n\n if (char === ' ' && !inSingleQuote && !inDoubleQuote) {\n if (current.length > 0) {\n args.push(current);\n current = '';\n }\n continue;\n }\n\n current += char;\n }\n\n if (current.length > 0) {\n args.push(current);\n }\n\n return args;\n}\n\n// Allowlist of safe action patterns\nconst SAFE_ACTION_PATTERNS: Array<{\n pattern: RegExp;\n validate?: (match: RegExpMatchArray) => boolean;\n}> = [\n // Git/GitHub CLI commands (limited to safe operations)\n { pattern: /^gh pr (view|list|status|checks) (\\d+)$/ },\n { pattern: /^gh pr review (\\d+) --approve$/ },\n { pattern: /^gh pr merge (\\d+) --squash$/ },\n { pattern: /^gh issue (view|list) (\\d+)?$/ },\n\n // NPM commands (limited to safe operations)\n { pattern: /^npm run (build|test|lint|lint:fix|test:run)$/ },\n { pattern: /^npm (test|run build)$/ },\n\n // StackMemory commands\n { pattern: /^stackmemory (status|notify check|context list)$/ },\n // Task start with optional --assign-me flag (Linear task ID is UUID format)\n {\n pattern: /^stackmemory task start ([a-f0-9-]{36})( --assign-me)?$/,\n },\n // Additional StackMemory commands for mobile/WhatsApp\n { pattern: /^stackmemory context show$/ },\n { pattern: /^stackmemory task list$/ },\n\n // Git commands\n { pattern: /^git (status|diff|log|branch)( --[a-z-]+)*$/ },\n { pattern: /^git add -A && git commit$/ },\n { pattern: /^gh pr create --fill$/ },\n // Git log with line limit for mobile-friendly output\n { pattern: /^git log --oneline -\\d{1,2}$/ },\n\n // WhatsApp/Mobile quick commands\n { pattern: /^status$/i },\n { pattern: /^tasks$/i },\n { pattern: /^context$/i },\n { pattern: /^help$/i },\n { pattern: /^sync$/i },\n\n // Claude Code launcher\n { pattern: /^claude-sm$/ },\n\n // Log viewing (safe read-only)\n { pattern: /^tail -\\d+ ~\\/\\.claude\\/logs\\/\\*\\.log$/ },\n\n // Custom aliases (cwm = claude worktree merge)\n { pattern: /^cwm$/ },\n\n // Simple echo/confirmation (no variables)\n {\n pattern:\n /^echo \"?(Done|OK|Confirmed|Acknowledged|Great work! Time for a coffee break\\.)\"?$/,\n },\n];\n\n/**\n * Check if an action is in the allowlist\n */\nfunction isActionAllowed(action: string): boolean {\n const trimmed = action.trim();\n return SAFE_ACTION_PATTERNS.some(({ pattern, validate }) => {\n const match = trimmed.match(pattern);\n if (!match) return false;\n if (validate && !validate(match)) return false;\n return true;\n });\n}\n\nexport interface PendingAction {\n id: string;\n promptId: string;\n response: string;\n action: string;\n timestamp: string;\n status: 'pending' | 'running' | 'completed' | 'failed';\n result?: string;\n error?: string;\n}\n\nexport interface ActionQueue {\n actions: PendingAction[];\n lastChecked: string;\n}\n\nconst QUEUE_PATH = join(homedir(), '.stackmemory', 'sms-action-queue.json');\n\nconst DEFAULT_QUEUE: ActionQueue = {\n actions: [],\n lastChecked: new Date().toISOString(),\n};\n\nexport function loadActionQueue(): ActionQueue {\n try {\n if (existsSync(QUEUE_PATH)) {\n const data = JSON.parse(readFileSync(QUEUE_PATH, 'utf8'));\n return parseConfigSafe(\n ActionQueueSchema,\n data,\n DEFAULT_QUEUE,\n 'action-queue'\n );\n }\n } catch {\n // Use defaults\n }\n return { ...DEFAULT_QUEUE, lastChecked: new Date().toISOString() };\n}\n\nexport function saveActionQueue(queue: ActionQueue): void {\n try {\n ensureSecureDir(join(homedir(), '.stackmemory'));\n writeFileSecure(QUEUE_PATH, JSON.stringify(queue, null, 2));\n } catch {\n // Silently fail\n }\n}\n\nexport function queueAction(\n promptId: string,\n response: string,\n action: string\n): string {\n const queue = loadActionQueue();\n // Use cryptographically secure random ID\n const id = randomBytes(8).toString('hex');\n\n queue.actions.push({\n id,\n promptId,\n response,\n action,\n timestamp: new Date().toISOString(),\n status: 'pending',\n });\n\n saveActionQueue(queue);\n return id;\n}\n\n/**\n * Get Linear client if available\n * Returns null if credentials are missing or invalid\n */\nfunction getLinearClient(): LinearClient | null {\n // Try API key first - must be valid format (lin_api_*)\n const apiKey = process.env['LINEAR_API_KEY'];\n if (apiKey && apiKey.startsWith('lin_api_')) {\n return new LinearClient({ apiKey });\n }\n\n try {\n const authManager = new LinearAuthManager();\n const tokens = authManager.loadTokens();\n if (tokens?.accessToken) {\n return new LinearClient({ accessToken: tokens.accessToken });\n }\n } catch {\n // Auth not available\n }\n\n return null;\n}\n\n/**\n * Handle special actions that require API calls instead of shell commands\n */\nasync function handleSpecialAction(action: string): Promise<{\n handled: boolean;\n success?: boolean;\n output?: string;\n error?: string;\n}> {\n // Handle stackmemory task start command\n const taskStartMatch = action.match(\n /^stackmemory task start ([a-f0-9-]{36})( --assign-me)?$/\n );\n if (taskStartMatch) {\n const issueId = taskStartMatch[1];\n const client = getLinearClient();\n\n if (!client) {\n return {\n handled: true,\n success: false,\n error:\n 'Linear not configured. Set LINEAR_API_KEY or run stackmemory linear setup.',\n };\n }\n\n try {\n const result = await client.startIssue(issueId);\n if (result.success && result.issue) {\n return {\n handled: true,\n success: true,\n output: `Started: ${result.issue.identifier} - ${result.issue.title}`,\n };\n }\n return {\n handled: true,\n success: false,\n error: result.error || 'Failed to start issue',\n };\n } catch (err) {\n return {\n handled: true,\n success: false,\n error: err instanceof Error ? err.message : 'Unknown error',\n };\n }\n }\n\n return { handled: false };\n}\n\n/**\n * Execute an action safely using allowlist validation\n * This prevents command injection by only allowing pre-approved commands\n */\nexport async function executeActionSafe(\n action: string,\n _response: string\n): Promise<{ success: boolean; output?: string; error?: string }> {\n // Check if action is in allowlist\n if (!isActionAllowed(action)) {\n console.error(`[sms-action] Action not in allowlist: ${action}`);\n return {\n success: false,\n error: `Action not allowed. Only pre-approved commands can be executed via SMS.`,\n };\n }\n\n // Check for special actions that need API calls\n const specialResult = await handleSpecialAction(action);\n if (specialResult.handled) {\n return {\n success: specialResult.success || false,\n output: specialResult.output,\n error: specialResult.error,\n };\n }\n\n try {\n console.log(`[sms-action] Executing safe action: ${action}`);\n\n // Parse the action into command and args, respecting quotes\n const parts = parseCommandArgs(action);\n if (parts.length === 0) {\n return { success: false, error: 'Empty command' };\n }\n\n const cmd = parts[0];\n const args = parts.slice(1);\n\n // Use execFileSync for commands without shell interpretation\n // This prevents shell injection even if the allowlist is somehow bypassed\n const output = execFileSync(cmd, args, {\n encoding: 'utf8',\n timeout: 60000,\n stdio: ['pipe', 'pipe', 'pipe'],\n shell: false, // Explicitly disable shell\n });\n\n return { success: true, output };\n } catch (err) {\n const error = err instanceof Error ? err.message : String(err);\n return { success: false, error };\n }\n}\n\nexport function getPendingActions(): PendingAction[] {\n const queue = loadActionQueue();\n return queue.actions.filter((a) => a.status === 'pending');\n}\n\nexport function markActionRunning(id: string): void {\n const queue = loadActionQueue();\n const action = queue.actions.find((a) => a.id === id);\n if (action) {\n action.status = 'running';\n saveActionQueue(queue);\n }\n}\n\nexport function markActionCompleted(\n id: string,\n result?: string,\n error?: string\n): void {\n const queue = loadActionQueue();\n const action = queue.actions.find((a) => a.id === id);\n if (action) {\n action.status = error ? 'failed' : 'completed';\n action.result = result;\n action.error = error;\n saveActionQueue(queue);\n }\n}\n\nexport async function executeAction(action: PendingAction): Promise<{\n success: boolean;\n output?: string;\n error?: string;\n}> {\n markActionRunning(action.id);\n\n // Use the safe execution path to prevent command injection\n const result = await executeActionSafe(action.action, action.response);\n\n if (result.success) {\n markActionCompleted(action.id, result.output);\n } else {\n markActionCompleted(action.id, undefined, result.error);\n }\n\n return result;\n}\n\nexport async function processAllPendingActions(): Promise<{\n processed: number;\n succeeded: number;\n failed: number;\n}> {\n const pending = getPendingActions();\n let succeeded = 0;\n let failed = 0;\n\n for (const action of pending) {\n const result = await executeAction(action);\n if (result.success) {\n succeeded++;\n } else {\n failed++;\n }\n }\n\n return { processed: pending.length, succeeded, failed };\n}\n\n// Clean up old completed actions (keep last 50)\nexport function cleanupOldActions(): number {\n const queue = loadActionQueue();\n const completed = queue.actions.filter(\n (a) => a.status === 'completed' || a.status === 'failed'\n );\n\n if (completed.length > 50) {\n const toRemove = completed.slice(0, completed.length - 50);\n queue.actions = queue.actions.filter(\n (a) => !toRemove.find((r) => r.id === a.id)\n );\n saveActionQueue(queue);\n return toRemove.length;\n }\n\n return 0;\n}\n\n/**\n * Action Templates - Common actions for SMS responses\n *\n * SECURITY NOTE: These templates return command strings that must be\n * validated against SAFE_ACTION_PATTERNS before execution.\n * Templates that accept user input are removed to prevent injection.\n */\nexport const ACTION_TEMPLATES = {\n // Git/PR actions (PR numbers must be validated as integers)\n approvePR: (prNumber: string) => {\n // Validate PR number is numeric only\n if (!/^\\d+$/.test(prNumber)) {\n throw new Error('Invalid PR number');\n }\n return `gh pr review ${prNumber} --approve`;\n },\n mergePR: (prNumber: string) => {\n if (!/^\\d+$/.test(prNumber)) {\n throw new Error('Invalid PR number');\n }\n return `gh pr merge ${prNumber} --squash`;\n },\n viewPR: (prNumber: string) => {\n if (!/^\\d+$/.test(prNumber)) {\n throw new Error('Invalid PR number');\n }\n return `gh pr view ${prNumber}`;\n },\n\n // Build actions (no user input)\n rebuild: () => `npm run build`,\n retest: () => `npm run test:run`,\n lint: () => `npm run lint:fix`,\n\n // Status actions (no user input)\n status: () => `stackmemory status`,\n checkNotifications: () => `stackmemory notify check`,\n\n // REMOVED for security - these templates allowed arbitrary user input:\n // - requestChanges (allowed arbitrary message)\n // - closePR (could be used maliciously)\n // - deploy/rollback (too dangerous for SMS)\n // - verifyDeployment (allowed arbitrary URL)\n // - notifySlack (allowed arbitrary message - command injection)\n // - notifyTeam (allowed arbitrary message - command injection)\n};\n\n/**\n * Create action string from template\n */\nexport function createAction(\n template: keyof typeof ACTION_TEMPLATES,\n ...args: string[]\n): string {\n const fn = ACTION_TEMPLATES[template];\n if (typeof fn === 'function') {\n return (fn as (...args: string[]) => string)(...args);\n }\n return fn;\n}\n\n/**\n * Watch for new actions and execute them\n */\nexport function startActionWatcher(intervalMs: number = 5000): NodeJS.Timeout {\n console.log(\n `[sms-action] Starting action watcher (interval: ${intervalMs}ms)`\n );\n\n return setInterval(() => {\n const pending = getPendingActions();\n if (pending.length > 0) {\n console.log(`[sms-action] Found ${pending.length} pending action(s)`);\n processAllPendingActions();\n }\n }, intervalMs);\n}\n\n/**\n * Integration with SMS webhook - queue action when response received\n */\nexport function handleSMSResponse(\n promptId: string,\n response: string,\n action?: string\n): void {\n if (action) {\n const actionId = queueAction(promptId, response, action);\n console.log(`[sms-action] Queued action ${actionId}: ${action}`);\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }