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,1218 @@
|
|
|
1
|
+
import * as pty from 'node-pty';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
import { LatticeError, asClaudeSessionId, asStreamingId } from '../../types/index.js';
|
|
4
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
5
|
+
import { EventEmitter } from 'events';
|
|
6
|
+
import { existsSync, readFileSync } from 'fs';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { dirname } from 'path';
|
|
9
|
+
import os from 'os';
|
|
10
|
+
import { JsonLinesParser } from './json-lines-parser.js';
|
|
11
|
+
import { createLogger } from '../infrastructure/logger.js';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { getProcessEventLog } from './process-event-log.js';
|
|
14
|
+
/**
|
|
15
|
+
* Expand tilde (~) in paths to the user's home directory.
|
|
16
|
+
* Node.js spawn doesn't expand ~ like shells do, causing ENOENT errors.
|
|
17
|
+
*/
|
|
18
|
+
function expandTilde(filePath) {
|
|
19
|
+
if (filePath === '~' || filePath.startsWith('~/')) {
|
|
20
|
+
return path.join(os.homedir(), filePath.slice(1));
|
|
21
|
+
}
|
|
22
|
+
return filePath;
|
|
23
|
+
}
|
|
24
|
+
// Get the directory of this module
|
|
25
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
26
|
+
const __dirname = dirname(__filename);
|
|
27
|
+
/**
|
|
28
|
+
* Manages Claude CLI processes and their lifecycle
|
|
29
|
+
*/
|
|
30
|
+
export class ClaudeProcessManager extends EventEmitter {
|
|
31
|
+
processes = new Map();
|
|
32
|
+
outputBuffers = new Map();
|
|
33
|
+
timeouts = new Map();
|
|
34
|
+
conversationConfigs = new Map();
|
|
35
|
+
sessionIds = new Map(); // streamingId -> claude session_id
|
|
36
|
+
claudeExecutablePath;
|
|
37
|
+
logger;
|
|
38
|
+
envOverrides;
|
|
39
|
+
historyReader;
|
|
40
|
+
mcpConfigPath;
|
|
41
|
+
statusTracker;
|
|
42
|
+
conversationStatusManager;
|
|
43
|
+
toolMetricsService;
|
|
44
|
+
sessionInfoService;
|
|
45
|
+
fileSystemService;
|
|
46
|
+
notificationService;
|
|
47
|
+
routerService;
|
|
48
|
+
killedProcesses = new Set(); // Track which processes we've killed
|
|
49
|
+
processEventLog;
|
|
50
|
+
constructor(historyReader, statusTracker, claudeExecutablePath, envOverrides, toolMetricsService, sessionInfoService, fileSystemService) {
|
|
51
|
+
super();
|
|
52
|
+
this.historyReader = historyReader;
|
|
53
|
+
this.statusTracker = statusTracker;
|
|
54
|
+
this.claudeExecutablePath = claudeExecutablePath || this.findClaudeExecutable();
|
|
55
|
+
this.logger = createLogger('ClaudeProcessManager');
|
|
56
|
+
this.envOverrides = envOverrides || {};
|
|
57
|
+
this.toolMetricsService = toolMetricsService;
|
|
58
|
+
this.sessionInfoService = sessionInfoService;
|
|
59
|
+
this.fileSystemService = fileSystemService;
|
|
60
|
+
this.processEventLog = getProcessEventLog();
|
|
61
|
+
}
|
|
62
|
+
setRouterService(service) {
|
|
63
|
+
this.routerService = service;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Find the Claude executable - check user-local and system paths
|
|
67
|
+
*/
|
|
68
|
+
findClaudeExecutable() {
|
|
69
|
+
// Check common installation paths including user-local installs
|
|
70
|
+
const userLocalPath = path.join(os.homedir(), '.local', 'bin', 'claude');
|
|
71
|
+
const candidatePaths = [
|
|
72
|
+
userLocalPath, // ~/.local/bin/claude (common for user installs)
|
|
73
|
+
'/usr/local/bin/claude',
|
|
74
|
+
'/usr/bin/claude',
|
|
75
|
+
];
|
|
76
|
+
for (const candidate of candidatePaths) {
|
|
77
|
+
if (existsSync(candidate)) {
|
|
78
|
+
return candidate;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Fallback: try PATH, excluding node_modules
|
|
82
|
+
const pathEnv = process.env.PATH || '';
|
|
83
|
+
const pathDirs = pathEnv.split(path.delimiter);
|
|
84
|
+
for (const dir of pathDirs) {
|
|
85
|
+
if (dir.includes('node_modules'))
|
|
86
|
+
continue; // Skip node_modules
|
|
87
|
+
const candidate = path.join(dir, 'claude');
|
|
88
|
+
if (existsSync(candidate)) {
|
|
89
|
+
return candidate;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
throw new Error('Claude CLI not found.\n\n' +
|
|
93
|
+
'Claudia Orchestrator requires Claude Code CLI to be installed.\n' +
|
|
94
|
+
'Install it with: npm install -g @anthropic-ai/claude-code\n' +
|
|
95
|
+
'Then authenticate with: claude\n\n' +
|
|
96
|
+
'More info: https://docs.anthropic.com/en/docs/claude-code');
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Set the MCP config path to be used for all conversations
|
|
100
|
+
*/
|
|
101
|
+
setMCPConfigPath(path) {
|
|
102
|
+
this.mcpConfigPath = path;
|
|
103
|
+
this.logger.debug('MCP config path set', { path });
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Set the optimistic conversation service
|
|
107
|
+
*/
|
|
108
|
+
setConversationStatusManager(service) {
|
|
109
|
+
this.conversationStatusManager = service;
|
|
110
|
+
this.logger.debug('Conversation status manager set');
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Set the notification service
|
|
114
|
+
*/
|
|
115
|
+
setNotificationService(service) {
|
|
116
|
+
this.notificationService = service;
|
|
117
|
+
this.logger.debug('Notification service set');
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Start a new Claude conversation (or resume if resumedSessionId is provided)
|
|
121
|
+
*/
|
|
122
|
+
async startConversation(config) {
|
|
123
|
+
const isResume = !!config.resumedSessionId;
|
|
124
|
+
// Guard against duplicate spawns for the same session
|
|
125
|
+
if (isResume && config.resumedSessionId) {
|
|
126
|
+
const resumeSessionId = asClaudeSessionId(config.resumedSessionId);
|
|
127
|
+
if (this.statusTracker.isSessionActive(resumeSessionId)) {
|
|
128
|
+
this.logger.warn('Attempted to resume already-active session', {
|
|
129
|
+
sessionId: config.resumedSessionId
|
|
130
|
+
});
|
|
131
|
+
throw new LatticeError('SESSION_ALREADY_ACTIVE', `Cannot resume session ${config.resumedSessionId}: already has an active Claude process`, 409 // Conflict
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
this.logger.debug('Start conversation requested', {
|
|
136
|
+
hasInitialPrompt: !!config.initialPrompt,
|
|
137
|
+
promptLength: config.initialPrompt?.length,
|
|
138
|
+
workingDirectory: config.workingDirectory,
|
|
139
|
+
model: config.model,
|
|
140
|
+
allowedTools: config.allowedTools,
|
|
141
|
+
disallowedTools: config.disallowedTools,
|
|
142
|
+
hasSystemPrompt: !!config.systemPrompt,
|
|
143
|
+
claudePath: config.claudeExecutablePath || this.claudeExecutablePath,
|
|
144
|
+
isResume,
|
|
145
|
+
resumedSessionId: config.resumedSessionId,
|
|
146
|
+
previousMessageCount: config.previousMessages?.length || 0
|
|
147
|
+
});
|
|
148
|
+
// If resuming and no working directory provided, fetch from original session
|
|
149
|
+
let workingDirectory = config.workingDirectory;
|
|
150
|
+
if (isResume && !workingDirectory && config.resumedSessionId) {
|
|
151
|
+
const fetchedWorkingDirectory = await this.historyReader.getConversationWorkingDirectory(config.resumedSessionId);
|
|
152
|
+
if (!fetchedWorkingDirectory) {
|
|
153
|
+
throw new LatticeError('CONVERSATION_NOT_FOUND', `Could not find working directory for session ${config.resumedSessionId}`, 404);
|
|
154
|
+
}
|
|
155
|
+
workingDirectory = fetchedWorkingDirectory;
|
|
156
|
+
this.logger.debug('Found working directory for resume session', {
|
|
157
|
+
sessionId: config.resumedSessionId,
|
|
158
|
+
workingDirectory
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
const args = isResume && config.resumedSessionId
|
|
162
|
+
? this.buildResumeArgs({
|
|
163
|
+
sessionId: config.resumedSessionId,
|
|
164
|
+
message: config.initialPrompt,
|
|
165
|
+
permissionMode: config.permissionMode,
|
|
166
|
+
hasMultimodalContent: !!(config.initialContent && config.initialContent.length > 0)
|
|
167
|
+
})
|
|
168
|
+
: this.buildStartArgs(config);
|
|
169
|
+
const spawnConfig = {
|
|
170
|
+
executablePath: config.claudeExecutablePath || this.claudeExecutablePath,
|
|
171
|
+
cwd: workingDirectory || config.workingDirectory || process.cwd(),
|
|
172
|
+
env: { ...process.env, ...this.envOverrides }
|
|
173
|
+
};
|
|
174
|
+
this.logger.debug('Spawn config prepared', {
|
|
175
|
+
executablePath: spawnConfig.executablePath,
|
|
176
|
+
cwd: spawnConfig.cwd,
|
|
177
|
+
hasEnvOverrides: Object.keys(this.envOverrides).length > 0,
|
|
178
|
+
envOverrideKeys: Object.keys(this.envOverrides),
|
|
179
|
+
isResume
|
|
180
|
+
});
|
|
181
|
+
return this.executeConversationFlow(isResume ? 'resuming' : 'starting', isResume && config.resumedSessionId ? { resumeSessionId: config.resumedSessionId } : {}, config, args, spawnConfig, isResume ? 'PROCESS_RESUME_FAILED' : 'PROCESS_START_FAILED', isResume ? 'Failed to resume Claude process' : 'Failed to start Claude process');
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Stop a conversation gracefully (SIGTERM, then SIGKILL after 3s)
|
|
185
|
+
*/
|
|
186
|
+
async stopConversation(streamingId) {
|
|
187
|
+
this.logger.debug('Stopping conversation', { streamingId });
|
|
188
|
+
const process = this.processes.get(streamingId);
|
|
189
|
+
if (!process) {
|
|
190
|
+
this.logger.warn('No process found for conversation', { streamingId });
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
// Force kill if not already killed
|
|
195
|
+
if (!this.killedProcesses.has(streamingId)) {
|
|
196
|
+
this.logger.info('Sending SIGTERM to process', { streamingId, pid: process.pid });
|
|
197
|
+
// Log stop requested
|
|
198
|
+
this.processEventLog.stopRequested({
|
|
199
|
+
traceId: streamingId,
|
|
200
|
+
streamingId,
|
|
201
|
+
pid: process.pid,
|
|
202
|
+
});
|
|
203
|
+
process.kill('SIGTERM');
|
|
204
|
+
this.killedProcesses.add(streamingId);
|
|
205
|
+
// If SIGTERM doesn't work, use SIGKILL after 3 seconds
|
|
206
|
+
const killTimeout = setTimeout(() => {
|
|
207
|
+
try {
|
|
208
|
+
if (this.processes.has(streamingId)) {
|
|
209
|
+
this.logger.warn('Process not responding to SIGTERM, sending SIGKILL', { streamingId, pid: process.pid });
|
|
210
|
+
// Log force kill due to SIGTERM timeout
|
|
211
|
+
this.processEventLog.forceKill({
|
|
212
|
+
traceId: streamingId,
|
|
213
|
+
streamingId,
|
|
214
|
+
pid: process.pid,
|
|
215
|
+
reason: 'SIGTERM timeout',
|
|
216
|
+
});
|
|
217
|
+
process.kill('SIGKILL');
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// Process may have already exited, ignore
|
|
222
|
+
}
|
|
223
|
+
}, 3000);
|
|
224
|
+
// Track timeout for cleanup on process exit
|
|
225
|
+
const sessionTimeouts = this.timeouts.get(streamingId) || [];
|
|
226
|
+
sessionTimeouts.push(killTimeout);
|
|
227
|
+
this.timeouts.set(streamingId, sessionTimeouts);
|
|
228
|
+
}
|
|
229
|
+
// Note: Cleanup happens in handleProcessClose when process actually exits
|
|
230
|
+
// Don't clean up here - the process may still be running
|
|
231
|
+
this.logger.info('Stop signal sent to process', { streamingId });
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
this.logger.error('Error stopping conversation', error, { streamingId });
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Interrupt a conversation (SIGINT - like Ctrl-C)
|
|
241
|
+
* This allows the process to handle the interrupt gracefully
|
|
242
|
+
*/
|
|
243
|
+
interruptConversation(streamingId) {
|
|
244
|
+
this.logger.info('Interrupting conversation', { streamingId });
|
|
245
|
+
const process = this.processes.get(streamingId);
|
|
246
|
+
if (!process) {
|
|
247
|
+
this.logger.warn('No process found for interrupt', { streamingId });
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
this.logger.info('Sending SIGINT to process', { streamingId, pid: process.pid });
|
|
252
|
+
process.kill('SIGINT');
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
this.logger.error('Error interrupting conversation', error, { streamingId });
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Force kill a conversation immediately (SIGKILL)
|
|
262
|
+
*/
|
|
263
|
+
forceKillConversation(streamingId) {
|
|
264
|
+
this.logger.info('Force killing conversation', { streamingId });
|
|
265
|
+
const process = this.processes.get(streamingId);
|
|
266
|
+
if (!process) {
|
|
267
|
+
this.logger.warn('No process found for force kill', { streamingId });
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
this.logger.warn('Sending SIGKILL to process', { streamingId, pid: process.pid });
|
|
272
|
+
// Log force kill
|
|
273
|
+
this.processEventLog.forceKill({
|
|
274
|
+
traceId: streamingId,
|
|
275
|
+
streamingId,
|
|
276
|
+
pid: process.pid,
|
|
277
|
+
reason: 'user requested',
|
|
278
|
+
});
|
|
279
|
+
process.kill('SIGKILL');
|
|
280
|
+
this.killedProcesses.add(streamingId);
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
this.logger.error('Error force killing conversation', error, { streamingId });
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Get active sessions with their session IDs
|
|
290
|
+
*/
|
|
291
|
+
getActiveSessions() {
|
|
292
|
+
const sessions = Array.from(this.processes.keys())
|
|
293
|
+
.map(streamingId => {
|
|
294
|
+
const sessionId = this.sessionIds.get(streamingId);
|
|
295
|
+
return sessionId ? { streamingId, sessionId } : null;
|
|
296
|
+
})
|
|
297
|
+
.filter((s) => s !== null);
|
|
298
|
+
this.logger.debug('Getting active sessions', { sessionCount: sessions.length });
|
|
299
|
+
return sessions;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Check if a session is active
|
|
303
|
+
*/
|
|
304
|
+
isSessionActive(streamingId) {
|
|
305
|
+
const active = this.processes.has(streamingId);
|
|
306
|
+
return active;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Get the Claude session ID for a streaming ID
|
|
310
|
+
* This mapping persists for the lifetime of the process
|
|
311
|
+
*/
|
|
312
|
+
getClaudeSessionId(streamingId) {
|
|
313
|
+
return this.sessionIds.get(streamingId);
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Wait for the system init message from Claude CLI
|
|
317
|
+
* This should always be the first message in the stream
|
|
318
|
+
*/
|
|
319
|
+
async waitForSystemInit(streamingId) {
|
|
320
|
+
this.logger.debug('Waiting for system init message', { streamingId });
|
|
321
|
+
return new Promise((resolve, reject) => {
|
|
322
|
+
let isResolved = false;
|
|
323
|
+
let stderrOutput = '';
|
|
324
|
+
// Set up timeout (1 minute)
|
|
325
|
+
const timeout = setTimeout(() => {
|
|
326
|
+
if (!isResolved) {
|
|
327
|
+
isResolved = true;
|
|
328
|
+
cleanup();
|
|
329
|
+
this.logger.error('Timeout waiting for system init message', {
|
|
330
|
+
streamingId,
|
|
331
|
+
stderrOutput: stderrOutput || '(no stderr output)'
|
|
332
|
+
});
|
|
333
|
+
// Include stderr output in error message if available
|
|
334
|
+
let errorMessage = 'Timeout waiting for system initialization from Claude CLI';
|
|
335
|
+
if (stderrOutput) {
|
|
336
|
+
errorMessage += `. Error output: ${stderrOutput}`;
|
|
337
|
+
}
|
|
338
|
+
reject(new LatticeError('SYSTEM_INIT_TIMEOUT', errorMessage, 500));
|
|
339
|
+
}
|
|
340
|
+
}, 60000);
|
|
341
|
+
// Cleanup function to remove all listeners
|
|
342
|
+
const cleanup = () => {
|
|
343
|
+
clearTimeout(timeout);
|
|
344
|
+
this.removeListener('claude-message', messageHandler);
|
|
345
|
+
this.removeListener('process-closed', processClosedHandler);
|
|
346
|
+
this.removeListener('process-error', processErrorHandler);
|
|
347
|
+
};
|
|
348
|
+
// Register timeout for cleanup on process termination
|
|
349
|
+
const existingTimeouts = this.timeouts.get(streamingId) || [];
|
|
350
|
+
existingTimeouts.push(timeout);
|
|
351
|
+
this.timeouts.set(streamingId, existingTimeouts);
|
|
352
|
+
// Listen for process exit before system init is received
|
|
353
|
+
const processClosedHandler = ({ streamingId: closedStreamingId, code }) => {
|
|
354
|
+
if (closedStreamingId !== streamingId || isResolved) {
|
|
355
|
+
return; // Not our process or already resolved
|
|
356
|
+
}
|
|
357
|
+
isResolved = true;
|
|
358
|
+
cleanup();
|
|
359
|
+
this.logger.error('Claude process exited before system init message', {
|
|
360
|
+
streamingId,
|
|
361
|
+
exitCode: code,
|
|
362
|
+
stderrOutput: stderrOutput || '(no stderr output)'
|
|
363
|
+
});
|
|
364
|
+
// Create error message with Claude CLI output if available
|
|
365
|
+
let errorMessage = 'Claude CLI process exited before sending system initialization message';
|
|
366
|
+
if (stderrOutput) {
|
|
367
|
+
// Extract Claude CLI's actual output from parser errors
|
|
368
|
+
const claudeOutputMatch = stderrOutput.match(/Invalid JSON: (.+)/);
|
|
369
|
+
if (claudeOutputMatch) {
|
|
370
|
+
errorMessage += `. Claude CLI said: "${claudeOutputMatch[1]}"`;
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
errorMessage += `. Error output: ${stderrOutput}`;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (code !== null) {
|
|
377
|
+
errorMessage += `. Exit code: ${code}`;
|
|
378
|
+
}
|
|
379
|
+
reject(new LatticeError('CLAUDE_PROCESS_EXITED_EARLY', errorMessage, 500));
|
|
380
|
+
};
|
|
381
|
+
// Listen for process errors (including stderr output)
|
|
382
|
+
const processErrorHandler = ({ streamingId: errorStreamingId, error }) => {
|
|
383
|
+
if (errorStreamingId !== streamingId) {
|
|
384
|
+
return; // Not our process
|
|
385
|
+
}
|
|
386
|
+
// Capture stderr output for error context
|
|
387
|
+
stderrOutput += error;
|
|
388
|
+
this.logger.debug('Captured stderr output during system init wait', {
|
|
389
|
+
streamingId,
|
|
390
|
+
errorLength: error.length,
|
|
391
|
+
totalStderrLength: stderrOutput.length
|
|
392
|
+
});
|
|
393
|
+
};
|
|
394
|
+
// Listen for the first claude-message event for this streamingId
|
|
395
|
+
const messageHandler = ({ streamingId: msgStreamingId, message }) => {
|
|
396
|
+
if (msgStreamingId !== streamingId) {
|
|
397
|
+
return; // Not for our session
|
|
398
|
+
}
|
|
399
|
+
if (isResolved) {
|
|
400
|
+
return; // Already resolved
|
|
401
|
+
}
|
|
402
|
+
this.logger.debug('Received message from Claude CLI during init wait', {
|
|
403
|
+
streamingId,
|
|
404
|
+
messageType: message?.type,
|
|
405
|
+
messageSubtype: 'subtype' in message ? message.subtype : undefined,
|
|
406
|
+
hasSessionId: 'session_id' in message ? !!message.session_id : false
|
|
407
|
+
});
|
|
408
|
+
// Skip hook_response messages (Claude v2 sends these before init)
|
|
409
|
+
// Cast to unknown string to compare - StreamEvent type doesn't include all system subtypes
|
|
410
|
+
if (message?.type === 'system' && 'subtype' in message && message.subtype === 'hook_response') {
|
|
411
|
+
this.logger.debug('Skipping hook_response message, waiting for init', { streamingId });
|
|
412
|
+
return; // Keep waiting for init message
|
|
413
|
+
}
|
|
414
|
+
// Now we have a non-hook message, mark as resolved
|
|
415
|
+
isResolved = true;
|
|
416
|
+
cleanup();
|
|
417
|
+
// Validate that this message is a system init message
|
|
418
|
+
if (!message || message.type !== 'system' || !('subtype' in message) || message.subtype !== 'init') {
|
|
419
|
+
this.logger.error('Expected system init message', {
|
|
420
|
+
streamingId,
|
|
421
|
+
actualType: message?.type,
|
|
422
|
+
actualSubtype: 'subtype' in message ? message.subtype : undefined,
|
|
423
|
+
expectedType: 'system',
|
|
424
|
+
expectedSubtype: 'init'
|
|
425
|
+
});
|
|
426
|
+
reject(new LatticeError('INVALID_SYSTEM_INIT', `Expected system init message, but got: ${message?.type}/${'subtype' in message ? message.subtype : 'undefined'}`, 500));
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
// At this point, TypeScript knows message is SystemInitMessage
|
|
430
|
+
const systemInitMessage = message;
|
|
431
|
+
// Validate required fields
|
|
432
|
+
const requiredFields = ['session_id', 'cwd', 'tools', 'mcp_servers', 'model', 'permissionMode', 'apiKeySource'];
|
|
433
|
+
const missingFields = requiredFields.filter(field => systemInitMessage[field] === undefined);
|
|
434
|
+
if (missingFields.length > 0) {
|
|
435
|
+
this.logger.error('System init message missing required fields', {
|
|
436
|
+
streamingId,
|
|
437
|
+
missingFields,
|
|
438
|
+
availableFields: Object.keys(systemInitMessage)
|
|
439
|
+
});
|
|
440
|
+
reject(new LatticeError('INCOMPLETE_SYSTEM_INIT', `System init message missing required fields: ${missingFields.join(', ')}`, 500));
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
this.logger.debug('Successfully received valid system init message', {
|
|
444
|
+
streamingId,
|
|
445
|
+
sessionId: systemInitMessage.session_id,
|
|
446
|
+
cwd: systemInitMessage.cwd,
|
|
447
|
+
model: systemInitMessage.model,
|
|
448
|
+
toolCount: systemInitMessage.tools?.length || 0,
|
|
449
|
+
mcpServerCount: systemInitMessage.mcp_servers?.length || 0
|
|
450
|
+
});
|
|
451
|
+
// Log system init to process event log
|
|
452
|
+
this.processEventLog.systemInit({
|
|
453
|
+
traceId: streamingId,
|
|
454
|
+
streamingId,
|
|
455
|
+
sessionId: systemInitMessage.session_id,
|
|
456
|
+
model: systemInitMessage.model,
|
|
457
|
+
workingDirectory: systemInitMessage.cwd,
|
|
458
|
+
toolCount: systemInitMessage.tools?.length || 0,
|
|
459
|
+
mcpServerCount: systemInitMessage.mcp_servers?.length || 0,
|
|
460
|
+
});
|
|
461
|
+
// Register active session immediately when we have the session_id
|
|
462
|
+
// Include optimistic context if available
|
|
463
|
+
const config = this.conversationConfigs.get(streamingId);
|
|
464
|
+
if (this.conversationStatusManager && config) {
|
|
465
|
+
const optimisticContext = {
|
|
466
|
+
initialPrompt: config.initialPrompt || '',
|
|
467
|
+
workingDirectory: config.workingDirectory || process.cwd(),
|
|
468
|
+
model: config.model || 'default',
|
|
469
|
+
timestamp: new Date().toISOString(),
|
|
470
|
+
inheritedMessages: config.previousMessages
|
|
471
|
+
};
|
|
472
|
+
// Store in local sessionIds map (never cleaned up until process exits)
|
|
473
|
+
const claudeSessionId = asClaudeSessionId(systemInitMessage.session_id);
|
|
474
|
+
this.sessionIds.set(streamingId, claudeSessionId);
|
|
475
|
+
this.conversationStatusManager.registerActiveSession(streamingId, claudeSessionId, optimisticContext);
|
|
476
|
+
this.logger.debug('Registered conversation context', {
|
|
477
|
+
streamingId,
|
|
478
|
+
claudeSessionId,
|
|
479
|
+
inheritedMessageCount: config.previousMessages?.length || 0
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
// Fallback to old behavior if service not set
|
|
484
|
+
const claudeSessionId = asClaudeSessionId(systemInitMessage.session_id);
|
|
485
|
+
this.sessionIds.set(streamingId, claudeSessionId);
|
|
486
|
+
this.statusTracker.registerActiveSession(streamingId, claudeSessionId);
|
|
487
|
+
this.logger.debug('Registered active session with status tracker (no optimistic service)', {
|
|
488
|
+
streamingId,
|
|
489
|
+
claudeSessionId
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
resolve(systemInitMessage);
|
|
493
|
+
};
|
|
494
|
+
// Set up all event listeners
|
|
495
|
+
this.on('claude-message', messageHandler);
|
|
496
|
+
this.on('process-closed', processClosedHandler);
|
|
497
|
+
this.on('process-error', processErrorHandler);
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Execute common conversation flow for both start and resume operations
|
|
502
|
+
*/
|
|
503
|
+
async executeConversationFlow(operation, loggerContext, config, args, spawnConfig, errorCode, errorPrefix) {
|
|
504
|
+
const streamingId = asStreamingId(uuidv4()); // CUI's internal streaming identifier
|
|
505
|
+
const traceId = streamingId; // Use streamingId as traceId for now
|
|
506
|
+
const isResume = operation === 'resuming';
|
|
507
|
+
// Store config for use in waitForSystemInit
|
|
508
|
+
this.conversationConfigs.set(streamingId, config);
|
|
509
|
+
// Log spawn start
|
|
510
|
+
this.processEventLog.spawnStart({
|
|
511
|
+
traceId,
|
|
512
|
+
streamingId,
|
|
513
|
+
isResume,
|
|
514
|
+
workingDirectory: spawnConfig.cwd,
|
|
515
|
+
model: config.model,
|
|
516
|
+
resumedSessionId: config.resumedSessionId,
|
|
517
|
+
});
|
|
518
|
+
try {
|
|
519
|
+
// Validate Claude executable before proceeding
|
|
520
|
+
if (this.fileSystemService) {
|
|
521
|
+
await this.fileSystemService.validateExecutable(spawnConfig.executablePath);
|
|
522
|
+
}
|
|
523
|
+
this.logger.debug(`${operation.charAt(0).toUpperCase() + operation.slice(1)} conversation`, {
|
|
524
|
+
streamingId,
|
|
525
|
+
operation,
|
|
526
|
+
configKeys: Object.keys(config),
|
|
527
|
+
argCount: args.length,
|
|
528
|
+
...loggerContext
|
|
529
|
+
});
|
|
530
|
+
this.logger.debug(`Built Claude ${operation} args`, {
|
|
531
|
+
streamingId,
|
|
532
|
+
args,
|
|
533
|
+
argsString: args.join(' '),
|
|
534
|
+
...loggerContext
|
|
535
|
+
});
|
|
536
|
+
// Set up system init promise before spawning process
|
|
537
|
+
const systemInitPromise = this.waitForSystemInit(streamingId);
|
|
538
|
+
// Add streamingId to environment for MCP server to use
|
|
539
|
+
// Filter out debugging-related environment variables that would cause
|
|
540
|
+
// the VSCode debugger to attach to the Claude CLI child process
|
|
541
|
+
// Also filter CLAUDECODE to prevent nested session detection issues
|
|
542
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
543
|
+
const { NODE_OPTIONS, VSCODE_INSPECTOR_OPTIONS, CLAUDECODE, CLAUDE_CODE_ENTRYPOINT, ...cleanEnv } = spawnConfig.env;
|
|
544
|
+
// Expand tilde to actual home directory path
|
|
545
|
+
const expandedCwd = expandTilde(spawnConfig.cwd);
|
|
546
|
+
const envWithStreamingId = {
|
|
547
|
+
...cleanEnv,
|
|
548
|
+
CUI_STREAMING_ID: streamingId,
|
|
549
|
+
PWD: expandedCwd,
|
|
550
|
+
INIT_CWD: expandedCwd
|
|
551
|
+
};
|
|
552
|
+
// Use piped spawn for multimodal content (stdin works properly with pipes)
|
|
553
|
+
// Use PTY for text-only (better terminal experience)
|
|
554
|
+
const hasMultimodalContent = !!(config.initialContent && config.initialContent.length > 0);
|
|
555
|
+
const managedProcess = this.spawnProcess({ ...spawnConfig, env: envWithStreamingId }, args, streamingId, hasMultimodalContent // usePipe = true for multimodal
|
|
556
|
+
);
|
|
557
|
+
this.processes.set(streamingId, managedProcess);
|
|
558
|
+
this.setupProcessHandlers(streamingId, managedProcess);
|
|
559
|
+
// Emit process-spawned event so StreamManager can start buffering events
|
|
560
|
+
// This handles the race condition where events are broadcast before SSE client connects
|
|
561
|
+
this.emit('process-spawned', { streamingId });
|
|
562
|
+
// For multimodal content, send via stdin BEFORE waiting for system init
|
|
563
|
+
// The CLI will process this and emit the system init message
|
|
564
|
+
if (hasMultimodalContent && config.initialContent) {
|
|
565
|
+
const contentBlocks = [
|
|
566
|
+
...config.initialContent,
|
|
567
|
+
...(config.initialPrompt ? [{ type: 'text', text: config.initialPrompt }] : [])
|
|
568
|
+
];
|
|
569
|
+
const stdinMessage = JSON.stringify({
|
|
570
|
+
type: 'user',
|
|
571
|
+
message: {
|
|
572
|
+
role: 'user',
|
|
573
|
+
content: contentBlocks
|
|
574
|
+
},
|
|
575
|
+
session_id: config.resumedSessionId || ''
|
|
576
|
+
});
|
|
577
|
+
this.logger.info('Sending multimodal content via stdin (piped mode)', {
|
|
578
|
+
streamingId,
|
|
579
|
+
contentBlockCount: contentBlocks.length,
|
|
580
|
+
messageLength: stdinMessage.length
|
|
581
|
+
});
|
|
582
|
+
managedProcess.write(stdinMessage + '\n');
|
|
583
|
+
// Close stdin to signal end of input - CLI will process and respond
|
|
584
|
+
if (managedProcess.type === 'pipe') {
|
|
585
|
+
managedProcess.endStdin();
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
// Log spawn success
|
|
589
|
+
if (managedProcess.pid) {
|
|
590
|
+
this.processEventLog.spawnSuccess({
|
|
591
|
+
traceId,
|
|
592
|
+
streamingId,
|
|
593
|
+
pid: managedProcess.pid,
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
// Handle spawn errors by listening for our custom event
|
|
597
|
+
const spawnErrorPromise = new Promise((_, reject) => {
|
|
598
|
+
this.once('spawn-error', (error) => {
|
|
599
|
+
this.processes.delete(streamingId);
|
|
600
|
+
reject(error);
|
|
601
|
+
});
|
|
602
|
+
});
|
|
603
|
+
// Wait a bit to see if spawn fails immediately
|
|
604
|
+
this.logger.debug('Waiting for spawn validation', { streamingId, ...loggerContext });
|
|
605
|
+
const delayPromise = new Promise(resolve => {
|
|
606
|
+
setTimeout(() => {
|
|
607
|
+
this.logger.debug('Spawn validation period passed, process appears stable', { streamingId, ...loggerContext });
|
|
608
|
+
this.removeAllListeners('spawn-error');
|
|
609
|
+
resolve(streamingId);
|
|
610
|
+
}, 100);
|
|
611
|
+
});
|
|
612
|
+
await Promise.race([spawnErrorPromise, delayPromise]);
|
|
613
|
+
// Now wait for the system init message
|
|
614
|
+
this.logger.debug('Process spawned successfully, waiting for system init message', { streamingId, ...loggerContext });
|
|
615
|
+
const systemInit = await systemInitPromise;
|
|
616
|
+
// Check if cwd is a git repository and set initial_commit_head for new session
|
|
617
|
+
if (this.sessionInfoService && this.fileSystemService) {
|
|
618
|
+
try {
|
|
619
|
+
if (await this.fileSystemService.isGitRepository(systemInit.cwd)) {
|
|
620
|
+
const gitHead = await this.fileSystemService.getCurrentGitHead(systemInit.cwd);
|
|
621
|
+
if (gitHead) {
|
|
622
|
+
await this.sessionInfoService.updateSessionInfo(systemInit.session_id, {
|
|
623
|
+
initial_commit_head: gitHead
|
|
624
|
+
});
|
|
625
|
+
this.logger.debug('Set initial commit head for new session', {
|
|
626
|
+
sessionId: systemInit.session_id,
|
|
627
|
+
gitHead
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
catch (error) {
|
|
633
|
+
this.logger.warn('Failed to set initial commit head for new session', {
|
|
634
|
+
sessionId: systemInit.session_id,
|
|
635
|
+
cwd: systemInit.cwd,
|
|
636
|
+
error: error instanceof Error ? error.message : String(error)
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
this.logger.debug(`${operation.charAt(0).toUpperCase() + operation.slice(1)} conversation successfully`, {
|
|
641
|
+
streamingId,
|
|
642
|
+
sessionId: systemInit.session_id,
|
|
643
|
+
model: systemInit.model,
|
|
644
|
+
cwd: systemInit.cwd,
|
|
645
|
+
processCount: this.processes.size,
|
|
646
|
+
...loggerContext
|
|
647
|
+
});
|
|
648
|
+
return { streamingId, systemInit };
|
|
649
|
+
}
|
|
650
|
+
catch (error) {
|
|
651
|
+
this.logger.error(`Error ${operation} conversation`, error, {
|
|
652
|
+
streamingId,
|
|
653
|
+
errorName: error instanceof Error ? error.name : 'Unknown',
|
|
654
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
655
|
+
errorCode: error instanceof LatticeError ? error.code : undefined,
|
|
656
|
+
...loggerContext
|
|
657
|
+
});
|
|
658
|
+
// Log spawn failure
|
|
659
|
+
this.processEventLog.spawnFailed({
|
|
660
|
+
traceId,
|
|
661
|
+
streamingId,
|
|
662
|
+
errorCode: error instanceof LatticeError ? error.code : errorCode,
|
|
663
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
664
|
+
});
|
|
665
|
+
// Clean up any resources if process fails
|
|
666
|
+
const timeouts = this.timeouts.get(streamingId);
|
|
667
|
+
if (timeouts) {
|
|
668
|
+
timeouts.forEach(timeout => clearTimeout(timeout));
|
|
669
|
+
this.timeouts.delete(streamingId);
|
|
670
|
+
}
|
|
671
|
+
this.processes.delete(streamingId);
|
|
672
|
+
this.outputBuffers.delete(streamingId);
|
|
673
|
+
this.conversationConfigs.delete(streamingId);
|
|
674
|
+
if (error instanceof LatticeError) {
|
|
675
|
+
throw error;
|
|
676
|
+
}
|
|
677
|
+
throw new LatticeError(errorCode, `${errorPrefix}: ${error}`, 500);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
buildBaseArgs() {
|
|
681
|
+
return [
|
|
682
|
+
'-p', // Print mode - required for programmatic use
|
|
683
|
+
];
|
|
684
|
+
}
|
|
685
|
+
buildResumeArgs(config) {
|
|
686
|
+
this.logger.debug('Building Claude resume args', {
|
|
687
|
+
sessionId: config.sessionId,
|
|
688
|
+
messagePreview: config.message.substring(0, 50) + (config.message.length > 50 ? '...' : ''),
|
|
689
|
+
permissionMode: config.permissionMode,
|
|
690
|
+
hasMultimodalContent: config.hasMultimodalContent
|
|
691
|
+
});
|
|
692
|
+
const args = this.buildBaseArgs();
|
|
693
|
+
// Add message as CLI arg - skip if multimodal content will be sent via stdin
|
|
694
|
+
// NOTE: For very long messages, this may hit CLI arg length limits
|
|
695
|
+
if (config.message && !config.hasMultimodalContent) {
|
|
696
|
+
args.push(config.message);
|
|
697
|
+
}
|
|
698
|
+
args.push('--resume', config.sessionId, '--output-format', 'stream-json', '--input-format', 'stream-json', // Enable stdin for AskUserQuestion responses
|
|
699
|
+
'--verbose');
|
|
700
|
+
// Add MCP config if available (same as start operations)
|
|
701
|
+
// This enables the permission prompt tool for ASK mode on resume
|
|
702
|
+
if (this.mcpConfigPath) {
|
|
703
|
+
args.push('--mcp-config', this.mcpConfigPath);
|
|
704
|
+
}
|
|
705
|
+
// Apply permission mode with MCP tool support enabled
|
|
706
|
+
this.applyPermissionMode(args, config.permissionMode);
|
|
707
|
+
this.logger.debug('Built Claude resume args', { args, hasMCPConfig: !!this.mcpConfigPath });
|
|
708
|
+
return args;
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Apply permission mode flags to CLI args
|
|
712
|
+
* Modes:
|
|
713
|
+
* - "bypassPermissions" (default): Skip all permission prompts (--dangerously-skip-permissions)
|
|
714
|
+
* - "default": Use Claude's default permission behavior (prompts for approval via MCP)
|
|
715
|
+
* - "acceptEdits": Auto-accept file edits but prompt for other tools
|
|
716
|
+
* - "plan": Plan mode - research only, no edits
|
|
717
|
+
*/
|
|
718
|
+
applyPermissionMode(args, permissionMode) {
|
|
719
|
+
switch (permissionMode) {
|
|
720
|
+
case 'default':
|
|
721
|
+
// Use MCP permission prompt tool to delegate approval to the dashboard
|
|
722
|
+
// The tool must be pre-allowed so Claude can use it without prompting
|
|
723
|
+
if (this.mcpConfigPath) {
|
|
724
|
+
args.push('--permission-prompt-tool', 'mcp__claudia-permissions__permission_prompt');
|
|
725
|
+
args.push('--allowedTools', 'mcp__claudia-permissions__permission_prompt');
|
|
726
|
+
this.logger.debug('Permission mode: default (using MCP permission prompt)');
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
// No MCP config available - fall back to bypass permissions
|
|
730
|
+
args.push('--dangerously-skip-permissions');
|
|
731
|
+
this.logger.debug('Permission mode: default but no MCP config, falling back to bypass');
|
|
732
|
+
}
|
|
733
|
+
break;
|
|
734
|
+
case 'acceptEdits':
|
|
735
|
+
args.push('--permission-mode', 'acceptEdits');
|
|
736
|
+
this.logger.debug('Permission mode: acceptEdits');
|
|
737
|
+
break;
|
|
738
|
+
case 'plan':
|
|
739
|
+
args.push('--permission-mode', 'plan');
|
|
740
|
+
this.logger.debug('Permission mode: plan');
|
|
741
|
+
break;
|
|
742
|
+
case 'bypassPermissions':
|
|
743
|
+
default:
|
|
744
|
+
// Default to bypass for backwards compatibility
|
|
745
|
+
args.push('--dangerously-skip-permissions');
|
|
746
|
+
this.logger.debug('Permission mode: bypassPermissions (dangerously skip)');
|
|
747
|
+
break;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
buildStartArgs(config) {
|
|
751
|
+
this.logger.debug('Building Claude start args', {
|
|
752
|
+
hasInitialPrompt: !!config.initialPrompt,
|
|
753
|
+
hasInitialContent: !!(config.initialContent && config.initialContent.length > 0),
|
|
754
|
+
promptPreview: config.initialPrompt ? config.initialPrompt.substring(0, 50) + (config.initialPrompt.length > 50 ? '...' : '') : null,
|
|
755
|
+
workingDirectory: config.workingDirectory,
|
|
756
|
+
model: config.model
|
|
757
|
+
});
|
|
758
|
+
const args = this.buildBaseArgs();
|
|
759
|
+
// Add initial prompt immediately after -p
|
|
760
|
+
// Skip if initialContent is provided - multimodal content will be sent via stdin
|
|
761
|
+
if (config.initialPrompt && !(config.initialContent && config.initialContent.length > 0)) {
|
|
762
|
+
args.push(config.initialPrompt);
|
|
763
|
+
}
|
|
764
|
+
args.push('--output-format', 'stream-json', // JSONL output format
|
|
765
|
+
'--input-format', 'stream-json', // Enable stdin for AskUserQuestion responses
|
|
766
|
+
'--verbose' // Required when using stream-json with print mode
|
|
767
|
+
);
|
|
768
|
+
// Add working directory access
|
|
769
|
+
// if (config.workingDirectory) {
|
|
770
|
+
// args.push('--add-dir', config.workingDirectory);
|
|
771
|
+
// }
|
|
772
|
+
// Add model specification
|
|
773
|
+
if (config.model) {
|
|
774
|
+
args.push('--model', config.model);
|
|
775
|
+
}
|
|
776
|
+
// Add allowed tools
|
|
777
|
+
if (config.allowedTools && config.allowedTools.length > 0) {
|
|
778
|
+
args.push('--allowedTools', config.allowedTools.join(','));
|
|
779
|
+
}
|
|
780
|
+
// Add disallowed tools
|
|
781
|
+
if (config.disallowedTools && config.disallowedTools.length > 0) {
|
|
782
|
+
args.push('--disallowedTools', config.disallowedTools.join(','));
|
|
783
|
+
}
|
|
784
|
+
// Add system prompt with Claudia environment info
|
|
785
|
+
const claudiaEnvInfo = `
|
|
786
|
+
## Claudia Orchestrator Environment
|
|
787
|
+
|
|
788
|
+
You are running inside the Claudia orchestrator dashboard.
|
|
789
|
+
|
|
790
|
+
**Server logs** (for debugging issues):
|
|
791
|
+
- \`~/.claudia/logs/server.log\` - Express server logs
|
|
792
|
+
- \`~/.claudia/logs/daemon.log\` - Process daemon logs
|
|
793
|
+
`;
|
|
794
|
+
const systemPrompt = config.systemPrompt
|
|
795
|
+
? `${config.systemPrompt}\n${claudiaEnvInfo}`
|
|
796
|
+
: claudiaEnvInfo;
|
|
797
|
+
args.push('--system-prompt', systemPrompt);
|
|
798
|
+
// Add MCP config if available
|
|
799
|
+
if (this.mcpConfigPath) {
|
|
800
|
+
args.push('--mcp-config', this.mcpConfigPath);
|
|
801
|
+
}
|
|
802
|
+
// Apply permission mode
|
|
803
|
+
this.applyPermissionMode(args, config.permissionMode);
|
|
804
|
+
this.logger.debug('Built Claude args', { args, hasMCPConfig: !!this.mcpConfigPath, permissionMode: config.permissionMode });
|
|
805
|
+
return args;
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Consolidated method to spawn Claude processes for both start and resume operations
|
|
809
|
+
* Uses node-pty for text-only (terminal experience) or piped spawn for multimodal (stdin works properly)
|
|
810
|
+
*/
|
|
811
|
+
spawnProcess(spawnConfig, args, streamingId, usePipe = false) {
|
|
812
|
+
const { executablePath } = spawnConfig;
|
|
813
|
+
// Expand tilde to actual home directory - Node.js spawn doesn't do shell expansion
|
|
814
|
+
const cwd = expandTilde(spawnConfig.cwd);
|
|
815
|
+
let { env } = spawnConfig;
|
|
816
|
+
// Inject router proxy if enabled
|
|
817
|
+
if (this.routerService?.isEnabled()) {
|
|
818
|
+
env = {
|
|
819
|
+
...env,
|
|
820
|
+
ANTHROPIC_BASE_URL: this.routerService.getProxyUrl(),
|
|
821
|
+
ANTHROPIC_API_KEY: 'router-managed'
|
|
822
|
+
};
|
|
823
|
+
this.logger.info('Using router proxy', {
|
|
824
|
+
streamingId,
|
|
825
|
+
proxyUrl: this.routerService.getProxyUrl()
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
// Check if MCP config is in args and validate it
|
|
829
|
+
const mcpConfigIndex = args.indexOf('--mcp-config');
|
|
830
|
+
if (mcpConfigIndex !== -1 && mcpConfigIndex + 1 < args.length) {
|
|
831
|
+
const mcpConfigPath = args[mcpConfigIndex + 1];
|
|
832
|
+
this.logger.debug('MCP config specified', {
|
|
833
|
+
streamingId,
|
|
834
|
+
mcpConfigPath,
|
|
835
|
+
exists: existsSync(mcpConfigPath)
|
|
836
|
+
});
|
|
837
|
+
// Try to read and log the MCP config content
|
|
838
|
+
try {
|
|
839
|
+
const mcpConfigContent = readFileSync(mcpConfigPath, 'utf-8');
|
|
840
|
+
this.logger.debug('MCP config content', {
|
|
841
|
+
streamingId,
|
|
842
|
+
mcpConfig: JSON.parse(mcpConfigContent)
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
catch (error) {
|
|
846
|
+
this.logger.error('Failed to read MCP config', { streamingId, error });
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
const spawnMode = usePipe ? 'PIPE' : 'PTY';
|
|
850
|
+
this.logger.debug(`Spawning Claude process with ${spawnMode}`, {
|
|
851
|
+
streamingId,
|
|
852
|
+
executablePath,
|
|
853
|
+
args,
|
|
854
|
+
cwd,
|
|
855
|
+
PATH: env.PATH,
|
|
856
|
+
nodeVersion: process.version,
|
|
857
|
+
platform: process.platform
|
|
858
|
+
});
|
|
859
|
+
try {
|
|
860
|
+
// Log the exact command for debugging
|
|
861
|
+
const fullCommand = `${executablePath} ${args.join(' ')}`;
|
|
862
|
+
this.logger.info(`SPAWNING CLAUDE COMMAND (${spawnMode}): ` + fullCommand, {
|
|
863
|
+
streamingId,
|
|
864
|
+
fullCommand,
|
|
865
|
+
executablePath,
|
|
866
|
+
args,
|
|
867
|
+
cwd,
|
|
868
|
+
env: Object.entries(env).reduce((acc, [key, value]) => {
|
|
869
|
+
acc[key] = value;
|
|
870
|
+
return acc;
|
|
871
|
+
}, {})
|
|
872
|
+
});
|
|
873
|
+
if (usePipe) {
|
|
874
|
+
// Use piped spawn for multimodal - stdin will be a pipe, not a TTY
|
|
875
|
+
// This allows CLI to block waiting for stdin input
|
|
876
|
+
const childProcess = spawn(executablePath, args, {
|
|
877
|
+
cwd,
|
|
878
|
+
env: env,
|
|
879
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
880
|
+
});
|
|
881
|
+
if (!childProcess.pid) {
|
|
882
|
+
this.logger.error('Failed to spawn Claude process - no PID assigned', { streamingId });
|
|
883
|
+
throw new Error('Failed to spawn Claude process - no PID assigned');
|
|
884
|
+
}
|
|
885
|
+
this.logger.info('Claude process spawned successfully (PIPE)', {
|
|
886
|
+
streamingId,
|
|
887
|
+
pid: childProcess.pid
|
|
888
|
+
});
|
|
889
|
+
return {
|
|
890
|
+
type: 'pipe',
|
|
891
|
+
child: childProcess,
|
|
892
|
+
pid: childProcess.pid,
|
|
893
|
+
write: (data) => childProcess.stdin?.write(data),
|
|
894
|
+
kill: (signal) => childProcess.kill(signal),
|
|
895
|
+
endStdin: () => childProcess.stdin?.end(),
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
else {
|
|
899
|
+
// Use node-pty to spawn with a pseudo-terminal
|
|
900
|
+
// This is required because Claude CLI -p mode only outputs when connected to a TTY
|
|
901
|
+
const claudeProcess = pty.spawn(executablePath, args, {
|
|
902
|
+
name: 'xterm-256color',
|
|
903
|
+
cols: 200,
|
|
904
|
+
rows: 50,
|
|
905
|
+
cwd,
|
|
906
|
+
env: env
|
|
907
|
+
});
|
|
908
|
+
if (!claudeProcess.pid) {
|
|
909
|
+
this.logger.error('Failed to spawn Claude process - no PID assigned', { streamingId });
|
|
910
|
+
throw new Error('Failed to spawn Claude process - no PID assigned');
|
|
911
|
+
}
|
|
912
|
+
this.logger.info('Claude process spawned successfully (PTY)', {
|
|
913
|
+
streamingId,
|
|
914
|
+
pid: claudeProcess.pid
|
|
915
|
+
});
|
|
916
|
+
return {
|
|
917
|
+
type: 'pty',
|
|
918
|
+
pty: claudeProcess,
|
|
919
|
+
pid: claudeProcess.pid,
|
|
920
|
+
write: (data) => claudeProcess.write(data),
|
|
921
|
+
kill: (signal) => claudeProcess.kill(signal),
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
catch (error) {
|
|
926
|
+
this.logger.error('Error in spawnProcess', error, { streamingId });
|
|
927
|
+
if (error instanceof LatticeError) {
|
|
928
|
+
throw error;
|
|
929
|
+
}
|
|
930
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
931
|
+
if (errorMessage.includes('ENOENT') || errorMessage.includes('not found')) {
|
|
932
|
+
throw new LatticeError('CLAUDE_NOT_FOUND', 'Claude CLI not found. Please ensure Claude is installed and in PATH.', 500);
|
|
933
|
+
}
|
|
934
|
+
throw new LatticeError('PROCESS_SPAWN_FAILED', `Failed to spawn Claude process: ${errorMessage}`, 500);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
setupProcessHandlers(streamingId, managedProcess) {
|
|
938
|
+
this.logger.debug(`Setting up ${managedProcess.type.toUpperCase()} process handlers`, { streamingId, pid: managedProcess.pid });
|
|
939
|
+
// Create JSONL parser for Claude output
|
|
940
|
+
const parser = new JsonLinesParser();
|
|
941
|
+
// Initialize output buffer for this session
|
|
942
|
+
this.outputBuffers.set(streamingId, '');
|
|
943
|
+
// Handle parsed JSONL messages from Claude
|
|
944
|
+
parser.on('data', (message) => {
|
|
945
|
+
// Skip echoed stdin messages - PTY echoes our input back
|
|
946
|
+
// Piped processes don't echo, but we still filter non-tool-result user messages
|
|
947
|
+
// BUT: tool_result messages are also type: 'user' (Claude API protocol:
|
|
948
|
+
// user = input TO Claude, assistant = output FROM Claude)
|
|
949
|
+
// We need to let tool_result messages through so the UI can update tool status
|
|
950
|
+
if (message?.type === 'user') {
|
|
951
|
+
const content = message.message?.content;
|
|
952
|
+
const isToolResult = Array.isArray(content) &&
|
|
953
|
+
content.some((block) => block.type === 'tool_result');
|
|
954
|
+
if (!isToolResult) {
|
|
955
|
+
this.logger.debug('Skipping echoed stdin message', {
|
|
956
|
+
streamingId,
|
|
957
|
+
messageType: message?.type
|
|
958
|
+
});
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
// Fall through to emit tool_result messages
|
|
962
|
+
this.logger.debug('Allowing tool_result message through', {
|
|
963
|
+
streamingId,
|
|
964
|
+
toolResultCount: content.filter((b) => b.type === 'tool_result').length
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
this.logger.debug('Received Claude message', {
|
|
968
|
+
streamingId,
|
|
969
|
+
messageType: message?.type,
|
|
970
|
+
hasContent: !!message?.content,
|
|
971
|
+
contentLength: message?.content?.length,
|
|
972
|
+
messageKeys: message ? Object.keys(message) : [],
|
|
973
|
+
timestamp: new Date().toISOString()
|
|
974
|
+
});
|
|
975
|
+
this.handleClaudeMessage(streamingId, message);
|
|
976
|
+
});
|
|
977
|
+
parser.on('error', (error) => {
|
|
978
|
+
this.logger.error('Parser error', error, {
|
|
979
|
+
streamingId,
|
|
980
|
+
errorType: error.name,
|
|
981
|
+
errorMessage: error.message,
|
|
982
|
+
bufferState: this.outputBuffers.get(streamingId)?.length || 0
|
|
983
|
+
});
|
|
984
|
+
this.handleProcessError(streamingId, error);
|
|
985
|
+
});
|
|
986
|
+
if (managedProcess.type === 'pty') {
|
|
987
|
+
// Handle PTY data output (combined stdout/stderr from the pseudo-terminal)
|
|
988
|
+
// PTY combines both streams, so we need to parse JSONL from the raw output
|
|
989
|
+
managedProcess.pty.onData((data) => {
|
|
990
|
+
// Log raw PTY data for debugging (debug level - very verbose)
|
|
991
|
+
this.logger.debug('RAW PTY DATA', {
|
|
992
|
+
streamingId,
|
|
993
|
+
dataLength: data.length,
|
|
994
|
+
dataPreview: data.substring(0, 200)
|
|
995
|
+
});
|
|
996
|
+
// Strip ANSI escape sequences that might interfere with JSON parsing
|
|
997
|
+
// Common PTY escape sequences: cursor control, colors, etc.
|
|
998
|
+
const cleanedData = data.replace(/\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07/g, '');
|
|
999
|
+
// Feed cleaned data to the JSONL parser
|
|
1000
|
+
if (cleanedData.trim()) {
|
|
1001
|
+
parser.write(cleanedData);
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
1004
|
+
// Handle PTY process exit (node-pty uses onExit, not EventEmitter 'exit')
|
|
1005
|
+
managedProcess.pty.onExit(({ exitCode }) => {
|
|
1006
|
+
this.logger.debug('PTY process exited', {
|
|
1007
|
+
streamingId,
|
|
1008
|
+
exitCode,
|
|
1009
|
+
normalExit: exitCode === 0,
|
|
1010
|
+
timestamp: new Date().toISOString(),
|
|
1011
|
+
outputBuffer: this.outputBuffers.get(streamingId) || 'No output captured'
|
|
1012
|
+
});
|
|
1013
|
+
this.handleProcessClose(streamingId, exitCode);
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
else {
|
|
1017
|
+
// Handle piped child process stdout
|
|
1018
|
+
managedProcess.child.stdout?.on('data', (data) => {
|
|
1019
|
+
const str = data.toString();
|
|
1020
|
+
this.logger.debug('RAW PIPE STDOUT', {
|
|
1021
|
+
streamingId,
|
|
1022
|
+
dataLength: str.length,
|
|
1023
|
+
dataPreview: str.substring(0, 200)
|
|
1024
|
+
});
|
|
1025
|
+
if (str.trim()) {
|
|
1026
|
+
parser.write(str);
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
1029
|
+
// Handle piped child process stderr
|
|
1030
|
+
managedProcess.child.stderr?.on('data', (data) => {
|
|
1031
|
+
this.logger.warn('Claude CLI stderr (piped)', { streamingId, stderr: data.toString() });
|
|
1032
|
+
});
|
|
1033
|
+
// Handle piped process close
|
|
1034
|
+
managedProcess.child.on('close', (code) => {
|
|
1035
|
+
this.logger.debug('Piped process closed', {
|
|
1036
|
+
streamingId,
|
|
1037
|
+
exitCode: code,
|
|
1038
|
+
normalExit: code === 0,
|
|
1039
|
+
timestamp: new Date().toISOString(),
|
|
1040
|
+
outputBuffer: this.outputBuffers.get(streamingId) || 'No output captured'
|
|
1041
|
+
});
|
|
1042
|
+
this.handleProcessClose(streamingId, code);
|
|
1043
|
+
});
|
|
1044
|
+
// Handle piped process error
|
|
1045
|
+
managedProcess.child.on('error', (error) => {
|
|
1046
|
+
this.handleProcessError(streamingId, error);
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
handleClaudeMessage(streamingId, message) {
|
|
1051
|
+
this.logger.debug('Handling Claude message', {
|
|
1052
|
+
streamingId,
|
|
1053
|
+
messageType: message?.type,
|
|
1054
|
+
isError: message?.type === 'error',
|
|
1055
|
+
isResult: message?.type === 'result'
|
|
1056
|
+
});
|
|
1057
|
+
// Log important message types to process event log
|
|
1058
|
+
const messageType = message?.type;
|
|
1059
|
+
const messageSubtype = 'subtype' in message ? message.subtype : undefined;
|
|
1060
|
+
this.processEventLog.message({
|
|
1061
|
+
traceId: streamingId,
|
|
1062
|
+
streamingId,
|
|
1063
|
+
messageType: messageType || 'unknown',
|
|
1064
|
+
messageSubtype,
|
|
1065
|
+
});
|
|
1066
|
+
// Log result messages specifically
|
|
1067
|
+
if (messageType === 'result') {
|
|
1068
|
+
this.processEventLog.result({
|
|
1069
|
+
traceId: streamingId,
|
|
1070
|
+
streamingId,
|
|
1071
|
+
resultSubtype: messageSubtype,
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1074
|
+
// Log tool use from assistant content blocks
|
|
1075
|
+
if (messageType === 'assistant' && 'message' in message && message.message?.content) {
|
|
1076
|
+
const content = message.message.content;
|
|
1077
|
+
if (Array.isArray(content)) {
|
|
1078
|
+
for (const block of content) {
|
|
1079
|
+
if (block.type === 'tool_use' && block.name && block.id) {
|
|
1080
|
+
this.processEventLog.toolUse({
|
|
1081
|
+
traceId: streamingId,
|
|
1082
|
+
streamingId,
|
|
1083
|
+
toolName: block.name,
|
|
1084
|
+
toolUseId: block.id,
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
this.emit('claude-message', { streamingId, message });
|
|
1091
|
+
// When we receive a 'result' message, the conversation turn is complete.
|
|
1092
|
+
// For PTY, we don't need to close stdin - the process will exit naturally after result.
|
|
1093
|
+
// The PTY's onExit handler will clean up.
|
|
1094
|
+
if (message?.type === 'result') {
|
|
1095
|
+
this.logger.debug('Received result message, process will exit naturally', {
|
|
1096
|
+
streamingId,
|
|
1097
|
+
resultSubtype: 'subtype' in message ? message.subtype : undefined
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
handleProcessClose(streamingId, code) {
|
|
1102
|
+
// Log process exit
|
|
1103
|
+
this.processEventLog.processExit({
|
|
1104
|
+
traceId: streamingId,
|
|
1105
|
+
streamingId,
|
|
1106
|
+
exitCode: code,
|
|
1107
|
+
});
|
|
1108
|
+
// Clear any pending timeouts for this session
|
|
1109
|
+
const timeouts = this.timeouts.get(streamingId);
|
|
1110
|
+
if (timeouts) {
|
|
1111
|
+
timeouts.forEach(timeout => clearTimeout(timeout));
|
|
1112
|
+
this.timeouts.delete(streamingId);
|
|
1113
|
+
}
|
|
1114
|
+
this.processes.delete(streamingId);
|
|
1115
|
+
this.outputBuffers.delete(streamingId);
|
|
1116
|
+
const config = this.conversationConfigs.get(streamingId);
|
|
1117
|
+
this.conversationConfigs.delete(streamingId);
|
|
1118
|
+
// Send notification if service is available
|
|
1119
|
+
if (this.notificationService && config) {
|
|
1120
|
+
// Get session ID from conversation status or config
|
|
1121
|
+
const sessionId = this.statusTracker.getSessionId(streamingId) || 'unknown';
|
|
1122
|
+
// Try to get conversation metadata for summary
|
|
1123
|
+
this.historyReader.fetchConversationDirect(sessionId)
|
|
1124
|
+
.then(({ metadata }) => {
|
|
1125
|
+
if (this.notificationService && metadata) {
|
|
1126
|
+
return this.notificationService.sendConversationEndNotification(streamingId, sessionId, metadata.summary);
|
|
1127
|
+
}
|
|
1128
|
+
})
|
|
1129
|
+
.catch((error) => {
|
|
1130
|
+
this.logger.error('Failed to send conversation end notification', error);
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
this.emit('process-closed', { streamingId, code });
|
|
1134
|
+
}
|
|
1135
|
+
handleProcessError(streamingId, error) {
|
|
1136
|
+
const errorMessage = error.toString();
|
|
1137
|
+
const isBuffer = Buffer.isBuffer(error);
|
|
1138
|
+
this.logger.error('Process error occurred', {
|
|
1139
|
+
streamingId,
|
|
1140
|
+
error: errorMessage,
|
|
1141
|
+
errorType: isBuffer ? 'stderr-output' : error.constructor.name,
|
|
1142
|
+
errorLength: errorMessage.length,
|
|
1143
|
+
processStillActive: this.processes.has(streamingId),
|
|
1144
|
+
timestamp: new Date().toISOString()
|
|
1145
|
+
});
|
|
1146
|
+
this.emit('process-error', { streamingId, error: errorMessage });
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Send an answer to an AskUserQuestion tool via stdin.
|
|
1150
|
+
* The Claude CLI with --input-format stream-json expects a tool_result message.
|
|
1151
|
+
*
|
|
1152
|
+
* Per the SDK docs, the answer format must include both the original questions
|
|
1153
|
+
* and an answers object mapping question text to selected option labels.
|
|
1154
|
+
*/
|
|
1155
|
+
sendQuestionAnswer(streamingId, toolUseId, questions, answers) {
|
|
1156
|
+
const process = this.processes.get(streamingId);
|
|
1157
|
+
if (!process) {
|
|
1158
|
+
this.logger.warn('Cannot send question answer: process not found', { streamingId });
|
|
1159
|
+
return false;
|
|
1160
|
+
}
|
|
1161
|
+
const sessionId = this.sessionIds.get(streamingId);
|
|
1162
|
+
if (!sessionId) {
|
|
1163
|
+
this.logger.warn('Cannot send question answer: session ID not found', { streamingId });
|
|
1164
|
+
return false;
|
|
1165
|
+
}
|
|
1166
|
+
// Build the tool result content per SDK docs:
|
|
1167
|
+
// { questions: [...], answers: { "Question text?": "Selected label" } }
|
|
1168
|
+
const toolResultContent = JSON.stringify({
|
|
1169
|
+
questions,
|
|
1170
|
+
answers
|
|
1171
|
+
});
|
|
1172
|
+
// Format as a tool_result message per Claude CLI stream-json protocol
|
|
1173
|
+
const stdinMessage = JSON.stringify({
|
|
1174
|
+
type: 'user',
|
|
1175
|
+
message: {
|
|
1176
|
+
role: 'user',
|
|
1177
|
+
content: [{
|
|
1178
|
+
type: 'tool_result',
|
|
1179
|
+
tool_use_id: toolUseId,
|
|
1180
|
+
content: toolResultContent
|
|
1181
|
+
}]
|
|
1182
|
+
},
|
|
1183
|
+
session_id: sessionId
|
|
1184
|
+
});
|
|
1185
|
+
this.logger.info('Sending question answer via stdin', {
|
|
1186
|
+
streamingId,
|
|
1187
|
+
sessionId,
|
|
1188
|
+
toolUseId,
|
|
1189
|
+
answerCount: Object.keys(answers).length,
|
|
1190
|
+
stdinMessagePreview: stdinMessage.substring(0, 500),
|
|
1191
|
+
fullMessage: stdinMessage
|
|
1192
|
+
});
|
|
1193
|
+
// Write to PTY stdin with newline
|
|
1194
|
+
process.write(stdinMessage + '\n');
|
|
1195
|
+
this.logger.info('Question answer written to PTY stdin', { streamingId, toolUseId });
|
|
1196
|
+
return true;
|
|
1197
|
+
}
|
|
1198
|
+
/**
|
|
1199
|
+
* Send a raw stdin message to a Claude process.
|
|
1200
|
+
* Used by ClaudiaService for sending messages to Claudia's session.
|
|
1201
|
+
* The message should already be formatted as a JSON string.
|
|
1202
|
+
*/
|
|
1203
|
+
sendStdinMessage(streamingId, message) {
|
|
1204
|
+
const process = this.processes.get(streamingId);
|
|
1205
|
+
if (!process) {
|
|
1206
|
+
this.logger.warn('Cannot send stdin message: process not found', { streamingId });
|
|
1207
|
+
return false;
|
|
1208
|
+
}
|
|
1209
|
+
this.logger.info('Sending stdin message', {
|
|
1210
|
+
streamingId,
|
|
1211
|
+
messageLength: message.length
|
|
1212
|
+
});
|
|
1213
|
+
// Write to PTY stdin with newline
|
|
1214
|
+
process.write(message + '\n');
|
|
1215
|
+
return true;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
//# sourceMappingURL=claude-process-manager.js.map
|