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,936 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import { createLogger } from '../infrastructure/logger.js';
|
|
3
|
+
import { anthropicService } from '../insights/anthropic-service.js';
|
|
4
|
+
import { SessionInfoService } from '../sessions/session-info-service.js';
|
|
5
|
+
import { geminiService } from '../gemini-service.js';
|
|
6
|
+
import { getSessionActivityWatcher } from '../sessions/session-activity-watcher.js';
|
|
7
|
+
/**
|
|
8
|
+
* Generate a trace ID for correlating recompute events through the system.
|
|
9
|
+
* Format: <session-prefix>-<timestamp-hex>-<random>
|
|
10
|
+
*/
|
|
11
|
+
function generateTraceId(sessionId) {
|
|
12
|
+
const sessionPrefix = sessionId.slice(0, 8);
|
|
13
|
+
const timestampHex = Date.now().toString(16);
|
|
14
|
+
const random = crypto.randomBytes(2).toString('hex');
|
|
15
|
+
return `${sessionPrefix}-${timestampHex}-${random}`;
|
|
16
|
+
}
|
|
17
|
+
function buildStateSnapshot(cache, insights) {
|
|
18
|
+
if (insights) {
|
|
19
|
+
return {
|
|
20
|
+
mission: insights.context?.mission || undefined,
|
|
21
|
+
description: insights.description || undefined,
|
|
22
|
+
theme: insights.theme || undefined,
|
|
23
|
+
milestones: insights.milestones?.map(m => `${m.label}:${m.status}`) || [],
|
|
24
|
+
notableCount: insights.notable?.length || 0,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (cache) {
|
|
28
|
+
return {
|
|
29
|
+
mission: cache.context?.mission || undefined,
|
|
30
|
+
description: cache.description || undefined,
|
|
31
|
+
theme: cache.theme || undefined,
|
|
32
|
+
milestones: cache.milestones?.map((m) => `${m.label}:${m.status}`) || [],
|
|
33
|
+
notableCount: cache.notable?.length || 0,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* InsightsComputer - The computational engine for generating insights.
|
|
40
|
+
*
|
|
41
|
+
* This service handles the actual LLM calls and prompt construction for insights.
|
|
42
|
+
* It does NOT coordinate when to generate insights - that's InsightsCoordinator's job.
|
|
43
|
+
*
|
|
44
|
+
* Responsibilities:
|
|
45
|
+
* - Building prompts for insight generation
|
|
46
|
+
* - Calling Anthropic/Gemini APIs
|
|
47
|
+
* - Caching computed insights
|
|
48
|
+
* - Generating identity images
|
|
49
|
+
*
|
|
50
|
+
* Related services:
|
|
51
|
+
* - InsightsCoordinator (insights-coordinator.ts) - orchestrates when to compute
|
|
52
|
+
* - AnthropicService (anthropic-service.ts) - actual API calls
|
|
53
|
+
*/
|
|
54
|
+
export class InsightsComputer {
|
|
55
|
+
logger;
|
|
56
|
+
historyReader;
|
|
57
|
+
sessionInfoService;
|
|
58
|
+
constructor(historyReader, sessionInfoService) {
|
|
59
|
+
this.logger = createLogger('InsightsComputer');
|
|
60
|
+
this.historyReader = historyReader;
|
|
61
|
+
this.sessionInfoService = sessionInfoService || SessionInfoService.getInstance();
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Check if a session is automated (e.g., Claudia sessions).
|
|
65
|
+
* Automated sessions should skip expensive AI insight generation.
|
|
66
|
+
*/
|
|
67
|
+
async isAutomatedSession(sessionId) {
|
|
68
|
+
try {
|
|
69
|
+
const { metadata } = await this.historyReader.fetchConversationDirect(sessionId);
|
|
70
|
+
return metadata.projectPath?.toLowerCase().includes('/claudia') || false;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// If we can't determine, assume not automated
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Extract the final todo state from conversation messages
|
|
79
|
+
*/
|
|
80
|
+
extractTodoState(messages) {
|
|
81
|
+
let lastTodoState = null;
|
|
82
|
+
for (const msg of messages) {
|
|
83
|
+
const content = msg.message?.content;
|
|
84
|
+
if (!Array.isArray(content))
|
|
85
|
+
continue;
|
|
86
|
+
for (const block of content) {
|
|
87
|
+
if (typeof block === 'object' &&
|
|
88
|
+
block !== null &&
|
|
89
|
+
'type' in block &&
|
|
90
|
+
block.type === 'tool_use' &&
|
|
91
|
+
'name' in block &&
|
|
92
|
+
block.name === 'TodoWrite' &&
|
|
93
|
+
'input' in block &&
|
|
94
|
+
typeof block.input === 'object' &&
|
|
95
|
+
block.input !== null &&
|
|
96
|
+
'todos' in block.input &&
|
|
97
|
+
Array.isArray(block.input.todos)) {
|
|
98
|
+
lastTodoState = block.input.todos;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return lastTodoState;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Compute progress from todo state
|
|
106
|
+
*/
|
|
107
|
+
computeProgress(todos) {
|
|
108
|
+
const completed = todos.filter(t => t.status === 'completed').length;
|
|
109
|
+
const inProgress = todos.filter(t => t.status === 'in_progress').length;
|
|
110
|
+
const pending = todos.filter(t => t.status === 'pending').length;
|
|
111
|
+
const total = todos.length;
|
|
112
|
+
const percent = total > 0 ? Math.round((completed / total) * 100) : 0;
|
|
113
|
+
return { completed, inProgress, pending, total, percent };
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Extract the last N actions from recent messages (tool uses, user messages, assistant text)
|
|
117
|
+
* Returns an array of {tool, timestamp} for mini action log display
|
|
118
|
+
*/
|
|
119
|
+
extractRecentActions(messages, limit = 14) {
|
|
120
|
+
const actions = [];
|
|
121
|
+
// Walk backwards through messages
|
|
122
|
+
for (let i = messages.length - 1; i >= 0 && actions.length < limit; i--) {
|
|
123
|
+
const msg = messages[i];
|
|
124
|
+
const content = msg.message?.content;
|
|
125
|
+
const timestamp = msg.timestamp ? new Date(msg.timestamp).getTime() : Date.now();
|
|
126
|
+
// User messages - handle both string and array content
|
|
127
|
+
if (msg.type === 'user') {
|
|
128
|
+
// String content (most common for typed user prompts)
|
|
129
|
+
if (typeof content === 'string' && content.trim().length > 0) {
|
|
130
|
+
actions.push({
|
|
131
|
+
tool: 'User',
|
|
132
|
+
timestamp
|
|
133
|
+
});
|
|
134
|
+
if (actions.length >= limit)
|
|
135
|
+
break;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
// Array content - look for text blocks
|
|
139
|
+
if (Array.isArray(content)) {
|
|
140
|
+
for (const block of content) {
|
|
141
|
+
if (typeof block === 'object' &&
|
|
142
|
+
block !== null &&
|
|
143
|
+
'type' in block &&
|
|
144
|
+
block.type === 'text' &&
|
|
145
|
+
'text' in block &&
|
|
146
|
+
typeof block.text === 'string' &&
|
|
147
|
+
block.text.trim().length > 0) {
|
|
148
|
+
actions.push({
|
|
149
|
+
tool: 'User',
|
|
150
|
+
timestamp
|
|
151
|
+
});
|
|
152
|
+
// Only count one user action per message
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (actions.length >= limit)
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
// Skip non-array content for assistant messages
|
|
162
|
+
if (!Array.isArray(content))
|
|
163
|
+
continue;
|
|
164
|
+
// Assistant messages - both tool uses and text responses
|
|
165
|
+
if (msg.type === 'assistant') {
|
|
166
|
+
let hasToolUse = false;
|
|
167
|
+
let _hasText = false;
|
|
168
|
+
// First pass: check what types we have
|
|
169
|
+
for (const block of content) {
|
|
170
|
+
if (typeof block === 'object' && block !== null && 'type' in block) {
|
|
171
|
+
if (block.type === 'tool_use')
|
|
172
|
+
hasToolUse = true;
|
|
173
|
+
if (block.type === 'text')
|
|
174
|
+
_hasText = true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Second pass: add actions
|
|
178
|
+
for (const block of content) {
|
|
179
|
+
if (typeof block !== 'object' || block === null || !('type' in block))
|
|
180
|
+
continue;
|
|
181
|
+
if (block.type === 'tool_use' && 'name' in block && typeof block.name === 'string') {
|
|
182
|
+
actions.push({
|
|
183
|
+
tool: block.name,
|
|
184
|
+
timestamp
|
|
185
|
+
});
|
|
186
|
+
if (actions.length >= limit)
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
else if (block.type === 'text' && 'text' in block && typeof block.text === 'string') {
|
|
190
|
+
// Only add text response if there's no tool use in this message
|
|
191
|
+
if (!hasToolUse && block.text.trim().length > 0) {
|
|
192
|
+
actions.push({
|
|
193
|
+
tool: 'Response',
|
|
194
|
+
timestamp
|
|
195
|
+
});
|
|
196
|
+
// Only count one text response per message
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (actions.length >= limit)
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Reverse to get chronological order (oldest first, newest last)
|
|
206
|
+
return actions.reverse();
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Extract user prompts from conversation
|
|
210
|
+
*/
|
|
211
|
+
extractUserPrompts(messages) {
|
|
212
|
+
const prompts = [];
|
|
213
|
+
for (const msg of messages) {
|
|
214
|
+
if (msg.type !== 'user')
|
|
215
|
+
continue;
|
|
216
|
+
const content = msg.message?.content;
|
|
217
|
+
if (typeof content === 'string' && content.trim()) {
|
|
218
|
+
prompts.push(content.trim());
|
|
219
|
+
}
|
|
220
|
+
else if (Array.isArray(content)) {
|
|
221
|
+
for (const block of content) {
|
|
222
|
+
if (typeof block === 'object' &&
|
|
223
|
+
block !== null &&
|
|
224
|
+
'type' in block &&
|
|
225
|
+
block.type === 'text' &&
|
|
226
|
+
'text' in block &&
|
|
227
|
+
typeof block.text === 'string') {
|
|
228
|
+
prompts.push(block.text.trim());
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return prompts.filter(p => p.length > 0 && !p.startsWith('['));
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Extract recent assistant text responses (last N messages)
|
|
237
|
+
* Used to capture outcomes and progress for insight generation
|
|
238
|
+
*/
|
|
239
|
+
extractRecentAssistantText(messages, limit = 3) {
|
|
240
|
+
const texts = [];
|
|
241
|
+
// Work backwards from the end to get most recent first
|
|
242
|
+
for (let i = messages.length - 1; i >= 0 && texts.length < limit; i--) {
|
|
243
|
+
const msg = messages[i];
|
|
244
|
+
if (msg.type !== 'assistant')
|
|
245
|
+
continue;
|
|
246
|
+
const content = msg.message?.content;
|
|
247
|
+
if (Array.isArray(content)) {
|
|
248
|
+
for (const block of content) {
|
|
249
|
+
if (typeof block === 'object' &&
|
|
250
|
+
block !== null &&
|
|
251
|
+
'type' in block &&
|
|
252
|
+
block.type === 'text' &&
|
|
253
|
+
'text' in block &&
|
|
254
|
+
typeof block.text === 'string' &&
|
|
255
|
+
block.text.trim()) {
|
|
256
|
+
texts.push(block.text.trim());
|
|
257
|
+
break; // Only one text block per message
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return texts;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Format a tool call into a human-readable action description
|
|
266
|
+
*/
|
|
267
|
+
formatToolAction(toolName, input) {
|
|
268
|
+
switch (toolName) {
|
|
269
|
+
case 'Read':
|
|
270
|
+
return `Reading ${this.extractFilename(input.file_path)}`;
|
|
271
|
+
case 'Edit':
|
|
272
|
+
return `Editing ${this.extractFilename(input.file_path)}`;
|
|
273
|
+
case 'Write':
|
|
274
|
+
return `Writing ${this.extractFilename(input.file_path)}`;
|
|
275
|
+
case 'Grep':
|
|
276
|
+
return `Searching: "${input.pattern?.slice(0, 40)}"`;
|
|
277
|
+
case 'Glob':
|
|
278
|
+
return `Finding: ${input.pattern?.slice(0, 40)}`;
|
|
279
|
+
case 'Bash': {
|
|
280
|
+
const description = input.description;
|
|
281
|
+
if (description) {
|
|
282
|
+
return description.slice(0, 50);
|
|
283
|
+
}
|
|
284
|
+
const command = input.command || '';
|
|
285
|
+
return command.slice(0, 50) || 'Running command';
|
|
286
|
+
}
|
|
287
|
+
case 'Task':
|
|
288
|
+
return input.description?.slice(0, 50) || 'Delegating task';
|
|
289
|
+
case 'TodoWrite': {
|
|
290
|
+
const todos = input.todos;
|
|
291
|
+
if (todos && todos.length > 0) {
|
|
292
|
+
const inProgress = todos.find(t => t.status === 'in_progress');
|
|
293
|
+
if (inProgress?.content) {
|
|
294
|
+
return inProgress.content.slice(0, 50);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return 'Updating todo list';
|
|
298
|
+
}
|
|
299
|
+
default:
|
|
300
|
+
return toolName;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Extract filename from a path for display
|
|
305
|
+
*/
|
|
306
|
+
extractFilename(path) {
|
|
307
|
+
if (!path)
|
|
308
|
+
return 'file';
|
|
309
|
+
const parts = path.split('/');
|
|
310
|
+
return parts[parts.length - 1] || 'file';
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Generate rich structured session insights using Opus 4.5
|
|
314
|
+
*/
|
|
315
|
+
async generateStructuredInsights(messages, sessionId) {
|
|
316
|
+
// Check if Anthropic is configured
|
|
317
|
+
if (!anthropicService.isConfigured()) {
|
|
318
|
+
this.logger.debug('Anthropic service not configured, skipping insight extraction');
|
|
319
|
+
return { context: null, milestones: [], notable: [], recentActions: [], theme: null, tags: null };
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
// Get user prompts for context
|
|
323
|
+
const userPrompts = this.extractUserPrompts(messages);
|
|
324
|
+
if (userPrompts.length === 0) {
|
|
325
|
+
return { context: null, milestones: [], notable: [], recentActions: [], theme: null, tags: null };
|
|
326
|
+
}
|
|
327
|
+
// Get todo items for context
|
|
328
|
+
const todos = this.extractTodoState(messages);
|
|
329
|
+
const todoContext = todos
|
|
330
|
+
? `\nTask list:\n${todos.map(t => `- [${t.status}] ${t.content}`).join('\n')}`
|
|
331
|
+
: '';
|
|
332
|
+
// Get recent assistant responses for outcome/progress context
|
|
333
|
+
const recentAssistantText = this.extractRecentAssistantText(messages, 3);
|
|
334
|
+
const assistantContext = recentAssistantText.length > 0
|
|
335
|
+
? `\nRecent assistant responses:\n${recentAssistantText.map(t => `- "${t}"`).join('\n')}`
|
|
336
|
+
: '';
|
|
337
|
+
// Build conversation context - no truncation, models have plenty of context
|
|
338
|
+
const conversationText = `User requests (chronological):
|
|
339
|
+
${userPrompts.slice(-12).map(p => `- "${p}"`).join('\n')}
|
|
340
|
+
${todoContext}${assistantContext}`;
|
|
341
|
+
const result = await anthropicService.extractSessionInsights(conversationText, sessionId);
|
|
342
|
+
return {
|
|
343
|
+
context: result.context,
|
|
344
|
+
milestones: result.milestones,
|
|
345
|
+
notable: result.notable || [],
|
|
346
|
+
recentActions: result.recentActions,
|
|
347
|
+
theme: result.theme || null,
|
|
348
|
+
tags: result.tags || null
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
this.logger.error('Failed to generate structured insights - re-throwing to preserve existing cache', { error });
|
|
353
|
+
// Re-throw so callers can decide whether to use cached data or handle the error
|
|
354
|
+
throw error;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Get full insights for a session (checks cache first)
|
|
359
|
+
* For automated sessions, returns quick insights without AI generation.
|
|
360
|
+
*
|
|
361
|
+
* NON-BLOCKING BEHAVIOR:
|
|
362
|
+
* - If cache is fresh → return it immediately
|
|
363
|
+
* - If cache is stale → return stale data immediately, trigger background regeneration
|
|
364
|
+
* - If no cache → generate synchronously (first time only)
|
|
365
|
+
*/
|
|
366
|
+
async getInsights(sessionId) {
|
|
367
|
+
this.logger.debug('[GET INSIGHTS] Called', {
|
|
368
|
+
sessionId: sessionId.slice(0, 8),
|
|
369
|
+
timestamp: new Date().toISOString()
|
|
370
|
+
});
|
|
371
|
+
// Check cache first
|
|
372
|
+
const cached = await this.sessionInfoService.getInsights(sessionId);
|
|
373
|
+
this.logger.debug('[GET INSIGHTS] Cache lookup result', {
|
|
374
|
+
sessionId: sessionId.slice(0, 8),
|
|
375
|
+
hasCached: !!cached,
|
|
376
|
+
isStale: cached?.stale || false,
|
|
377
|
+
cachedMission: cached?.context ? (typeof cached.context === 'string' ? JSON.parse(cached.context).mission : cached.context.mission)?.slice(0, 50) : null
|
|
378
|
+
});
|
|
379
|
+
if (cached && !cached.stale) {
|
|
380
|
+
this.logger.debug('[GET INSIGHTS] Returning cached (not stale)', {
|
|
381
|
+
sessionId: sessionId.slice(0, 8)
|
|
382
|
+
});
|
|
383
|
+
return this.cachedToSessionInsights(cached);
|
|
384
|
+
}
|
|
385
|
+
// For automated sessions, skip AI generation entirely
|
|
386
|
+
const isAutomated = await this.isAutomatedSession(sessionId);
|
|
387
|
+
if (isAutomated) {
|
|
388
|
+
this.logger.debug('Skipping AI insights for automated session', { sessionId });
|
|
389
|
+
return this.getInsightsQuick(sessionId);
|
|
390
|
+
}
|
|
391
|
+
// NON-BLOCKING: If we have stale cache, return it immediately and regenerate in background
|
|
392
|
+
if (cached && cached.stale) {
|
|
393
|
+
this.logger.debug('[GET INSIGHTS] Returning stale cache, triggering background regeneration', {
|
|
394
|
+
sessionId: sessionId.slice(0, 8)
|
|
395
|
+
});
|
|
396
|
+
// Fire off background regeneration (don't await)
|
|
397
|
+
this.regenerateInsightsInBackground(sessionId, cached).catch(err => this.logger.debug('Background regeneration failed', { sessionId, error: err }));
|
|
398
|
+
// Return stale data immediately for fast UX
|
|
399
|
+
return this.cachedToSessionInsights(cached);
|
|
400
|
+
}
|
|
401
|
+
// No cache at all - must generate synchronously (first time for this session)
|
|
402
|
+
this.logger.warn('[GET INSIGHTS] No cache - triggering synchronous generation', {
|
|
403
|
+
sessionId: sessionId.slice(0, 8)
|
|
404
|
+
});
|
|
405
|
+
const traceId = generateTraceId(sessionId);
|
|
406
|
+
const startTime = Date.now();
|
|
407
|
+
const beforeState = buildStateSnapshot(cached);
|
|
408
|
+
const insights = await this.computeInsights(sessionId);
|
|
409
|
+
// Cache the result and audit the recompute
|
|
410
|
+
const afterState = buildStateSnapshot(null, insights);
|
|
411
|
+
const changedFields = [];
|
|
412
|
+
if (beforeState.mission !== afterState.mission)
|
|
413
|
+
changedFields.push('mission');
|
|
414
|
+
if (beforeState.description !== afterState.description)
|
|
415
|
+
changedFields.push('description');
|
|
416
|
+
if (beforeState.theme !== afterState.theme)
|
|
417
|
+
changedFields.push('theme');
|
|
418
|
+
if (JSON.stringify(beforeState.milestones) !== JSON.stringify(afterState.milestones))
|
|
419
|
+
changedFields.push('milestones');
|
|
420
|
+
const durationMs = Date.now() - startTime;
|
|
421
|
+
// Audit the recompute
|
|
422
|
+
this.sessionInfoService.auditEventInsight({
|
|
423
|
+
traceId,
|
|
424
|
+
sessionId,
|
|
425
|
+
eventType: 'recompute',
|
|
426
|
+
trigger: 'api_request',
|
|
427
|
+
beforeState,
|
|
428
|
+
afterState,
|
|
429
|
+
patchedFields: changedFields,
|
|
430
|
+
durationMs,
|
|
431
|
+
llmResponse: `Opus returned: context=${!!insights.context}, description=${!!insights.description}, milestones=${(insights.milestones || []).length}`,
|
|
432
|
+
});
|
|
433
|
+
// Cache the result (don't await - fire and forget)
|
|
434
|
+
this.cacheInsights(sessionId, insights).catch(err => this.logger.debug('Failed to cache insights', { sessionId, error: err }));
|
|
435
|
+
// Build V9 fields inline for immediate return (before cache is written)
|
|
436
|
+
const now = new Date().toISOString();
|
|
437
|
+
const progressItems = (insights.milestones || []).map(m => ({
|
|
438
|
+
label: m.label,
|
|
439
|
+
status: m.status,
|
|
440
|
+
addedAt: now,
|
|
441
|
+
updatedAt: now
|
|
442
|
+
}));
|
|
443
|
+
const progressSummary = {
|
|
444
|
+
done: progressItems.filter(p => p.status === 'done').length,
|
|
445
|
+
current: progressItems.filter(p => p.status === 'current').length,
|
|
446
|
+
pending: progressItems.filter(p => p.status === 'pending').length,
|
|
447
|
+
paused: 0,
|
|
448
|
+
abandoned: 0,
|
|
449
|
+
total: progressItems.length
|
|
450
|
+
};
|
|
451
|
+
const notableEvents = (insights.notable || []).map(n => ({
|
|
452
|
+
text: n.text || n.description || '',
|
|
453
|
+
icon: n.icon,
|
|
454
|
+
addedAt: now
|
|
455
|
+
}));
|
|
456
|
+
const recentActionsV2 = (insights.recentActions || []).map(text => ({
|
|
457
|
+
text,
|
|
458
|
+
addedAt: now
|
|
459
|
+
}));
|
|
460
|
+
return {
|
|
461
|
+
...insights,
|
|
462
|
+
progressItems,
|
|
463
|
+
progressSummary,
|
|
464
|
+
notableEvents,
|
|
465
|
+
recentActionsV2
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Regenerate insights in the background (for stale cache refresh).
|
|
470
|
+
* This runs async and updates the cache when done.
|
|
471
|
+
*/
|
|
472
|
+
async regenerateInsightsInBackground(sessionId, existingCache) {
|
|
473
|
+
const traceId = generateTraceId(sessionId);
|
|
474
|
+
const startTime = Date.now();
|
|
475
|
+
const beforeState = buildStateSnapshot(existingCache);
|
|
476
|
+
this.logger.info('[BACKGROUND REGEN] Starting', {
|
|
477
|
+
traceId,
|
|
478
|
+
sessionId: sessionId.slice(0, 8)
|
|
479
|
+
});
|
|
480
|
+
try {
|
|
481
|
+
const insights = await this.computeInsights(sessionId);
|
|
482
|
+
// Cache the result and audit the recompute
|
|
483
|
+
const afterState = buildStateSnapshot(null, insights);
|
|
484
|
+
const changedFields = [];
|
|
485
|
+
if (beforeState.mission !== afterState.mission)
|
|
486
|
+
changedFields.push('mission');
|
|
487
|
+
if (beforeState.description !== afterState.description)
|
|
488
|
+
changedFields.push('description');
|
|
489
|
+
if (beforeState.theme !== afterState.theme)
|
|
490
|
+
changedFields.push('theme');
|
|
491
|
+
if (JSON.stringify(beforeState.milestones) !== JSON.stringify(afterState.milestones))
|
|
492
|
+
changedFields.push('milestones');
|
|
493
|
+
const durationMs = Date.now() - startTime;
|
|
494
|
+
// Audit the recompute
|
|
495
|
+
this.sessionInfoService.auditEventInsight({
|
|
496
|
+
traceId,
|
|
497
|
+
sessionId,
|
|
498
|
+
eventType: 'recompute',
|
|
499
|
+
trigger: 'background_stale_refresh',
|
|
500
|
+
beforeState,
|
|
501
|
+
afterState,
|
|
502
|
+
patchedFields: changedFields,
|
|
503
|
+
durationMs,
|
|
504
|
+
llmResponse: `Opus returned: context=${!!insights.context}, description=${!!insights.description}, milestones=${(insights.milestones || []).length}`,
|
|
505
|
+
});
|
|
506
|
+
await this.cacheInsights(sessionId, insights);
|
|
507
|
+
this.logger.info('[BACKGROUND REGEN] Completed', {
|
|
508
|
+
traceId,
|
|
509
|
+
sessionId: sessionId.slice(0, 8),
|
|
510
|
+
durationMs,
|
|
511
|
+
changedFields
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
catch (error) {
|
|
515
|
+
this.logger.warn('[BACKGROUND REGEN] Failed - existing cache preserved', {
|
|
516
|
+
traceId,
|
|
517
|
+
sessionId: sessionId.slice(0, 8),
|
|
518
|
+
error
|
|
519
|
+
});
|
|
520
|
+
// Don't throw - this is a background operation
|
|
521
|
+
// IMPORTANT: We intentionally don't cache anything on failure to preserve existing good data
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Compute insights from conversation (bypasses cache)
|
|
526
|
+
*/
|
|
527
|
+
async computeInsights(sessionId) {
|
|
528
|
+
const { messages } = await this.historyReader.fetchConversationDirect(sessionId);
|
|
529
|
+
const messageCount = messages.length;
|
|
530
|
+
// Extract todo state
|
|
531
|
+
const todos = this.extractTodoState(messages);
|
|
532
|
+
// Compute progress
|
|
533
|
+
const progress = todos ? this.computeProgress(todos) : null;
|
|
534
|
+
// Get recently completed (last 3)
|
|
535
|
+
const recentlyCompleted = todos
|
|
536
|
+
? todos
|
|
537
|
+
.filter(t => t.status === 'completed')
|
|
538
|
+
.slice(-3)
|
|
539
|
+
.map(t => t.content)
|
|
540
|
+
: [];
|
|
541
|
+
// Get outstanding tasks
|
|
542
|
+
const outstanding = todos
|
|
543
|
+
? todos
|
|
544
|
+
.filter(t => t.status === 'pending' || t.status === 'in_progress')
|
|
545
|
+
.map(t => t.content)
|
|
546
|
+
: [];
|
|
547
|
+
// Get current task (in_progress)
|
|
548
|
+
const currentTask = todos
|
|
549
|
+
? todos.find(t => t.status === 'in_progress')?.content || null
|
|
550
|
+
: null;
|
|
551
|
+
// Generate rich structured insights using Opus
|
|
552
|
+
const structured = await this.generateStructuredInsights(messages, sessionId);
|
|
553
|
+
// Use Opus theme (no heuristic fallback - theme is evaluated on user message)
|
|
554
|
+
const theme = structured.theme || null;
|
|
555
|
+
// Generate brief description for title (backward compat)
|
|
556
|
+
const description = structured.context?.mission || null;
|
|
557
|
+
// Build purposes array from context for backward compatibility
|
|
558
|
+
const purposes = structured.context
|
|
559
|
+
? [structured.context.mission]
|
|
560
|
+
: [];
|
|
561
|
+
return {
|
|
562
|
+
sessionId,
|
|
563
|
+
context: structured.context,
|
|
564
|
+
tags: structured.tags,
|
|
565
|
+
theme,
|
|
566
|
+
purposes,
|
|
567
|
+
description,
|
|
568
|
+
progress,
|
|
569
|
+
recentlyCompleted,
|
|
570
|
+
outstanding,
|
|
571
|
+
currentTask,
|
|
572
|
+
messageCount,
|
|
573
|
+
// purpose is set by fast patch (Haiku), not full compute (Opus)
|
|
574
|
+
// It will be preserved from existing cache during cacheInsights
|
|
575
|
+
// === Deprecated fields (kept for backward compat with cached data) ===
|
|
576
|
+
// These are no longer generated by LLM - always empty defaults.
|
|
577
|
+
// V8 fields will be converted to V9 equivalents in cacheInsights()
|
|
578
|
+
currentState: null,
|
|
579
|
+
panels: [],
|
|
580
|
+
milestones: structured.milestones, // Always [] from anthropic-service
|
|
581
|
+
notable: structured.notable, // Always [] from anthropic-service
|
|
582
|
+
recentActions: structured.recentActions, // Always [] from anthropic-service
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Cache insights to database
|
|
587
|
+
*/
|
|
588
|
+
async cacheInsights(sessionId, insights) {
|
|
589
|
+
const now = new Date().toISOString();
|
|
590
|
+
// Preserve existing purpose if new insights don't have one
|
|
591
|
+
// (purpose is set by fast patch/Haiku, not full compute/Opus)
|
|
592
|
+
let purposeToCache = insights.purpose;
|
|
593
|
+
if (!purposeToCache) {
|
|
594
|
+
const existing = await this.sessionInfoService.getInsights(sessionId);
|
|
595
|
+
purposeToCache = existing?.purpose;
|
|
596
|
+
}
|
|
597
|
+
// Initialize V9 progress items from milestones
|
|
598
|
+
const progressItems = (insights.milestones || []).map(m => ({
|
|
599
|
+
label: m.label,
|
|
600
|
+
status: m.status,
|
|
601
|
+
addedAt: now,
|
|
602
|
+
updatedAt: now
|
|
603
|
+
}));
|
|
604
|
+
// Compute progress summary
|
|
605
|
+
const progressSummary = {
|
|
606
|
+
done: progressItems.filter(p => p.status === 'done').length,
|
|
607
|
+
current: progressItems.filter(p => p.status === 'current').length,
|
|
608
|
+
pending: progressItems.filter(p => p.status === 'pending').length,
|
|
609
|
+
paused: 0,
|
|
610
|
+
abandoned: 0,
|
|
611
|
+
total: progressItems.length
|
|
612
|
+
};
|
|
613
|
+
// Initialize V9 notable events from notable
|
|
614
|
+
const notableEvents = (insights.notable || []).map(n => ({
|
|
615
|
+
text: n.text || n.description || '',
|
|
616
|
+
icon: n.icon,
|
|
617
|
+
addedAt: now
|
|
618
|
+
}));
|
|
619
|
+
// Initialize V9 recent actions
|
|
620
|
+
const recentActionsV2 = (insights.recentActions || []).map(text => ({
|
|
621
|
+
text,
|
|
622
|
+
addedAt: now
|
|
623
|
+
}));
|
|
624
|
+
const cached = {
|
|
625
|
+
session_id: sessionId,
|
|
626
|
+
description: insights.description ?? null,
|
|
627
|
+
context: insights.context,
|
|
628
|
+
current_state: insights.currentState ?? null,
|
|
629
|
+
last_patch_trace_id: null,
|
|
630
|
+
purposes: insights.purposes || [],
|
|
631
|
+
milestones: insights.milestones || [],
|
|
632
|
+
notable: insights.notable || [],
|
|
633
|
+
tags: insights.tags,
|
|
634
|
+
recent_actions: insights.recentActions || [],
|
|
635
|
+
panels: insights.panels || [],
|
|
636
|
+
progress_completed: insights.progress?.completed || 0,
|
|
637
|
+
progress_total: insights.progress?.total || 0,
|
|
638
|
+
outstanding_tasks: insights.outstanding || [],
|
|
639
|
+
completed_tasks: insights.recentlyCompleted || [],
|
|
640
|
+
current_task: insights.currentTask ?? null,
|
|
641
|
+
theme: insights.theme,
|
|
642
|
+
computed_at: now,
|
|
643
|
+
stale: false,
|
|
644
|
+
message_count: insights.messageCount,
|
|
645
|
+
// V9 accumulative fields
|
|
646
|
+
progress_items: progressItems,
|
|
647
|
+
notable_events: notableEvents,
|
|
648
|
+
recent_actions_v2: recentActionsV2,
|
|
649
|
+
progress_summary: progressSummary,
|
|
650
|
+
// Evolving purpose (preserved from existing cache if not in new insights)
|
|
651
|
+
purpose: purposeToCache
|
|
652
|
+
};
|
|
653
|
+
await this.sessionInfoService.setInsights(cached);
|
|
654
|
+
// Generate identity image if session doesn't have one yet (fire and forget)
|
|
655
|
+
this.maybeGenerateIdentityImage(sessionId, insights).catch(err => {
|
|
656
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
657
|
+
this.logger.warn('Failed to generate identity image (background)', {
|
|
658
|
+
sessionId,
|
|
659
|
+
errorMessage,
|
|
660
|
+
errorType: err?.constructor?.name
|
|
661
|
+
});
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Generate identity image for a session if it doesn't already have one.
|
|
666
|
+
* This is called after insights are cached/patched, and runs in the background.
|
|
667
|
+
* Public so InsightsCoordinator can call it after patching too.
|
|
668
|
+
*/
|
|
669
|
+
async maybeGenerateIdentityImage(sessionId, insights) {
|
|
670
|
+
// Check if session already has an identity image
|
|
671
|
+
const hasImage = await this.sessionInfoService.hasIdentityImage(sessionId);
|
|
672
|
+
if (hasImage) {
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
// Need context to generate meaningful image
|
|
676
|
+
if (!insights.context?.mission) {
|
|
677
|
+
this.logger.debug('Skipping identity image - no mission context', { sessionId });
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
const imageContext = {
|
|
681
|
+
mission: insights.context.mission,
|
|
682
|
+
project: insights.context.project,
|
|
683
|
+
theme: insights.theme ?? undefined,
|
|
684
|
+
workTypes: insights.tags?.workType
|
|
685
|
+
};
|
|
686
|
+
this.logger.info('Generating identity image for session', {
|
|
687
|
+
sessionId,
|
|
688
|
+
imageContext
|
|
689
|
+
});
|
|
690
|
+
try {
|
|
691
|
+
const result = await geminiService.generateSessionImage(imageContext);
|
|
692
|
+
await this.sessionInfoService.setIdentityImage(sessionId, result.imageData);
|
|
693
|
+
this.logger.info('Identity image generated and saved', { sessionId });
|
|
694
|
+
// Emit a follow-up SSE event so clients can update their cache with the new image
|
|
695
|
+
// This solves the race condition where the initial insights SSE is sent before
|
|
696
|
+
// the identity image is generated
|
|
697
|
+
try {
|
|
698
|
+
await getSessionActivityWatcher().emitInsightsUpdate(sessionId, 'patched');
|
|
699
|
+
this.logger.debug('Emitted SSE update for new identity image', { sessionId });
|
|
700
|
+
}
|
|
701
|
+
catch (sseError) {
|
|
702
|
+
this.logger.debug('Failed to emit SSE for identity image', { sessionId, error: sseError });
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
catch (error) {
|
|
706
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
707
|
+
this.logger.warn('Failed to generate identity image', {
|
|
708
|
+
sessionId,
|
|
709
|
+
errorMessage,
|
|
710
|
+
errorType: error?.constructor?.name
|
|
711
|
+
});
|
|
712
|
+
// Don't throw - this is a non-critical background operation
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Convert cached insights to SessionInsights format
|
|
717
|
+
*/
|
|
718
|
+
cachedToSessionInsights(cached) {
|
|
719
|
+
return {
|
|
720
|
+
sessionId: cached.session_id,
|
|
721
|
+
context: cached.context,
|
|
722
|
+
tags: cached.tags,
|
|
723
|
+
purposes: cached.purposes || [],
|
|
724
|
+
description: cached.description,
|
|
725
|
+
progress: cached.progress_total > 0 ? {
|
|
726
|
+
completed: cached.progress_completed,
|
|
727
|
+
inProgress: 0,
|
|
728
|
+
pending: cached.progress_total - cached.progress_completed,
|
|
729
|
+
total: cached.progress_total,
|
|
730
|
+
percent: Math.round((cached.progress_completed / cached.progress_total) * 100)
|
|
731
|
+
} : null,
|
|
732
|
+
recentlyCompleted: cached.completed_tasks,
|
|
733
|
+
outstanding: cached.outstanding_tasks,
|
|
734
|
+
currentTask: cached.current_task,
|
|
735
|
+
theme: cached.theme,
|
|
736
|
+
computedAt: cached.computed_at,
|
|
737
|
+
patchedAt: cached.patched_at,
|
|
738
|
+
messageCountAtPatch: cached.message_count,
|
|
739
|
+
purpose: cached.purpose,
|
|
740
|
+
// V9 accumulative fields (actively used)
|
|
741
|
+
progressItems: cached.progress_items,
|
|
742
|
+
notableEvents: cached.notable_events,
|
|
743
|
+
recentActionsV2: cached.recent_actions_v2,
|
|
744
|
+
progressSummary: cached.progress_summary,
|
|
745
|
+
// === Deprecated fields (kept for backward compat) ===
|
|
746
|
+
// These may have data from older sessions but are no longer generated
|
|
747
|
+
currentState: null,
|
|
748
|
+
lastPatchTraceId: cached.last_patch_trace_id,
|
|
749
|
+
panels: [],
|
|
750
|
+
milestones: (cached.milestones || []),
|
|
751
|
+
notable: (cached.notable || []),
|
|
752
|
+
recentActions: cached.recent_actions || [],
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Get cached insights for multiple sessions (for list view)
|
|
757
|
+
*/
|
|
758
|
+
async getCachedInsightsForSessions(sessionIds) {
|
|
759
|
+
const allCached = await this.sessionInfoService.getAllInsights();
|
|
760
|
+
const result = new Map();
|
|
761
|
+
for (const id of sessionIds) {
|
|
762
|
+
const cached = allCached.get(id);
|
|
763
|
+
if (cached) {
|
|
764
|
+
result.set(id, this.cachedToSessionInsights(cached));
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return result;
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Compute and cache insights for sessions that don't have cached insights
|
|
771
|
+
*/
|
|
772
|
+
async computeMissingInsights(sessionIds, maxConcurrent = 3) {
|
|
773
|
+
const missing = await this.sessionInfoService.getMissingInsightsSessionIds(sessionIds);
|
|
774
|
+
if (missing.length === 0) {
|
|
775
|
+
return 0;
|
|
776
|
+
}
|
|
777
|
+
this.logger.info('Computing missing insights', { count: missing.length });
|
|
778
|
+
// Process in batches to avoid overwhelming the system
|
|
779
|
+
let computed = 0;
|
|
780
|
+
for (let i = 0; i < missing.length; i += maxConcurrent) {
|
|
781
|
+
const batch = missing.slice(i, i + maxConcurrent);
|
|
782
|
+
await Promise.all(batch.map(async (sessionId) => {
|
|
783
|
+
try {
|
|
784
|
+
const insights = await this.computeInsights(sessionId);
|
|
785
|
+
await this.cacheInsights(sessionId, insights);
|
|
786
|
+
computed++;
|
|
787
|
+
}
|
|
788
|
+
catch (error) {
|
|
789
|
+
this.logger.debug('Failed to compute insights for session', { sessionId, error });
|
|
790
|
+
}
|
|
791
|
+
}));
|
|
792
|
+
}
|
|
793
|
+
return computed;
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Recompute insights for stale sessions (force refresh)
|
|
797
|
+
* Preserves accumulated milestones/notable from existing cache if new result is empty
|
|
798
|
+
*/
|
|
799
|
+
async recomputeStaleInsights(sessionIds, maxConcurrent = 3) {
|
|
800
|
+
if (sessionIds.length === 0) {
|
|
801
|
+
return 0;
|
|
802
|
+
}
|
|
803
|
+
this.logger.info('Recomputing stale insights', { count: sessionIds.length });
|
|
804
|
+
// Process in batches to avoid overwhelming the system
|
|
805
|
+
let computed = 0;
|
|
806
|
+
for (let i = 0; i < sessionIds.length; i += maxConcurrent) {
|
|
807
|
+
const batch = sessionIds.slice(i, i + maxConcurrent);
|
|
808
|
+
await Promise.all(batch.map(async (sessionId) => {
|
|
809
|
+
const traceId = generateTraceId(sessionId);
|
|
810
|
+
const startTime = Date.now();
|
|
811
|
+
try {
|
|
812
|
+
// Get existing cache BEFORE regenerating - we may need to preserve accumulated state
|
|
813
|
+
const existingCache = await this.sessionInfoService.getInsights(sessionId);
|
|
814
|
+
const beforeState = buildStateSnapshot(existingCache);
|
|
815
|
+
this.logger.info('Computing insights for session', { traceId, sessionId });
|
|
816
|
+
const insights = await this.computeInsights(sessionId);
|
|
817
|
+
// Preserve accumulated milestones/notable if new result came back empty
|
|
818
|
+
// This handles cases where the LLM regeneration doesn't capture the full history
|
|
819
|
+
// Note: These V8 fields are deprecated and always [] for new sessions,
|
|
820
|
+
// but old cached data may have values we want to preserve during recompute.
|
|
821
|
+
if (existingCache) {
|
|
822
|
+
const existingMilestones = existingCache.milestones || [];
|
|
823
|
+
const existingNotable = existingCache.notable || [];
|
|
824
|
+
// If new milestones are empty but we had some before, preserve them
|
|
825
|
+
if ((insights.milestones || []).length === 0 && existingMilestones.length > 0) {
|
|
826
|
+
this.logger.info('Preserving accumulated milestones from cache', {
|
|
827
|
+
traceId,
|
|
828
|
+
sessionId,
|
|
829
|
+
preservedCount: existingMilestones.length
|
|
830
|
+
});
|
|
831
|
+
insights.milestones = existingMilestones;
|
|
832
|
+
}
|
|
833
|
+
// If new notable events are empty but we had some before, preserve them
|
|
834
|
+
if ((insights.notable || []).length === 0 && existingNotable.length > 0) {
|
|
835
|
+
this.logger.info('Preserving accumulated notable events from cache', {
|
|
836
|
+
traceId,
|
|
837
|
+
sessionId,
|
|
838
|
+
preservedCount: existingNotable.length
|
|
839
|
+
});
|
|
840
|
+
insights.notable = existingNotable;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
await this.cacheInsights(sessionId, insights);
|
|
844
|
+
computed++;
|
|
845
|
+
// Build after state and determine what changed
|
|
846
|
+
const afterState = buildStateSnapshot(null, insights);
|
|
847
|
+
const changedFields = [];
|
|
848
|
+
if (beforeState.mission !== afterState.mission)
|
|
849
|
+
changedFields.push('mission');
|
|
850
|
+
if (beforeState.description !== afterState.description)
|
|
851
|
+
changedFields.push('description');
|
|
852
|
+
if (beforeState.theme !== afterState.theme)
|
|
853
|
+
changedFields.push('theme');
|
|
854
|
+
if (JSON.stringify(beforeState.milestones) !== JSON.stringify(afterState.milestones))
|
|
855
|
+
changedFields.push('milestones');
|
|
856
|
+
const durationMs = Date.now() - startTime;
|
|
857
|
+
// Audit the recompute with full before/after state
|
|
858
|
+
this.sessionInfoService.auditEventInsight({
|
|
859
|
+
traceId,
|
|
860
|
+
sessionId,
|
|
861
|
+
eventType: 'recompute',
|
|
862
|
+
trigger: 'stale_refresh',
|
|
863
|
+
beforeState,
|
|
864
|
+
afterState,
|
|
865
|
+
patchedFields: changedFields,
|
|
866
|
+
durationMs,
|
|
867
|
+
llmResponse: `Opus returned: context=${!!insights.context}, description=${!!insights.description}, milestones=${(insights.milestones || []).length}`,
|
|
868
|
+
});
|
|
869
|
+
this.logger.info('Insights computed successfully', {
|
|
870
|
+
traceId,
|
|
871
|
+
sessionId,
|
|
872
|
+
milestones: (insights.milestones || []).length,
|
|
873
|
+
notable: (insights.notable || []).length,
|
|
874
|
+
changedFields,
|
|
875
|
+
durationMs,
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
catch (error) {
|
|
879
|
+
this.logger.warn('Failed to compute insights for session', { traceId, sessionId, error });
|
|
880
|
+
}
|
|
881
|
+
}));
|
|
882
|
+
}
|
|
883
|
+
return computed;
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Get insights without AI generation (faster, for list views)
|
|
887
|
+
*/
|
|
888
|
+
async getInsightsQuick(sessionId) {
|
|
889
|
+
// Check cache first - return cached AI insights if available
|
|
890
|
+
const cached = await this.sessionInfoService.getInsights(sessionId);
|
|
891
|
+
if (cached) {
|
|
892
|
+
return this.cachedToSessionInsights(cached);
|
|
893
|
+
}
|
|
894
|
+
// No cached insights - fall back to local extraction (no AI call)
|
|
895
|
+
const { messages } = await this.historyReader.fetchConversationDirect(sessionId);
|
|
896
|
+
const todos = this.extractTodoState(messages);
|
|
897
|
+
const progress = todos ? this.computeProgress(todos) : null;
|
|
898
|
+
const recentlyCompleted = todos
|
|
899
|
+
? todos
|
|
900
|
+
.filter(t => t.status === 'completed')
|
|
901
|
+
.slice(-3)
|
|
902
|
+
.map(t => t.content)
|
|
903
|
+
: [];
|
|
904
|
+
const outstanding = todos
|
|
905
|
+
? todos
|
|
906
|
+
.filter(t => t.status === 'pending' || t.status === 'in_progress')
|
|
907
|
+
.map(t => t.content)
|
|
908
|
+
: [];
|
|
909
|
+
const currentTask = todos
|
|
910
|
+
? todos.find(t => t.status === 'in_progress')?.content || null
|
|
911
|
+
: null;
|
|
912
|
+
// No theme in quick mode - will be set on user message
|
|
913
|
+
return {
|
|
914
|
+
sessionId,
|
|
915
|
+
context: null,
|
|
916
|
+
tags: null,
|
|
917
|
+
purposes: [],
|
|
918
|
+
description: null, // Skip AI generation for quick mode
|
|
919
|
+
progress,
|
|
920
|
+
recentlyCompleted,
|
|
921
|
+
outstanding,
|
|
922
|
+
currentTask,
|
|
923
|
+
theme: null,
|
|
924
|
+
// Deprecated fields (empty defaults)
|
|
925
|
+
currentState: null,
|
|
926
|
+
panels: [],
|
|
927
|
+
milestones: [],
|
|
928
|
+
notable: [],
|
|
929
|
+
recentActions: [],
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
// Legacy alias for backward compatibility during migration
|
|
934
|
+
/** @deprecated Use InsightsComputer instead */
|
|
935
|
+
export const SessionInsightsService = InsightsComputer;
|
|
936
|
+
//# sourceMappingURL=insights-computer.js.map
|