lattice-orchestrator 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +58 -0
- package/config/logrotate.conf +15 -0
- package/dist/cli-parser.d.ts +11 -0
- package/dist/cli-parser.d.ts.map +1 -0
- package/dist/cli-parser.js +48 -0
- package/dist/cli-parser.js.map +1 -0
- package/dist/lattice-server.d.ts +70 -0
- package/dist/lattice-server.d.ts.map +1 -0
- package/dist/lattice-server.js +969 -0
- package/dist/lattice-server.js.map +1 -0
- package/dist/mcp-server/index.d.ts +3 -0
- package/dist/mcp-server/index.d.ts.map +1 -0
- package/dist/mcp-server/index.js +190 -0
- package/dist/mcp-server/index.js.map +1 -0
- package/dist/mcp-server/lattice-tools.d.ts +15 -0
- package/dist/mcp-server/lattice-tools.d.ts.map +1 -0
- package/dist/mcp-server/lattice-tools.js +366 -0
- package/dist/mcp-server/lattice-tools.js.map +1 -0
- package/dist/middleware/cors-setup.d.ts +7 -0
- package/dist/middleware/cors-setup.d.ts.map +1 -0
- package/dist/middleware/cors-setup.js +8 -0
- package/dist/middleware/cors-setup.js.map +1 -0
- package/dist/middleware/error-handler.d.ts +4 -0
- package/dist/middleware/error-handler.d.ts.map +1 -0
- package/dist/middleware/error-handler.js +27 -0
- package/dist/middleware/error-handler.js.map +1 -0
- package/dist/middleware/query-parser.d.ts +11 -0
- package/dist/middleware/query-parser.d.ts.map +1 -0
- package/dist/middleware/query-parser.js +68 -0
- package/dist/middleware/query-parser.js.map +1 -0
- package/dist/middleware/request-logger.d.ts +4 -0
- package/dist/middleware/request-logger.d.ts.map +1 -0
- package/dist/middleware/request-logger.js +6 -0
- package/dist/middleware/request-logger.js.map +1 -0
- package/dist/process-daemon/index.d.ts +14 -0
- package/dist/process-daemon/index.d.ts.map +1 -0
- package/dist/process-daemon/index.js +51 -0
- package/dist/process-daemon/index.js.map +1 -0
- package/dist/process-daemon/process-daemon.d.ts +101 -0
- package/dist/process-daemon/process-daemon.d.ts.map +1 -0
- package/dist/process-daemon/process-daemon.js +846 -0
- package/dist/process-daemon/process-daemon.js.map +1 -0
- package/dist/process-daemon/process-manager-client.d.ts +123 -0
- package/dist/process-daemon/process-manager-client.d.ts.map +1 -0
- package/dist/process-daemon/process-manager-client.js +329 -0
- package/dist/process-daemon/process-manager-client.js.map +1 -0
- package/dist/process-daemon/process-manager-interface.d.ts +61 -0
- package/dist/process-daemon/process-manager-interface.d.ts.map +1 -0
- package/dist/process-daemon/process-manager-interface.js +8 -0
- package/dist/process-daemon/process-manager-interface.js.map +1 -0
- package/dist/process-daemon/test-daemon.d.ts +12 -0
- package/dist/process-daemon/test-daemon.d.ts.map +1 -0
- package/dist/process-daemon/test-daemon.js +81 -0
- package/dist/process-daemon/test-daemon.js.map +1 -0
- package/dist/process-daemon/types.d.ts +97 -0
- package/dist/process-daemon/types.d.ts.map +1 -0
- package/dist/process-daemon/types.js +8 -0
- package/dist/process-daemon/types.js.map +1 -0
- package/dist/routes/analysis.routes.d.ts +13 -0
- package/dist/routes/analysis.routes.d.ts.map +1 -0
- package/dist/routes/analysis.routes.js +520 -0
- package/dist/routes/analysis.routes.js.map +1 -0
- package/dist/routes/config.routes.d.ts +4 -0
- package/dist/routes/config.routes.d.ts.map +1 -0
- package/dist/routes/config.routes.js +27 -0
- package/dist/routes/config.routes.js.map +1 -0
- package/dist/routes/conversation.routes.d.ts +43 -0
- package/dist/routes/conversation.routes.d.ts.map +1 -0
- package/dist/routes/conversation.routes.js +79 -0
- package/dist/routes/conversation.routes.js.map +1 -0
- package/dist/routes/filesystem.routes.d.ts +4 -0
- package/dist/routes/filesystem.routes.d.ts.map +1 -0
- package/dist/routes/filesystem.routes.js +86 -0
- package/dist/routes/filesystem.routes.js.map +1 -0
- package/dist/routes/insights.routes.d.ts +17 -0
- package/dist/routes/insights.routes.d.ts.map +1 -0
- package/dist/routes/insights.routes.js +633 -0
- package/dist/routes/insights.routes.js.map +1 -0
- package/dist/routes/lattice.routes.d.ts +10 -0
- package/dist/routes/lattice.routes.d.ts.map +1 -0
- package/dist/routes/lattice.routes.js +123 -0
- package/dist/routes/lattice.routes.js.map +1 -0
- package/dist/routes/license.routes.d.ts +3 -0
- package/dist/routes/license.routes.d.ts.map +1 -0
- package/dist/routes/license.routes.js +95 -0
- package/dist/routes/license.routes.js.map +1 -0
- package/dist/routes/log.routes.d.ts +3 -0
- package/dist/routes/log.routes.d.ts.map +1 -0
- package/dist/routes/log.routes.js +184 -0
- package/dist/routes/log.routes.js.map +1 -0
- package/dist/routes/pending-question.routes.d.ts +9 -0
- package/dist/routes/pending-question.routes.d.ts.map +1 -0
- package/dist/routes/pending-question.routes.js +162 -0
- package/dist/routes/pending-question.routes.js.map +1 -0
- package/dist/routes/permission.routes.d.ts +18 -0
- package/dist/routes/permission.routes.d.ts.map +1 -0
- package/dist/routes/permission.routes.js +370 -0
- package/dist/routes/permission.routes.js.map +1 -0
- package/dist/routes/process-events.routes.d.ts +9 -0
- package/dist/routes/process-events.routes.d.ts.map +1 -0
- package/dist/routes/process-events.routes.js +141 -0
- package/dist/routes/process-events.routes.js.map +1 -0
- package/dist/routes/prototype.routes.d.ts +9 -0
- package/dist/routes/prototype.routes.d.ts.map +1 -0
- package/dist/routes/prototype.routes.js +757 -0
- package/dist/routes/prototype.routes.js.map +1 -0
- package/dist/routes/question.routes.d.ts +8 -0
- package/dist/routes/question.routes.d.ts.map +1 -0
- package/dist/routes/question.routes.js +83 -0
- package/dist/routes/question.routes.js.map +1 -0
- package/dist/routes/session-control.routes.d.ts +29 -0
- package/dist/routes/session-control.routes.d.ts.map +1 -0
- package/dist/routes/session-control.routes.js +455 -0
- package/dist/routes/session-control.routes.js.map +1 -0
- package/dist/routes/session-lifecycle.routes.d.ts +21 -0
- package/dist/routes/session-lifecycle.routes.d.ts.map +1 -0
- package/dist/routes/session-lifecycle.routes.js +256 -0
- package/dist/routes/session-lifecycle.routes.js.map +1 -0
- package/dist/routes/session-query.routes.d.ts +25 -0
- package/dist/routes/session-query.routes.d.ts.map +1 -0
- package/dist/routes/session-query.routes.js +363 -0
- package/dist/routes/session-query.routes.js.map +1 -0
- package/dist/routes/session-stream.routes.d.ts +21 -0
- package/dist/routes/session-stream.routes.d.ts.map +1 -0
- package/dist/routes/session-stream.routes.js +235 -0
- package/dist/routes/session-stream.routes.js.map +1 -0
- package/dist/routes/streaming.routes.d.ts +4 -0
- package/dist/routes/streaming.routes.d.ts.map +1 -0
- package/dist/routes/streaming.routes.js +33 -0
- package/dist/routes/streaming.routes.js.map +1 -0
- package/dist/routes/system.routes.d.ts +7 -0
- package/dist/routes/system.routes.d.ts.map +1 -0
- package/dist/routes/system.routes.js +214 -0
- package/dist/routes/system.routes.js.map +1 -0
- package/dist/routes/walkthrough.routes.d.ts +19 -0
- package/dist/routes/walkthrough.routes.d.ts.map +1 -0
- package/dist/routes/walkthrough.routes.js +688 -0
- package/dist/routes/walkthrough.routes.js.map +1 -0
- package/dist/routes/working-directories.routes.d.ts +4 -0
- package/dist/routes/working-directories.routes.d.ts.map +1 -0
- package/dist/routes/working-directories.routes.js +25 -0
- package/dist/routes/working-directories.routes.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +34 -0
- package/dist/server.js.map +1 -0
- package/dist/services/ToolMetricsService.d.ts +53 -0
- package/dist/services/ToolMetricsService.d.ts.map +1 -0
- package/dist/services/ToolMetricsService.js +230 -0
- package/dist/services/ToolMetricsService.js.map +1 -0
- package/dist/services/claude-router-service.d.ts +19 -0
- package/dist/services/claude-router-service.d.ts.map +1 -0
- package/dist/services/claude-router-service.js +160 -0
- package/dist/services/claude-router-service.js.map +1 -0
- package/dist/services/commands-service.d.ts +20 -0
- package/dist/services/commands-service.d.ts.map +1 -0
- package/dist/services/commands-service.js +115 -0
- package/dist/services/commands-service.js.map +1 -0
- package/dist/services/connection-debug-logger.d.ts +85 -0
- package/dist/services/connection-debug-logger.d.ts.map +1 -0
- package/dist/services/connection-debug-logger.js +221 -0
- package/dist/services/connection-debug-logger.js.map +1 -0
- package/dist/services/debug-log.d.ts +6 -0
- package/dist/services/debug-log.d.ts.map +1 -0
- package/dist/services/debug-log.js +27 -0
- package/dist/services/debug-log.js.map +1 -0
- package/dist/services/gemini-service.d.ts +35 -0
- package/dist/services/gemini-service.d.ts.map +1 -0
- package/dist/services/gemini-service.js +256 -0
- package/dist/services/gemini-service.js.map +1 -0
- package/dist/services/infrastructure/config-service.d.ts +79 -0
- package/dist/services/infrastructure/config-service.d.ts.map +1 -0
- package/dist/services/infrastructure/config-service.js +431 -0
- package/dist/services/infrastructure/config-service.js.map +1 -0
- package/dist/services/infrastructure/cost-tracker.d.ts +112 -0
- package/dist/services/infrastructure/cost-tracker.d.ts.map +1 -0
- package/dist/services/infrastructure/cost-tracker.js +423 -0
- package/dist/services/infrastructure/cost-tracker.js.map +1 -0
- package/dist/services/infrastructure/file-system-service.d.ts +61 -0
- package/dist/services/infrastructure/file-system-service.d.ts.map +1 -0
- package/dist/services/infrastructure/file-system-service.js +348 -0
- package/dist/services/infrastructure/file-system-service.js.map +1 -0
- package/dist/services/infrastructure/log-formatter.d.ts +5 -0
- package/dist/services/infrastructure/log-formatter.d.ts.map +1 -0
- package/dist/services/infrastructure/log-formatter.js +77 -0
- package/dist/services/infrastructure/log-formatter.js.map +1 -0
- package/dist/services/infrastructure/log-stream-buffer.d.ts +11 -0
- package/dist/services/infrastructure/log-stream-buffer.d.ts.map +1 -0
- package/dist/services/infrastructure/log-stream-buffer.js +36 -0
- package/dist/services/infrastructure/log-stream-buffer.js.map +1 -0
- package/dist/services/infrastructure/logger.d.ts +71 -0
- package/dist/services/infrastructure/logger.d.ts.map +1 -0
- package/dist/services/infrastructure/logger.js +215 -0
- package/dist/services/infrastructure/logger.js.map +1 -0
- package/dist/services/infrastructure/service-registry.d.ts +86 -0
- package/dist/services/infrastructure/service-registry.d.ts.map +1 -0
- package/dist/services/infrastructure/service-registry.js +162 -0
- package/dist/services/infrastructure/service-registry.js.map +1 -0
- package/dist/services/infrastructure/stream-manager.d.ts +87 -0
- package/dist/services/infrastructure/stream-manager.d.ts.map +1 -0
- package/dist/services/infrastructure/stream-manager.js +436 -0
- package/dist/services/infrastructure/stream-manager.js.map +1 -0
- package/dist/services/infrastructure/timing.d.ts +72 -0
- package/dist/services/infrastructure/timing.d.ts.map +1 -0
- package/dist/services/infrastructure/timing.js +115 -0
- package/dist/services/infrastructure/timing.js.map +1 -0
- package/dist/services/insights/anthropic-service.d.ts +224 -0
- package/dist/services/insights/anthropic-service.d.ts.map +1 -0
- package/dist/services/insights/anthropic-service.js +1062 -0
- package/dist/services/insights/anthropic-service.js.map +1 -0
- package/dist/services/insights/insight-audit-repository.d.ts +119 -0
- package/dist/services/insights/insight-audit-repository.d.ts.map +1 -0
- package/dist/services/insights/insight-audit-repository.js +242 -0
- package/dist/services/insights/insight-audit-repository.js.map +1 -0
- package/dist/services/insights/insight-queue.d.ts +99 -0
- package/dist/services/insights/insight-queue.d.ts.map +1 -0
- package/dist/services/insights/insight-queue.js +277 -0
- package/dist/services/insights/insight-queue.js.map +1 -0
- package/dist/services/insights/insights-computer.d.ts +132 -0
- package/dist/services/insights/insights-computer.d.ts.map +1 -0
- package/dist/services/insights/insights-computer.js +936 -0
- package/dist/services/insights/insights-computer.js.map +1 -0
- package/dist/services/insights/insights-coordinator.d.ts +165 -0
- package/dist/services/insights/insights-coordinator.d.ts.map +1 -0
- package/dist/services/insights/insights-coordinator.js +1678 -0
- package/dist/services/insights/insights-coordinator.js.map +1 -0
- package/dist/services/insights/insights-event-log.d.ts +196 -0
- package/dist/services/insights/insights-event-log.d.ts.map +1 -0
- package/dist/services/insights/insights-event-log.js +319 -0
- package/dist/services/insights/insights-event-log.js.map +1 -0
- package/dist/services/lattice-service.d.ts +77 -0
- package/dist/services/lattice-service.d.ts.map +1 -0
- package/dist/services/lattice-service.js +195 -0
- package/dist/services/lattice-service.js.map +1 -0
- package/dist/services/license-service.d.ts +69 -0
- package/dist/services/license-service.d.ts.map +1 -0
- package/dist/services/license-service.js +330 -0
- package/dist/services/license-service.js.map +1 -0
- package/dist/services/mcp-config-generator.d.ts +32 -0
- package/dist/services/mcp-config-generator.d.ts.map +1 -0
- package/dist/services/mcp-config-generator.js +126 -0
- package/dist/services/mcp-config-generator.js.map +1 -0
- package/dist/services/message-filter.d.ts +22 -0
- package/dist/services/message-filter.d.ts.map +1 -0
- package/dist/services/message-filter.js +57 -0
- package/dist/services/message-filter.js.map +1 -0
- package/dist/services/notification-service.d.ts +45 -0
- package/dist/services/notification-service.d.ts.map +1 -0
- package/dist/services/notification-service.js +184 -0
- package/dist/services/notification-service.js.map +1 -0
- package/dist/services/pending-question-service.d.ts +97 -0
- package/dist/services/pending-question-service.d.ts.map +1 -0
- package/dist/services/pending-question-service.js +223 -0
- package/dist/services/pending-question-service.js.map +1 -0
- package/dist/services/permission-event-log.d.ts +136 -0
- package/dist/services/permission-event-log.d.ts.map +1 -0
- package/dist/services/permission-event-log.js +252 -0
- package/dist/services/permission-event-log.js.map +1 -0
- package/dist/services/permission-pattern-matcher.d.ts +82 -0
- package/dist/services/permission-pattern-matcher.d.ts.map +1 -0
- package/dist/services/permission-pattern-matcher.js +294 -0
- package/dist/services/permission-pattern-matcher.js.map +1 -0
- package/dist/services/permission-tracker.d.ts +67 -0
- package/dist/services/permission-tracker.d.ts.map +1 -0
- package/dist/services/permission-tracker.js +162 -0
- package/dist/services/permission-tracker.js.map +1 -0
- package/dist/services/process/claude-process-manager.d.ts +142 -0
- package/dist/services/process/claude-process-manager.d.ts.map +1 -0
- package/dist/services/process/claude-process-manager.js +1218 -0
- package/dist/services/process/claude-process-manager.js.map +1 -0
- package/dist/services/process/conversation-status-manager.d.ts +110 -0
- package/dist/services/process/conversation-status-manager.d.ts.map +1 -0
- package/dist/services/process/conversation-status-manager.js +349 -0
- package/dist/services/process/conversation-status-manager.js.map +1 -0
- package/dist/services/process/json-lines-parser.d.ts +19 -0
- package/dist/services/process/json-lines-parser.d.ts.map +1 -0
- package/dist/services/process/json-lines-parser.js +59 -0
- package/dist/services/process/json-lines-parser.js.map +1 -0
- package/dist/services/process/process-event-log.d.ts +263 -0
- package/dist/services/process/process-event-log.d.ts.map +1 -0
- package/dist/services/process/process-event-log.js +509 -0
- package/dist/services/process/process-event-log.js.map +1 -0
- package/dist/services/process/process-manager-factory.d.ts +109 -0
- package/dist/services/process/process-manager-factory.d.ts.map +1 -0
- package/dist/services/process/process-manager-factory.js +338 -0
- package/dist/services/process/process-manager-factory.js.map +1 -0
- package/dist/services/question-tracker.d.ts +51 -0
- package/dist/services/question-tracker.d.ts.map +1 -0
- package/dist/services/question-tracker.js +111 -0
- package/dist/services/question-tracker.js.map +1 -0
- package/dist/services/sessions/claude-history-reader.d.ts +139 -0
- package/dist/services/sessions/claude-history-reader.d.ts.map +1 -0
- package/dist/services/sessions/claude-history-reader.js +864 -0
- package/dist/services/sessions/claude-history-reader.js.map +1 -0
- package/dist/services/sessions/conversation-cache.d.ts +98 -0
- package/dist/services/sessions/conversation-cache.d.ts.map +1 -0
- package/dist/services/sessions/conversation-cache.js +329 -0
- package/dist/services/sessions/conversation-cache.js.map +1 -0
- package/dist/services/sessions/session-activity-watcher.d.ts +67 -0
- package/dist/services/sessions/session-activity-watcher.d.ts.map +1 -0
- package/dist/services/sessions/session-activity-watcher.js +236 -0
- package/dist/services/sessions/session-activity-watcher.js.map +1 -0
- package/dist/services/sessions/session-analysis-service.d.ts +72 -0
- package/dist/services/sessions/session-analysis-service.d.ts.map +1 -0
- package/dist/services/sessions/session-analysis-service.js +373 -0
- package/dist/services/sessions/session-analysis-service.js.map +1 -0
- package/dist/services/sessions/session-branch-service.d.ts +76 -0
- package/dist/services/sessions/session-branch-service.d.ts.map +1 -0
- package/dist/services/sessions/session-branch-service.js +355 -0
- package/dist/services/sessions/session-branch-service.js.map +1 -0
- package/dist/services/sessions/session-info-service.d.ts +455 -0
- package/dist/services/sessions/session-info-service.d.ts.map +1 -0
- package/dist/services/sessions/session-info-service.js +1640 -0
- package/dist/services/sessions/session-info-service.js.map +1 -0
- package/dist/services/sessions/session-marks-repository.d.ts +78 -0
- package/dist/services/sessions/session-marks-repository.d.ts.map +1 -0
- package/dist/services/sessions/session-marks-repository.js +263 -0
- package/dist/services/sessions/session-marks-repository.js.map +1 -0
- package/dist/services/sessions/session-marks-service.d.ts +137 -0
- package/dist/services/sessions/session-marks-service.d.ts.map +1 -0
- package/dist/services/sessions/session-marks-service.js +562 -0
- package/dist/services/sessions/session-marks-service.js.map +1 -0
- package/dist/services/sessions/session-review-service.d.ts +98 -0
- package/dist/services/sessions/session-review-service.d.ts.map +1 -0
- package/dist/services/sessions/session-review-service.js +629 -0
- package/dist/services/sessions/session-review-service.js.map +1 -0
- package/dist/services/sessions/turn-capture-service.d.ts +83 -0
- package/dist/services/sessions/turn-capture-service.d.ts.map +1 -0
- package/dist/services/sessions/turn-capture-service.js +477 -0
- package/dist/services/sessions/turn-capture-service.js.map +1 -0
- package/dist/services/sessions/turn-repository.d.ts +48 -0
- package/dist/services/sessions/turn-repository.d.ts.map +1 -0
- package/dist/services/sessions/turn-repository.js +103 -0
- package/dist/services/sessions/turn-repository.js.map +1 -0
- package/dist/services/walkthrough-service.d.ts +226 -0
- package/dist/services/walkthrough-service.d.ts.map +1 -0
- package/dist/services/walkthrough-service.js +1112 -0
- package/dist/services/walkthrough-service.js.map +1 -0
- package/dist/services/walkthrough-skill-prompt.d.ts +34 -0
- package/dist/services/walkthrough-skill-prompt.d.ts.map +1 -0
- package/dist/services/walkthrough-skill-prompt.js +313 -0
- package/dist/services/walkthrough-skill-prompt.js.map +1 -0
- package/dist/services/web-push-service.d.ts +48 -0
- package/dist/services/web-push-service.d.ts.map +1 -0
- package/dist/services/web-push-service.js +186 -0
- package/dist/services/web-push-service.js.map +1 -0
- package/dist/services/working-directories-service.d.ts +19 -0
- package/dist/services/working-directories-service.d.ts.map +1 -0
- package/dist/services/working-directories-service.js +103 -0
- package/dist/services/working-directories-service.js.map +1 -0
- package/dist/types/config.d.ts +122 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +21 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/express.d.ts +5 -0
- package/dist/types/express.d.ts.map +1 -0
- package/dist/types/express.js +2 -0
- package/dist/types/express.js.map +1 -0
- package/dist/types/index.d.ts +400 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +41 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/insights.d.ts +176 -0
- package/dist/types/insights.d.ts.map +1 -0
- package/dist/types/insights.js +23 -0
- package/dist/types/insights.js.map +1 -0
- package/dist/types/license.d.ts +70 -0
- package/dist/types/license.d.ts.map +1 -0
- package/dist/types/license.js +5 -0
- package/dist/types/license.js.map +1 -0
- package/dist/types/router-config.d.ts +13 -0
- package/dist/types/router-config.d.ts.map +1 -0
- package/dist/types/router-config.js +2 -0
- package/dist/types/router-config.js.map +1 -0
- package/dist/utils/constants.d.ts +26 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +28 -0
- package/dist/utils/constants.js.map +1 -0
- package/dist/utils/machine-id.d.ts +7 -0
- package/dist/utils/machine-id.d.ts.map +1 -0
- package/dist/utils/machine-id.js +76 -0
- package/dist/utils/machine-id.js.map +1 -0
- package/dist/utils/server-startup.d.ts +11 -0
- package/dist/utils/server-startup.d.ts.map +1 -0
- package/dist/utils/server-startup.js +9 -0
- package/dist/utils/server-startup.js.map +1 -0
- package/dist/utils/update-check.d.ts +13 -0
- package/dist/utils/update-check.d.ts.map +1 -0
- package/dist/utils/update-check.js +90 -0
- package/dist/utils/update-check.js.map +1 -0
- package/dist/web/assets/ArchivedCardPrototype-S9ifiasa.js +5 -0
- package/dist/web/assets/BannerGallery-B__rJV6F.js +1 -0
- package/dist/web/assets/BannerPrototype-DBKP9Uiu.js +52 -0
- package/dist/web/assets/CodeHikeExperiment-B8jjWAFy.js +15 -0
- package/dist/web/assets/ContextTooltipVariations-DzklAFam.js +1 -0
- package/dist/web/assets/FontShowcasePrototype-KIMEWeP2.js +13 -0
- package/dist/web/assets/GeometricGallery-DddlWhHK.js +1 -0
- package/dist/web/assets/HistoryWalkthroughPrototype-DeniRRdw.js +18 -0
- package/dist/web/assets/InlineWalkthroughPrototype-Csd5r_Hk.js +1 -0
- package/dist/web/assets/MarkButtonPrototype-CxhxE0RP.js +1 -0
- package/dist/web/assets/MenuStylesPrototype-D7neA6YM.js +1 -0
- package/dist/web/assets/MomentCardVariations-2GT7GyFn.js +1 -0
- package/dist/web/assets/MomentHeaderVariations-DhGEw4XC.js +1 -0
- package/dist/web/assets/NarrativeWalkthroughDemo-B5C566fu.js +389 -0
- package/dist/web/assets/OutcomeVariations-BrZfsVcs.js +1 -0
- package/dist/web/assets/PermissionPatternPickerPrototype-CBOhe2Me.js +1 -0
- package/dist/web/assets/PermissionPrototype-BcF-a5an.js +1 -0
- package/dist/web/assets/PipelineGallery-BJhyM0rx.js +1 -0
- package/dist/web/assets/ScopeHeaderPrototype-GD1HNfaO.js +1 -0
- package/dist/web/assets/ScopeHeaderStylesPrototype-aa4uNJJ1.js +1 -0
- package/dist/web/assets/ScrollycodingPrototype-CKW1bf72.js +70 -0
- package/dist/web/assets/SectionHeaderVariations-DM8vUwfj.js +1 -0
- package/dist/web/assets/SemanticGallery-CtQEo7St.js +1 -0
- package/dist/web/assets/SessionCardPrototype-CgHCIMHe.js +1 -0
- package/dist/web/assets/SessionSidebarVariations-DMQL3Azj.js +3 -0
- package/dist/web/assets/SessionStartPrototype-Cwsv01Rr.js +1 -0
- package/dist/web/assets/SmartMenuPrototype-Br37Qbs_.js +1 -0
- package/dist/web/assets/StyleGallery-rZgrploB.js +1 -0
- package/dist/web/assets/TimelineCardPrototype-lzPc5mhe.js +19 -0
- package/dist/web/assets/ToolbarPrototype-Dm4BNZra.js +1 -0
- package/dist/web/assets/TooltipExperiment-Dy8QzTIP.js +13 -0
- package/dist/web/assets/WalkthroughCTAPrototype-uHoovujd.js +1 -0
- package/dist/web/assets/WalkthroughHeaderVariations-Do7Di1g1.js +1 -0
- package/dist/web/assets/WalkthroughShowcase-sGmRoPoM.js +112 -0
- package/dist/web/assets/arrow-right-D46Nx1mC.js +1 -0
- package/dist/web/assets/brain-BXIZKtOZ.js +1 -0
- package/dist/web/assets/grid-3x3-Cb81B62m.js +1 -0
- package/dist/web/assets/main-B1fyog77.js +321 -0
- package/dist/web/assets/main-C2PK2Klg.css +1 -0
- package/dist/web/assets/semantic-variations-Bd-W7ea2.js +1 -0
- package/dist/web/assets/target-Cf92wDTW.js +1 -0
- package/dist/web/favicon.png +0 -0
- package/dist/web/favicon.svg +22 -0
- package/dist/web/icon-192x192.png +0 -0
- package/dist/web/icon-512x512.png +0 -0
- package/dist/web/index.html +45 -0
- package/dist/web/manifest.json +61 -0
- package/package.json +192 -0
- package/scripts/postinstall.js +60 -0
|
@@ -0,0 +1,864 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import { LatticeError } from '../../types/index.js';
|
|
5
|
+
import { createLogger } from '../infrastructure/logger.js';
|
|
6
|
+
import { SessionInfoService } from '../sessions/session-info-service.js';
|
|
7
|
+
import { ConversationCache } from '../sessions/conversation-cache.js';
|
|
8
|
+
import { ToolMetricsService } from '../ToolMetricsService.js';
|
|
9
|
+
import { MessageFilter } from '../message-filter.js';
|
|
10
|
+
/**
|
|
11
|
+
* Reads conversation history from Claude's local storage
|
|
12
|
+
*/
|
|
13
|
+
export class ClaudeHistoryReader {
|
|
14
|
+
claudeHomePath;
|
|
15
|
+
logger;
|
|
16
|
+
sessionInfoService;
|
|
17
|
+
conversationCache;
|
|
18
|
+
toolMetricsService;
|
|
19
|
+
messageFilter;
|
|
20
|
+
constructor(sessionInfoService) {
|
|
21
|
+
this.claudeHomePath = path.join(os.homedir(), '.claude');
|
|
22
|
+
this.logger = createLogger('ClaudeHistoryReader');
|
|
23
|
+
this.sessionInfoService = sessionInfoService || SessionInfoService.getInstance();
|
|
24
|
+
this.conversationCache = new ConversationCache();
|
|
25
|
+
this.toolMetricsService = new ToolMetricsService();
|
|
26
|
+
this.messageFilter = new MessageFilter();
|
|
27
|
+
}
|
|
28
|
+
get homePath() {
|
|
29
|
+
return this.claudeHomePath;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Clear the conversation cache to force a refresh on next read
|
|
33
|
+
*/
|
|
34
|
+
clearCache() {
|
|
35
|
+
this.conversationCache.clear();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* List all conversations with optional filtering
|
|
39
|
+
*/
|
|
40
|
+
async listConversations(filter) {
|
|
41
|
+
const timings = {};
|
|
42
|
+
const startTime = Date.now();
|
|
43
|
+
try {
|
|
44
|
+
// FAST PATH: For archived=true queries, use DB-only path (avoids parsing JSONL files)
|
|
45
|
+
// This is critical for performance - archived sessions can number in the thousands
|
|
46
|
+
if (filter?.archived === true && filter?.pinned === undefined) {
|
|
47
|
+
return this.listArchivedConversationsFast(filter, timings, startTime);
|
|
48
|
+
}
|
|
49
|
+
// SLOW PATH: Parse JSONL files for non-archived or mixed queries
|
|
50
|
+
// Fetch ALL session info first - needed to filter before expensive parsing
|
|
51
|
+
const sessionInfoStart = Date.now();
|
|
52
|
+
const allSessionInfo = await this.sessionInfoService.getAllSessionInfo();
|
|
53
|
+
timings.getAllSessionInfo = Date.now() - sessionInfoStart;
|
|
54
|
+
// Fetch ALL insights - needed for cached tool metrics (Phase 1 optimization)
|
|
55
|
+
const insightsStart = Date.now();
|
|
56
|
+
const allInsights = await this.sessionInfoService.getAllInsights();
|
|
57
|
+
timings.getAllInsights = Date.now() - insightsStart;
|
|
58
|
+
// Pre-filter session IDs based on archived/pinned filters to avoid parsing unwanted files
|
|
59
|
+
let allowedSessionIds = null;
|
|
60
|
+
if (filter?.archived !== undefined || filter?.pinned !== undefined) {
|
|
61
|
+
allowedSessionIds = new Set();
|
|
62
|
+
for (const [sessionId, info] of Object.entries(allSessionInfo)) {
|
|
63
|
+
// Apply archive filter
|
|
64
|
+
if (filter.archived !== undefined && info.archived !== filter.archived) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
// Apply pinned filter
|
|
68
|
+
if (filter.pinned !== undefined && info.pinned !== filter.pinned) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
allowedSessionIds.add(sessionId);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Parse conversations (optionally filtered by sessionId)
|
|
75
|
+
const parseStart = Date.now();
|
|
76
|
+
const conversationChains = await this.parseAllConversations(allowedSessionIds);
|
|
77
|
+
timings.parseAllConversations = Date.now() - parseStart;
|
|
78
|
+
// Track sessions that need metrics backfilled (fire-and-forget after response)
|
|
79
|
+
const metricsToBackfill = [];
|
|
80
|
+
// Convert to ConversationSummary format and enhance with custom names
|
|
81
|
+
const allConversations = conversationChains.map((chain) => {
|
|
82
|
+
// Use bulk-fetched session info (O(1) lookup instead of async query)
|
|
83
|
+
// Default to archived: true to match syncMissingSessions behavior -
|
|
84
|
+
// sessions discovered outside Claudia start archived
|
|
85
|
+
const sessionInfo = allSessionInfo[chain.sessionId] || {
|
|
86
|
+
custom_name: '',
|
|
87
|
+
created_at: new Date().toISOString(),
|
|
88
|
+
updated_at: new Date().toISOString(),
|
|
89
|
+
version: 4,
|
|
90
|
+
pinned: false,
|
|
91
|
+
archived: true,
|
|
92
|
+
continuation_session_id: '',
|
|
93
|
+
initial_commit_head: '',
|
|
94
|
+
permission_mode: 'default'
|
|
95
|
+
};
|
|
96
|
+
// Use cached tool metrics if available (Phase 1 optimization)
|
|
97
|
+
const cachedInsights = allInsights.get(chain.sessionId);
|
|
98
|
+
let toolMetrics;
|
|
99
|
+
if (cachedInsights?.metrics_updated_at) {
|
|
100
|
+
// Use cached metrics - fast path
|
|
101
|
+
toolMetrics = {
|
|
102
|
+
linesAdded: cachedInsights.lines_added || 0,
|
|
103
|
+
linesRemoved: cachedInsights.lines_removed || 0,
|
|
104
|
+
editCount: cachedInsights.edit_count || 0,
|
|
105
|
+
writeCount: cachedInsights.write_count || 0
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// Calculate metrics and queue for backfill - slow path (only on first access)
|
|
110
|
+
toolMetrics = this.toolMetricsService.calculateMetricsFromMessages(chain.messages);
|
|
111
|
+
metricsToBackfill.push({ sessionId: chain.sessionId, metrics: toolMetrics, messageCount: chain.messages.length });
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
sessionId: chain.sessionId,
|
|
115
|
+
projectPath: chain.projectPath,
|
|
116
|
+
summary: chain.summary,
|
|
117
|
+
sessionInfo: sessionInfo,
|
|
118
|
+
createdAt: chain.createdAt,
|
|
119
|
+
updatedAt: chain.updatedAt,
|
|
120
|
+
messageCount: chain.messages.length,
|
|
121
|
+
totalDuration: chain.totalDuration,
|
|
122
|
+
model: chain.model,
|
|
123
|
+
status: 'completed', // Default status, will be updated by server
|
|
124
|
+
toolMetrics: toolMetrics
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
// Backfill metrics in background (don't block response)
|
|
128
|
+
if (metricsToBackfill.length > 0) {
|
|
129
|
+
this.logger.info('Backfilling tool metrics for sessions without cache', {
|
|
130
|
+
count: metricsToBackfill.length
|
|
131
|
+
});
|
|
132
|
+
// Fire and forget - update DB in background
|
|
133
|
+
Promise.all(metricsToBackfill.map(({ sessionId, metrics, messageCount }) => this.sessionInfoService.updateToolMetrics(sessionId, metrics, messageCount).catch(() => { }))).catch(() => { });
|
|
134
|
+
}
|
|
135
|
+
// Apply filters and pagination
|
|
136
|
+
const filterStart = Date.now();
|
|
137
|
+
const filtered = this.applyFilters(allConversations, filter);
|
|
138
|
+
const paginated = this.applyPagination(filtered, filter);
|
|
139
|
+
timings.filterAndPaginate = Date.now() - filterStart;
|
|
140
|
+
timings.total = Date.now() - startTime;
|
|
141
|
+
this.logger.debug('listConversations breakdown', {
|
|
142
|
+
timingsMs: timings,
|
|
143
|
+
sessionInfoCount: Object.keys(allSessionInfo).length,
|
|
144
|
+
insightsCount: allInsights.size,
|
|
145
|
+
conversationChainsCount: conversationChains.length,
|
|
146
|
+
filteredCount: filtered.length,
|
|
147
|
+
paginatedCount: paginated.length,
|
|
148
|
+
allowedSessionIds: allowedSessionIds?.size ?? 'all',
|
|
149
|
+
});
|
|
150
|
+
return {
|
|
151
|
+
conversations: paginated,
|
|
152
|
+
total: filtered.length
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
throw new LatticeError('HISTORY_READ_FAILED', `Failed to read conversation history: ${error}`, 500);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Fast path for listing archived sessions - queries DB directly without parsing JSONL files.
|
|
161
|
+
* This is ~100x faster than the file-parsing path for large archives.
|
|
162
|
+
*/
|
|
163
|
+
async listArchivedConversationsFast(filter, timings, startTime) {
|
|
164
|
+
const dbStart = Date.now();
|
|
165
|
+
// Get total count for pagination
|
|
166
|
+
const total = this.sessionInfoService.getArchivedSessionCount();
|
|
167
|
+
// Get paginated archived sessions with insights from DB
|
|
168
|
+
const limit = filter.limit || 20;
|
|
169
|
+
const offset = filter.offset || 0;
|
|
170
|
+
const dbResults = this.sessionInfoService.getArchivedSessionsWithInsights(limit, offset);
|
|
171
|
+
timings.dbQuery = Date.now() - dbStart;
|
|
172
|
+
// Convert to ConversationSummary format
|
|
173
|
+
const conversations = dbResults.map(({ sessionId, sessionInfo, insights }) => {
|
|
174
|
+
// Build tool metrics from insights if available
|
|
175
|
+
const toolMetrics = insights?.metrics_updated_at ? {
|
|
176
|
+
linesAdded: insights.lines_added || 0,
|
|
177
|
+
linesRemoved: insights.lines_removed || 0,
|
|
178
|
+
editCount: insights.edit_count || 0,
|
|
179
|
+
writeCount: insights.write_count || 0
|
|
180
|
+
} : {
|
|
181
|
+
linesAdded: 0,
|
|
182
|
+
linesRemoved: 0,
|
|
183
|
+
editCount: 0,
|
|
184
|
+
writeCount: 0
|
|
185
|
+
};
|
|
186
|
+
return {
|
|
187
|
+
sessionId,
|
|
188
|
+
projectPath: '', // Not available without parsing JSONL - acceptable for archive view
|
|
189
|
+
summary: insights?.purpose || sessionInfo.custom_name || 'Archived session',
|
|
190
|
+
sessionInfo,
|
|
191
|
+
createdAt: sessionInfo.created_at,
|
|
192
|
+
updatedAt: sessionInfo.updated_at,
|
|
193
|
+
messageCount: insights?.message_count || 0,
|
|
194
|
+
totalDuration: 0, // Not available without parsing JSONL
|
|
195
|
+
model: 'Unknown', // Not available without parsing JSONL
|
|
196
|
+
status: 'completed',
|
|
197
|
+
toolMetrics
|
|
198
|
+
};
|
|
199
|
+
});
|
|
200
|
+
timings.total = Date.now() - startTime;
|
|
201
|
+
this.logger.debug('listArchivedConversationsFast', {
|
|
202
|
+
timingsMs: timings,
|
|
203
|
+
total,
|
|
204
|
+
returnedCount: conversations.length,
|
|
205
|
+
limit,
|
|
206
|
+
offset
|
|
207
|
+
});
|
|
208
|
+
return { conversations, total };
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Get the file path for a session's JSONL file.
|
|
212
|
+
* Public wrapper around findSessionFile.
|
|
213
|
+
*/
|
|
214
|
+
async getSessionFilePath(sessionId) {
|
|
215
|
+
return this.findSessionFile(sessionId);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Find the JSONL file for a session by checking each project folder
|
|
219
|
+
* Returns the file path if found, null otherwise
|
|
220
|
+
*/
|
|
221
|
+
async findSessionFile(sessionId) {
|
|
222
|
+
const projectsPath = path.join(this.claudeHomePath, 'projects');
|
|
223
|
+
try {
|
|
224
|
+
const projects = await this.readDirectory(projectsPath);
|
|
225
|
+
for (const project of projects) {
|
|
226
|
+
const projectPath = path.join(projectsPath, project);
|
|
227
|
+
const stats = await fs.stat(projectPath);
|
|
228
|
+
if (!stats.isDirectory())
|
|
229
|
+
continue;
|
|
230
|
+
const sessionFile = path.join(projectPath, `${sessionId}.jsonl`);
|
|
231
|
+
try {
|
|
232
|
+
await fs.access(sessionFile);
|
|
233
|
+
return sessionFile;
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
// File doesn't exist in this project, continue searching
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
this.logger.error('Error searching for session file', error, { sessionId });
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Get file modification times for multiple sessions
|
|
248
|
+
* Used for lightweight activity detection without reading file contents
|
|
249
|
+
*/
|
|
250
|
+
async getSessionFileMtimes(sessionIds) {
|
|
251
|
+
const results = new Map();
|
|
252
|
+
await Promise.all(sessionIds.map(async (sessionId) => {
|
|
253
|
+
const filePath = await this.findSessionFile(sessionId);
|
|
254
|
+
if (filePath) {
|
|
255
|
+
try {
|
|
256
|
+
const stats = await fs.stat(filePath);
|
|
257
|
+
results.set(sessionId, stats.mtimeMs);
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
// File not accessible, skip
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}));
|
|
264
|
+
return results;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Get sessions that are "recently active" based on file modification time.
|
|
268
|
+
* A session is considered active if its JSONL file was modified within the threshold.
|
|
269
|
+
* This is more reliable than fuser since Claude writes frequently during activity.
|
|
270
|
+
*
|
|
271
|
+
* @param sessionIds - Session IDs to check
|
|
272
|
+
* @param thresholdMs - How recent the mtime must be (default: 60 seconds)
|
|
273
|
+
* @returns Set of session IDs that are recently active
|
|
274
|
+
*/
|
|
275
|
+
async getRecentlyActiveSessionsByMtime(sessionIds, thresholdMs = 60000) {
|
|
276
|
+
const activeSet = new Set();
|
|
277
|
+
const now = Date.now();
|
|
278
|
+
const projectsPath = path.join(this.claudeHomePath, 'projects');
|
|
279
|
+
// Build a Set for O(1) lookup
|
|
280
|
+
const sessionIdSet = new Set(sessionIds);
|
|
281
|
+
try {
|
|
282
|
+
// Get all project directories
|
|
283
|
+
const projects = await this.readDirectory(projectsPath);
|
|
284
|
+
// Check all JSONL files in parallel across all projects
|
|
285
|
+
await Promise.all(projects.map(async (project) => {
|
|
286
|
+
const projectDir = path.join(projectsPath, project);
|
|
287
|
+
try {
|
|
288
|
+
const stats = await fs.stat(projectDir);
|
|
289
|
+
if (!stats.isDirectory())
|
|
290
|
+
return;
|
|
291
|
+
const files = await this.readDirectory(projectDir);
|
|
292
|
+
await Promise.all(files
|
|
293
|
+
.filter(f => f.endsWith('.jsonl'))
|
|
294
|
+
.map(async (file) => {
|
|
295
|
+
const sessionId = file.replace('.jsonl', '');
|
|
296
|
+
if (!sessionIdSet.has(sessionId))
|
|
297
|
+
return;
|
|
298
|
+
try {
|
|
299
|
+
const filePath = path.join(projectDir, file);
|
|
300
|
+
const fileStats = await fs.stat(filePath);
|
|
301
|
+
const ageMs = now - fileStats.mtimeMs;
|
|
302
|
+
if (ageMs < thresholdMs) {
|
|
303
|
+
activeSet.add(sessionId);
|
|
304
|
+
this.logger.debug('Session detected as active by mtime', {
|
|
305
|
+
sessionId: sessionId.slice(0, 8),
|
|
306
|
+
ageSeconds: Math.round(ageMs / 1000)
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
// File not accessible, skip
|
|
312
|
+
}
|
|
313
|
+
}));
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
// Project dir not accessible, skip
|
|
317
|
+
}
|
|
318
|
+
}));
|
|
319
|
+
return activeSet;
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
this.logger.error('Error checking session mtimes', error);
|
|
323
|
+
return activeSet;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Fetch conversation with metadata in a single fast operation
|
|
328
|
+
* Uses direct file lookup instead of parsing all conversations
|
|
329
|
+
*/
|
|
330
|
+
async fetchConversationDirect(sessionId) {
|
|
331
|
+
const startTime = Date.now();
|
|
332
|
+
try {
|
|
333
|
+
// Find the session file directly
|
|
334
|
+
const filePath = await this.findSessionFile(sessionId);
|
|
335
|
+
if (!filePath) {
|
|
336
|
+
throw new LatticeError('CONVERSATION_NOT_FOUND', `Conversation ${sessionId} not found`, 404);
|
|
337
|
+
}
|
|
338
|
+
// Parse just this one file
|
|
339
|
+
const entries = await this.parseJsonlFile(filePath);
|
|
340
|
+
// Extract project path from file path
|
|
341
|
+
const projectsPath = path.join(this.claudeHomePath, 'projects');
|
|
342
|
+
const relativePath = filePath.replace(projectsPath + path.sep, '');
|
|
343
|
+
const projectName = relativePath.split(path.sep)[0];
|
|
344
|
+
const projectPath = this.decodeProjectName(projectName);
|
|
345
|
+
// Get summary from global summaries.json
|
|
346
|
+
const summary = await this.getSummaryForSession(sessionId);
|
|
347
|
+
// Build conversation chain from entries
|
|
348
|
+
const summaryMap = new Map();
|
|
349
|
+
if (summary)
|
|
350
|
+
summaryMap.set(sessionId, summary);
|
|
351
|
+
const chain = this.buildConversationChain(sessionId, entries.map(e => ({ ...e, sourceProject: projectPath })), summaryMap);
|
|
352
|
+
if (!chain) {
|
|
353
|
+
throw new LatticeError('CONVERSATION_NOT_FOUND', `Failed to build conversation ${sessionId}`, 404);
|
|
354
|
+
}
|
|
355
|
+
const elapsed = Date.now() - startTime;
|
|
356
|
+
this.logger.debug('Direct conversation fetch completed', {
|
|
357
|
+
sessionId,
|
|
358
|
+
elapsedMs: elapsed,
|
|
359
|
+
messageCount: chain.messages.length
|
|
360
|
+
});
|
|
361
|
+
return {
|
|
362
|
+
messages: this.messageFilter.filterMessages(chain.messages),
|
|
363
|
+
metadata: {
|
|
364
|
+
summary: chain.summary,
|
|
365
|
+
projectPath: chain.projectPath,
|
|
366
|
+
model: chain.model,
|
|
367
|
+
totalDuration: chain.totalDuration
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
catch (error) {
|
|
372
|
+
if (error instanceof LatticeError)
|
|
373
|
+
throw error;
|
|
374
|
+
throw new LatticeError('CONVERSATION_READ_FAILED', `Failed to read conversation: ${error}`, 500);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Get summary for a session from summaries.json
|
|
379
|
+
*/
|
|
380
|
+
async getSummaryForSession(sessionId) {
|
|
381
|
+
try {
|
|
382
|
+
const summariesPath = path.join(this.claudeHomePath, 'summaries.json');
|
|
383
|
+
const content = await fs.readFile(summariesPath, 'utf-8');
|
|
384
|
+
const summaries = JSON.parse(content);
|
|
385
|
+
return summaries[sessionId] || null;
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Decode project folder name to actual path
|
|
393
|
+
*/
|
|
394
|
+
decodeProjectName(projectName) {
|
|
395
|
+
// Project folders use dashes instead of slashes
|
|
396
|
+
// e.g., "-home-jason-cui-custom" -> "/home/jason/cui-custom"
|
|
397
|
+
return projectName.replace(/^-/, '/').replace(/-/g, '/');
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Get the working directory for a specific conversation session
|
|
401
|
+
* Reads the cwd from the session's JSONL file to get the accurate path
|
|
402
|
+
* (folder name decoding is lossy for paths containing hyphens)
|
|
403
|
+
*/
|
|
404
|
+
async getConversationWorkingDirectory(sessionId) {
|
|
405
|
+
const startTime = Date.now();
|
|
406
|
+
try {
|
|
407
|
+
const filePath = await this.findSessionFile(sessionId);
|
|
408
|
+
if (!filePath) {
|
|
409
|
+
this.logger.warn('Session file not found', { sessionId });
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
// Read the first entry from the JSONL file to get the actual cwd
|
|
413
|
+
// This is more reliable than decoding the folder name, which is lossy
|
|
414
|
+
// (e.g., "slingshot-ai" folder becomes "slingshot/ai" when decoded)
|
|
415
|
+
const entries = await this.parseJsonlFile(filePath);
|
|
416
|
+
// Find the first entry with a cwd field
|
|
417
|
+
const entryWithCwd = entries.find(e => e.cwd);
|
|
418
|
+
if (entryWithCwd?.cwd) {
|
|
419
|
+
this.logger.debug('Found working directory from JSONL cwd field', {
|
|
420
|
+
sessionId,
|
|
421
|
+
workingDirectory: entryWithCwd.cwd,
|
|
422
|
+
elapsedMs: Date.now() - startTime
|
|
423
|
+
});
|
|
424
|
+
return entryWithCwd.cwd;
|
|
425
|
+
}
|
|
426
|
+
// Fallback to folder name decoding if no cwd field found
|
|
427
|
+
// (older sessions may not have cwd in the JSONL)
|
|
428
|
+
const projectsPath = path.join(this.claudeHomePath, 'projects');
|
|
429
|
+
const relativePath = filePath.replace(projectsPath + path.sep, '');
|
|
430
|
+
const projectName = relativePath.split(path.sep)[0];
|
|
431
|
+
const projectPath = this.decodeProjectName(projectName);
|
|
432
|
+
this.logger.debug('Found working directory from folder name (fallback)', {
|
|
433
|
+
sessionId,
|
|
434
|
+
workingDirectory: projectPath,
|
|
435
|
+
elapsedMs: Date.now() - startTime
|
|
436
|
+
});
|
|
437
|
+
return projectPath;
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
this.logger.error('Error getting working directory for conversation', error, { sessionId });
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Get file modification times for JSONL files
|
|
446
|
+
* @param allowedSessionIds Optional filter - if provided, only stat files for these sessions
|
|
447
|
+
*/
|
|
448
|
+
async getFileModificationTimes(allowedSessionIds) {
|
|
449
|
+
const modTimes = new Map();
|
|
450
|
+
const projectsPath = path.join(this.claudeHomePath, 'projects');
|
|
451
|
+
this.logger.debug('Getting file modification times', {
|
|
452
|
+
projectsPath,
|
|
453
|
+
filtered: !!allowedSessionIds,
|
|
454
|
+
allowedCount: allowedSessionIds?.size
|
|
455
|
+
});
|
|
456
|
+
try {
|
|
457
|
+
const projects = await this.readDirectory(projectsPath);
|
|
458
|
+
this.logger.debug('Found projects', { projectCount: projects.length });
|
|
459
|
+
for (const project of projects) {
|
|
460
|
+
const projectPath = path.join(projectsPath, project);
|
|
461
|
+
const stats = await fs.stat(projectPath);
|
|
462
|
+
if (!stats.isDirectory())
|
|
463
|
+
continue;
|
|
464
|
+
const files = await this.readDirectory(projectPath);
|
|
465
|
+
const jsonlFiles = files.filter(f => f.endsWith('.jsonl'));
|
|
466
|
+
for (const file of jsonlFiles) {
|
|
467
|
+
// Skip files not in allowed list (optimization: avoid stat calls)
|
|
468
|
+
if (allowedSessionIds) {
|
|
469
|
+
const sessionId = file.replace('.jsonl', '');
|
|
470
|
+
if (!allowedSessionIds.has(sessionId))
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
const filePath = path.join(projectPath, file);
|
|
474
|
+
try {
|
|
475
|
+
const fileStats = await fs.stat(filePath);
|
|
476
|
+
modTimes.set(filePath, fileStats.mtimeMs);
|
|
477
|
+
}
|
|
478
|
+
catch (error) {
|
|
479
|
+
this.logger.warn('Failed to stat file', { filePath, error });
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
this.logger.debug('File modification times collection complete', {
|
|
484
|
+
totalFiles: modTimes.size,
|
|
485
|
+
projects: projects.length
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
this.logger.error('Error getting file modification times', error);
|
|
490
|
+
}
|
|
491
|
+
return modTimes;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Extract source project name from file path
|
|
495
|
+
*/
|
|
496
|
+
extractSourceProject(filePath) {
|
|
497
|
+
const projectsPath = path.join(this.claudeHomePath, 'projects');
|
|
498
|
+
const relativePath = path.relative(projectsPath, filePath);
|
|
499
|
+
const segments = relativePath.split(path.sep);
|
|
500
|
+
return segments[0]; // First segment is the project directory name
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Process all entries into conversation chains (the cheap in-memory operations)
|
|
504
|
+
*/
|
|
505
|
+
processAllEntries(allEntries) {
|
|
506
|
+
const startTime = Date.now();
|
|
507
|
+
this.logger.debug('Processing all entries into conversations', {
|
|
508
|
+
totalEntries: allEntries.length
|
|
509
|
+
});
|
|
510
|
+
// Group entries by sessionId
|
|
511
|
+
const sessionGroups = this.groupEntriesBySession(allEntries);
|
|
512
|
+
this.logger.debug('Entries grouped by session', {
|
|
513
|
+
sessionCount: sessionGroups.size,
|
|
514
|
+
totalEntries: allEntries.length
|
|
515
|
+
});
|
|
516
|
+
// Process summaries
|
|
517
|
+
const summaries = this.processSummaries(allEntries);
|
|
518
|
+
this.logger.debug('Summaries processed', {
|
|
519
|
+
summaryCount: summaries.size
|
|
520
|
+
});
|
|
521
|
+
// Build conversation chains
|
|
522
|
+
const conversationChains = [];
|
|
523
|
+
for (const [sessionId, entries] of sessionGroups) {
|
|
524
|
+
const chain = this.buildConversationChain(sessionId, entries, summaries);
|
|
525
|
+
if (chain) {
|
|
526
|
+
conversationChains.push(chain);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
const totalElapsed = Date.now() - startTime;
|
|
530
|
+
this.logger.debug('Entry processing complete', {
|
|
531
|
+
conversationCount: conversationChains.length,
|
|
532
|
+
totalElapsedMs: totalElapsed,
|
|
533
|
+
avgTimePerConversation: conversationChains.length > 0 ? totalElapsed / conversationChains.length : 0
|
|
534
|
+
});
|
|
535
|
+
return conversationChains;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Parse all conversations from all JSONL files with file-level caching and concurrency protection
|
|
539
|
+
* @param allowedSessionIds Optional set of session IDs to parse. If provided, only these sessions will be parsed.
|
|
540
|
+
*/
|
|
541
|
+
async parseAllConversations(allowedSessionIds) {
|
|
542
|
+
const startTime = Date.now();
|
|
543
|
+
const timings = {};
|
|
544
|
+
this.logger.debug('Starting parseAllConversations with file-level caching', {
|
|
545
|
+
filtered: allowedSessionIds !== null && allowedSessionIds !== undefined,
|
|
546
|
+
allowedCount: allowedSessionIds?.size
|
|
547
|
+
});
|
|
548
|
+
// Get current file modification times (filtered if allowedSessionIds provided)
|
|
549
|
+
const modTimeStart = Date.now();
|
|
550
|
+
const filteredModTimes = await this.getFileModificationTimes(allowedSessionIds);
|
|
551
|
+
timings.getFileModificationTimes = Date.now() - modTimeStart;
|
|
552
|
+
// Use the new file-level cache interface
|
|
553
|
+
const cacheStart = Date.now();
|
|
554
|
+
const conversations = await this.conversationCache.getOrParseConversations(filteredModTimes, (filePath) => this.parseJsonlFile(filePath), // Parse single file
|
|
555
|
+
(filePath) => this.extractSourceProject(filePath), // Get source project
|
|
556
|
+
(allEntries) => this.processAllEntries(allEntries) // Process entries
|
|
557
|
+
);
|
|
558
|
+
timings.getOrParseConversations = Date.now() - cacheStart;
|
|
559
|
+
timings.total = Date.now() - startTime;
|
|
560
|
+
this.logger.debug('parseAllConversations breakdown', {
|
|
561
|
+
timingsMs: timings,
|
|
562
|
+
filteredModTimesCount: filteredModTimes.size,
|
|
563
|
+
conversationCount: conversations.length,
|
|
564
|
+
});
|
|
565
|
+
return conversations;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Parse a single JSONL file and return all valid entries
|
|
569
|
+
*/
|
|
570
|
+
async parseJsonlFile(filePath) {
|
|
571
|
+
try {
|
|
572
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
573
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
574
|
+
const entries = [];
|
|
575
|
+
for (const line of lines) {
|
|
576
|
+
try {
|
|
577
|
+
const entry = JSON.parse(line);
|
|
578
|
+
entries.push(entry);
|
|
579
|
+
}
|
|
580
|
+
catch (parseError) {
|
|
581
|
+
this.logger.warn('Failed to parse line from JSONL file', {
|
|
582
|
+
error: parseError,
|
|
583
|
+
filePath,
|
|
584
|
+
line: line.substring(0, 100)
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return entries;
|
|
589
|
+
}
|
|
590
|
+
catch (error) {
|
|
591
|
+
this.logger.error('Failed to read JSONL file', error, { filePath });
|
|
592
|
+
return [];
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Group entries by sessionId
|
|
597
|
+
*/
|
|
598
|
+
groupEntriesBySession(entries) {
|
|
599
|
+
const sessionGroups = new Map();
|
|
600
|
+
for (const entry of entries) {
|
|
601
|
+
// Only group user and assistant messages
|
|
602
|
+
if ((entry.type === 'user' || entry.type === 'assistant') && entry.sessionId) {
|
|
603
|
+
if (!sessionGroups.has(entry.sessionId)) {
|
|
604
|
+
sessionGroups.set(entry.sessionId, []);
|
|
605
|
+
}
|
|
606
|
+
sessionGroups.get(entry.sessionId).push(entry);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return sessionGroups;
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Process summary entries and create leafUuid mapping
|
|
613
|
+
*/
|
|
614
|
+
processSummaries(entries) {
|
|
615
|
+
const summaries = new Map();
|
|
616
|
+
for (const entry of entries) {
|
|
617
|
+
if (entry.type === 'summary' && entry.leafUuid && entry.summary) {
|
|
618
|
+
summaries.set(entry.leafUuid, entry.summary);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return summaries;
|
|
622
|
+
}
|
|
623
|
+
async readDirectory(dirPath) {
|
|
624
|
+
try {
|
|
625
|
+
return await fs.readdir(dirPath);
|
|
626
|
+
}
|
|
627
|
+
catch (error) {
|
|
628
|
+
if (error.code === 'ENOENT') {
|
|
629
|
+
return [];
|
|
630
|
+
}
|
|
631
|
+
throw error;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Build a conversation chain from session entries
|
|
636
|
+
*/
|
|
637
|
+
buildConversationChain(sessionId, entries, summaries) {
|
|
638
|
+
try {
|
|
639
|
+
// Convert entries to ConversationMessage format
|
|
640
|
+
const messages = entries.map(entry => this.parseMessage(entry));
|
|
641
|
+
// Build message chain using parentUuid/uuid relationships
|
|
642
|
+
const orderedMessages = this.buildMessageChain(messages);
|
|
643
|
+
if (orderedMessages.length === 0) {
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
646
|
+
// Apply message filter
|
|
647
|
+
const filteredMessages = this.messageFilter.filterMessages(orderedMessages);
|
|
648
|
+
// Check if we have any messages left after filtering
|
|
649
|
+
if (filteredMessages.length === 0) {
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
// Filter out warmup/sidechain-only sessions
|
|
653
|
+
// These are subagent sessions that exist only to prime the context cache
|
|
654
|
+
const firstUserMessage = filteredMessages.find(msg => msg.type === 'user');
|
|
655
|
+
let isWarmupSession = false;
|
|
656
|
+
if (firstUserMessage?.message) {
|
|
657
|
+
const msgContent = 'content' in firstUserMessage.message
|
|
658
|
+
? firstUserMessage.message.content
|
|
659
|
+
: null;
|
|
660
|
+
if (msgContent) {
|
|
661
|
+
isWarmupSession = typeof msgContent === 'string'
|
|
662
|
+
? msgContent.trim() === 'Warmup'
|
|
663
|
+
: Array.isArray(msgContent) &&
|
|
664
|
+
msgContent.length === 1 &&
|
|
665
|
+
msgContent[0].type === 'text' &&
|
|
666
|
+
'text' in msgContent[0] &&
|
|
667
|
+
msgContent[0].text?.trim() === 'Warmup';
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
// Skip sessions that are pure warmup sessions (sidechain with just "Warmup" prompt)
|
|
671
|
+
if (isWarmupSession && orderedMessages[0]?.isSidechain) {
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
// Determine project path - use original first message for cwd before filtering
|
|
675
|
+
const firstMessage = orderedMessages[0];
|
|
676
|
+
let projectPath = '';
|
|
677
|
+
if (firstMessage.cwd) {
|
|
678
|
+
projectPath = firstMessage.cwd;
|
|
679
|
+
}
|
|
680
|
+
else {
|
|
681
|
+
// Fallback to decoding directory name from source project
|
|
682
|
+
const sourceProject = entries[0].sourceProject;
|
|
683
|
+
projectPath = this.decodeProjectPath(sourceProject);
|
|
684
|
+
}
|
|
685
|
+
// Determine conversation summary
|
|
686
|
+
const summary = this.determineConversationSummary(filteredMessages, summaries);
|
|
687
|
+
// Calculate metadata from filtered messages
|
|
688
|
+
const totalDuration = filteredMessages.reduce((sum, msg) => sum + (msg.durationMs || 0), 0);
|
|
689
|
+
const model = this.extractModel(filteredMessages);
|
|
690
|
+
// Get timestamps from filtered messages
|
|
691
|
+
const timestamps = filteredMessages
|
|
692
|
+
.map(msg => msg.timestamp)
|
|
693
|
+
.filter(ts => ts)
|
|
694
|
+
.sort();
|
|
695
|
+
const createdAt = timestamps[0] || new Date().toISOString();
|
|
696
|
+
const updatedAt = timestamps[timestamps.length - 1] || createdAt;
|
|
697
|
+
return {
|
|
698
|
+
sessionId,
|
|
699
|
+
messages: filteredMessages,
|
|
700
|
+
projectPath,
|
|
701
|
+
summary,
|
|
702
|
+
createdAt,
|
|
703
|
+
updatedAt,
|
|
704
|
+
totalDuration,
|
|
705
|
+
model
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
catch (error) {
|
|
709
|
+
this.logger.error('Error building conversation chain', error, { sessionId });
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Build ordered message chain using parentUuid relationships
|
|
715
|
+
*/
|
|
716
|
+
buildMessageChain(messages) {
|
|
717
|
+
// Create uuid to message mapping
|
|
718
|
+
const messageMap = new Map();
|
|
719
|
+
messages.forEach(msg => messageMap.set(msg.uuid, msg));
|
|
720
|
+
// Find head message (parentUuid is null)
|
|
721
|
+
// Prefer non-sidechain roots - sidechain entries (subagent tasks) have their own
|
|
722
|
+
// separate chains but share the same sessionId
|
|
723
|
+
const rootMessages = messages.filter(msg => !msg.parentUuid);
|
|
724
|
+
const mainRoot = rootMessages.find(msg => !msg.isSidechain);
|
|
725
|
+
const headMessage = mainRoot || rootMessages[0];
|
|
726
|
+
if (!headMessage) {
|
|
727
|
+
// If no head found, return messages sorted by timestamp
|
|
728
|
+
return messages.sort((a, b) => new Date(a.timestamp || '').getTime() - new Date(b.timestamp || '').getTime());
|
|
729
|
+
}
|
|
730
|
+
// Build chain from head
|
|
731
|
+
const orderedMessages = [];
|
|
732
|
+
const visited = new Set();
|
|
733
|
+
const traverse = (currentMessage) => {
|
|
734
|
+
if (visited.has(currentMessage.uuid)) {
|
|
735
|
+
return; // Avoid cycles
|
|
736
|
+
}
|
|
737
|
+
visited.add(currentMessage.uuid);
|
|
738
|
+
orderedMessages.push(currentMessage);
|
|
739
|
+
// Find children (messages with this message as parent)
|
|
740
|
+
const children = messages.filter(msg => msg.parentUuid === currentMessage.uuid);
|
|
741
|
+
// Sort children by timestamp to maintain order
|
|
742
|
+
children.sort((a, b) => new Date(a.timestamp || '').getTime() - new Date(b.timestamp || '').getTime());
|
|
743
|
+
children.forEach(child => traverse(child));
|
|
744
|
+
};
|
|
745
|
+
traverse(headMessage);
|
|
746
|
+
// Add any orphaned messages at the end
|
|
747
|
+
const orphanedMessages = messages.filter(msg => !visited.has(msg.uuid));
|
|
748
|
+
orderedMessages.push(...orphanedMessages.sort((a, b) => new Date(a.timestamp || '').getTime() - new Date(b.timestamp || '').getTime()));
|
|
749
|
+
return orderedMessages;
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Determine conversation summary from messages and summary map
|
|
753
|
+
*/
|
|
754
|
+
determineConversationSummary(messages, summaries) {
|
|
755
|
+
// Walk through messages from latest to earliest to find last available summary
|
|
756
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
757
|
+
const message = messages[i];
|
|
758
|
+
if (summaries.has(message.uuid)) {
|
|
759
|
+
return summaries.get(message.uuid);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
// Fallback to first user message content
|
|
763
|
+
const firstUserMessage = messages.find(msg => msg.type === 'user');
|
|
764
|
+
if (firstUserMessage && firstUserMessage.message) {
|
|
765
|
+
const content = this.extractMessageContent(firstUserMessage.message);
|
|
766
|
+
return content.length > 100 ? content.substring(0, 100) + '...' : content;
|
|
767
|
+
}
|
|
768
|
+
return 'No summary available';
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Extract text content from message object
|
|
772
|
+
*/
|
|
773
|
+
extractMessageContent(message) {
|
|
774
|
+
if (typeof message === 'string') {
|
|
775
|
+
return message;
|
|
776
|
+
}
|
|
777
|
+
if (message.content) {
|
|
778
|
+
if (typeof message.content === 'string') {
|
|
779
|
+
return message.content;
|
|
780
|
+
}
|
|
781
|
+
if (Array.isArray(message.content)) {
|
|
782
|
+
// Find first text content block
|
|
783
|
+
const textBlock = message.content.find((block) => block.type === 'text');
|
|
784
|
+
return textBlock && 'text' in textBlock ? textBlock.text : '';
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
return 'No content available';
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Extract model information from messages
|
|
791
|
+
*/
|
|
792
|
+
extractModel(messages) {
|
|
793
|
+
for (const message of messages) {
|
|
794
|
+
if (message.message && typeof message.message === 'object') {
|
|
795
|
+
const messageObj = message.message;
|
|
796
|
+
if (messageObj.model) {
|
|
797
|
+
return messageObj.model;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
return 'Unknown';
|
|
802
|
+
}
|
|
803
|
+
parseMessage(entry) {
|
|
804
|
+
return {
|
|
805
|
+
uuid: entry.uuid || '',
|
|
806
|
+
type: entry.type,
|
|
807
|
+
message: entry.message, // Non-null assertion since ConversationMessage requires it
|
|
808
|
+
timestamp: entry.timestamp || '',
|
|
809
|
+
sessionId: entry.sessionId || '',
|
|
810
|
+
parentUuid: entry.parentUuid,
|
|
811
|
+
isSidechain: entry.isSidechain,
|
|
812
|
+
userType: entry.userType,
|
|
813
|
+
cwd: entry.cwd,
|
|
814
|
+
version: entry.version,
|
|
815
|
+
durationMs: entry.durationMs
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
applyFilters(conversations, filter) {
|
|
819
|
+
if (!filter)
|
|
820
|
+
return conversations;
|
|
821
|
+
let filtered = [...conversations];
|
|
822
|
+
// Filter by project path
|
|
823
|
+
if (filter.projectPath) {
|
|
824
|
+
filtered = filtered.filter(c => c.projectPath === filter.projectPath);
|
|
825
|
+
}
|
|
826
|
+
// Filter by continuation session
|
|
827
|
+
if (filter.hasContinuation !== undefined) {
|
|
828
|
+
filtered = filtered.filter(c => {
|
|
829
|
+
const hasContinuation = c.sessionInfo.continuation_session_id !== '';
|
|
830
|
+
return filter.hasContinuation ? hasContinuation : !hasContinuation;
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
// Filter by archived status
|
|
834
|
+
if (filter.archived !== undefined) {
|
|
835
|
+
filtered = filtered.filter(c => c.sessionInfo.archived === filter.archived);
|
|
836
|
+
}
|
|
837
|
+
// Filter by pinned status
|
|
838
|
+
if (filter.pinned !== undefined) {
|
|
839
|
+
filtered = filtered.filter(c => c.sessionInfo.pinned === filter.pinned);
|
|
840
|
+
}
|
|
841
|
+
// Sort
|
|
842
|
+
if (filter.sortBy) {
|
|
843
|
+
filtered.sort((a, b) => {
|
|
844
|
+
const field = filter.sortBy === 'created' ? 'createdAt' : 'updatedAt';
|
|
845
|
+
const aVal = new Date(a[field]).getTime();
|
|
846
|
+
const bVal = new Date(b[field]).getTime();
|
|
847
|
+
return filter.order === 'desc' ? bVal - aVal : aVal - bVal;
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
return filtered;
|
|
851
|
+
}
|
|
852
|
+
applyPagination(conversations, filter) {
|
|
853
|
+
if (!filter)
|
|
854
|
+
return conversations;
|
|
855
|
+
const limit = filter.limit || 20;
|
|
856
|
+
const offset = filter.offset || 0;
|
|
857
|
+
return conversations.slice(offset, offset + limit);
|
|
858
|
+
}
|
|
859
|
+
decodeProjectPath(encoded) {
|
|
860
|
+
// Claude encodes directory paths by replacing '/' with '-'
|
|
861
|
+
return encoded.replace(/-/g, '/');
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
//# sourceMappingURL=claude-history-reader.js.map
|