mstro-app 0.4.52 → 0.5.1
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 +129 -190
- package/PRIVACY.md +3 -3
- package/README.md +15 -6
- package/bin/commands/config.js +0 -1
- package/bin/mstro.js +1 -2
- package/bin/postinstall.js +0 -1
- package/dist/server/cli/headless/claude-invoker-process.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker-process.js +0 -1
- package/dist/server/cli/headless/claude-invoker-process.js.map +1 -1
- package/dist/server/cli/headless/claude-invoker-stall.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker-stall.js +7 -3
- package/dist/server/cli/headless/claude-invoker-stall.js.map +1 -1
- package/dist/server/cli/headless/claude-invoker-stream.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker-stream.js +0 -1
- package/dist/server/cli/headless/claude-invoker-stream.js.map +1 -1
- package/dist/server/cli/headless/claude-invoker-tools.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker-tools.js +0 -1
- package/dist/server/cli/headless/claude-invoker-tools.js.map +1 -1
- package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker.js +1 -2
- package/dist/server/cli/headless/claude-invoker.js.map +1 -1
- package/dist/server/cli/headless/haiku-assessments.d.ts.map +1 -1
- package/dist/server/cli/headless/haiku-assessments.js +0 -1
- package/dist/server/cli/headless/haiku-assessments.js.map +1 -1
- package/dist/server/cli/headless/headless-logger.d.ts.map +1 -1
- package/dist/server/cli/headless/headless-logger.js +0 -1
- package/dist/server/cli/headless/headless-logger.js.map +1 -1
- package/dist/server/cli/headless/index.d.ts.map +1 -1
- package/dist/server/cli/headless/index.js +0 -1
- package/dist/server/cli/headless/index.js.map +1 -1
- package/dist/server/cli/headless/native-timeout-detector.d.ts.map +1 -1
- package/dist/server/cli/headless/native-timeout-detector.js +0 -1
- package/dist/server/cli/headless/native-timeout-detector.js.map +1 -1
- package/dist/server/cli/headless/output-utils.d.ts.map +1 -1
- package/dist/server/cli/headless/output-utils.js +0 -1
- package/dist/server/cli/headless/output-utils.js.map +1 -1
- package/dist/server/cli/headless/prompt-utils.d.ts.map +1 -1
- package/dist/server/cli/headless/prompt-utils.js +0 -1
- package/dist/server/cli/headless/prompt-utils.js.map +1 -1
- package/dist/server/cli/headless/resilient-runner.d.ts.map +1 -1
- package/dist/server/cli/headless/resilient-runner.js +0 -1
- package/dist/server/cli/headless/resilient-runner.js.map +1 -1
- package/dist/server/cli/headless/retry-strategies.d.ts.map +1 -1
- package/dist/server/cli/headless/retry-strategies.js +0 -1
- package/dist/server/cli/headless/retry-strategies.js.map +1 -1
- package/dist/server/cli/headless/runner.d.ts.map +1 -1
- package/dist/server/cli/headless/runner.js +63 -68
- package/dist/server/cli/headless/runner.js.map +1 -1
- package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
- package/dist/server/cli/headless/stall-assessor.js +9 -5
- package/dist/server/cli/headless/stall-assessor.js.map +1 -1
- package/dist/server/cli/headless/tool-watchdog.d.ts.map +1 -1
- package/dist/server/cli/headless/tool-watchdog.js +0 -1
- package/dist/server/cli/headless/tool-watchdog.js.map +1 -1
- package/dist/server/cli/headless/types.d.ts.map +1 -1
- package/dist/server/cli/headless/types.js +0 -1
- package/dist/server/cli/headless/types.js.map +1 -1
- package/dist/server/cli/improvisation-attachments.d.ts.map +1 -1
- package/dist/server/cli/improvisation-attachments.js +0 -1
- package/dist/server/cli/improvisation-attachments.js.map +1 -1
- package/dist/server/cli/improvisation-history-store.d.ts +16 -0
- package/dist/server/cli/improvisation-history-store.d.ts.map +1 -0
- package/dist/server/cli/improvisation-history-store.js +51 -0
- package/dist/server/cli/improvisation-history-store.js.map +1 -0
- package/dist/server/cli/improvisation-movements.d.ts +31 -0
- package/dist/server/cli/improvisation-movements.d.ts.map +1 -0
- package/dist/server/cli/improvisation-movements.js +92 -0
- package/dist/server/cli/improvisation-movements.js.map +1 -0
- package/dist/server/cli/improvisation-output-queue.d.ts +13 -0
- package/dist/server/cli/improvisation-output-queue.d.ts.map +1 -0
- package/dist/server/cli/improvisation-output-queue.js +39 -0
- package/dist/server/cli/improvisation-output-queue.js.map +1 -0
- package/dist/server/cli/improvisation-retry.d.ts +21 -51
- package/dist/server/cli/improvisation-retry.d.ts.map +1 -1
- package/dist/server/cli/improvisation-retry.js +18 -434
- package/dist/server/cli/improvisation-retry.js.map +1 -1
- package/dist/server/cli/improvisation-session-manager.d.ts +10 -8
- package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
- package/dist/server/cli/improvisation-session-manager.js +53 -149
- package/dist/server/cli/improvisation-session-manager.js.map +1 -1
- package/dist/server/cli/improvisation-types.d.ts.map +1 -1
- package/dist/server/cli/improvisation-types.js +0 -1
- package/dist/server/cli/improvisation-types.js.map +1 -1
- package/dist/server/cli/retry/retry-best-result.d.ts +4 -0
- package/dist/server/cli/retry/retry-best-result.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-best-result.js +60 -0
- package/dist/server/cli/retry/retry-best-result.js.map +1 -0
- package/dist/server/cli/retry/retry-context-loss.d.ts +6 -0
- package/dist/server/cli/retry/retry-context-loss.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-context-loss.js +67 -0
- package/dist/server/cli/retry/retry-context-loss.js.map +1 -0
- package/dist/server/cli/retry/retry-premature-completion.d.ts +5 -0
- package/dist/server/cli/retry/retry-premature-completion.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-premature-completion.js +80 -0
- package/dist/server/cli/retry/retry-premature-completion.js.map +1 -0
- package/dist/server/cli/retry/retry-recovery-strategies.d.ts +13 -0
- package/dist/server/cli/retry/retry-recovery-strategies.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-recovery-strategies.js +165 -0
- package/dist/server/cli/retry/retry-recovery-strategies.js.map +1 -0
- package/dist/server/cli/retry/retry-resume-strategy.d.ts +12 -0
- package/dist/server/cli/retry/retry-resume-strategy.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-resume-strategy.js +21 -0
- package/dist/server/cli/retry/retry-resume-strategy.js.map +1 -0
- package/dist/server/cli/retry/retry-runner-factory.d.ts +11 -0
- package/dist/server/cli/retry/retry-runner-factory.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-runner-factory.js +59 -0
- package/dist/server/cli/retry/retry-runner-factory.js.map +1 -0
- package/dist/server/cli/retry/retry-tool-results.d.ts +9 -0
- package/dist/server/cli/retry/retry-tool-results.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-tool-results.js +23 -0
- package/dist/server/cli/retry/retry-tool-results.js.map +1 -0
- package/dist/server/cli/retry/retry-types.d.ts +30 -0
- package/dist/server/cli/retry/retry-types.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-types.js +3 -0
- package/dist/server/cli/retry/retry-types.js.map +1 -0
- package/dist/server/index.js +21 -110
- package/dist/server/index.js.map +1 -1
- package/dist/server/mcp/bouncer-cli.js +0 -1
- package/dist/server/mcp/bouncer-cli.js.map +1 -1
- package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -1
- package/dist/server/mcp/bouncer-haiku.js +0 -1
- package/dist/server/mcp/bouncer-haiku.js.map +1 -1
- package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
- package/dist/server/mcp/bouncer-integration.js +0 -1
- package/dist/server/mcp/bouncer-integration.js.map +1 -1
- package/dist/server/mcp/security-analysis.d.ts.map +1 -1
- package/dist/server/mcp/security-analysis.js +0 -1
- package/dist/server/mcp/security-analysis.js.map +1 -1
- package/dist/server/mcp/security-audit.d.ts.map +1 -1
- package/dist/server/mcp/security-audit.js +0 -1
- package/dist/server/mcp/security-audit.js.map +1 -1
- package/dist/server/mcp/security-patterns.d.ts.map +1 -1
- package/dist/server/mcp/security-patterns.js +0 -1
- package/dist/server/mcp/security-patterns.js.map +1 -1
- package/dist/server/mcp/server.js +0 -1
- package/dist/server/mcp/server.js.map +1 -1
- package/dist/server/routes/files.d.ts.map +1 -1
- package/dist/server/routes/files.js +0 -1
- package/dist/server/routes/files.js.map +1 -1
- package/dist/server/routes/improvise.d.ts.map +1 -1
- package/dist/server/routes/improvise.js +0 -1
- package/dist/server/routes/improvise.js.map +1 -1
- package/dist/server/routes/index.d.ts.map +1 -1
- package/dist/server/routes/index.js +0 -1
- package/dist/server/routes/index.js.map +1 -1
- package/dist/server/routes/instances.d.ts.map +1 -1
- package/dist/server/routes/instances.js +0 -1
- package/dist/server/routes/instances.js.map +1 -1
- package/dist/server/routes/notifications.d.ts.map +1 -1
- package/dist/server/routes/notifications.js +0 -1
- package/dist/server/routes/notifications.js.map +1 -1
- package/dist/server/server-setup.d.ts +16 -1
- package/dist/server/server-setup.d.ts.map +1 -1
- package/dist/server/server-setup.js +107 -1
- package/dist/server/server-setup.js.map +1 -1
- package/dist/server/services/analytics.d.ts.map +1 -1
- package/dist/server/services/analytics.js +0 -1
- package/dist/server/services/analytics.js.map +1 -1
- package/dist/server/services/auth.d.ts.map +1 -1
- package/dist/server/services/auth.js +0 -1
- package/dist/server/services/auth.js.map +1 -1
- package/dist/server/services/client-id.d.ts.map +1 -1
- package/dist/server/services/client-id.js +0 -1
- package/dist/server/services/client-id.js.map +1 -1
- package/dist/server/services/file-explorer-ops.d.ts.map +1 -1
- package/dist/server/services/file-explorer-ops.js +0 -1
- package/dist/server/services/file-explorer-ops.js.map +1 -1
- package/dist/server/services/files.d.ts.map +1 -1
- package/dist/server/services/files.js +0 -1
- package/dist/server/services/files.js.map +1 -1
- package/dist/server/services/instances.d.ts.map +1 -1
- package/dist/server/services/instances.js +0 -1
- package/dist/server/services/instances.js.map +1 -1
- package/dist/server/services/pathUtils.d.ts.map +1 -1
- package/dist/server/services/pathUtils.js +0 -1
- package/dist/server/services/pathUtils.js.map +1 -1
- package/dist/server/services/plan/agent-loader.d.ts.map +1 -1
- package/dist/server/services/plan/agent-loader.js +0 -1
- package/dist/server/services/plan/agent-loader.js.map +1 -1
- package/dist/server/services/plan/board-config.d.ts +21 -0
- package/dist/server/services/plan/board-config.d.ts.map +1 -0
- package/dist/server/services/plan/board-config.js +111 -0
- package/dist/server/services/plan/board-config.js.map +1 -0
- package/dist/server/services/plan/composer.d.ts +1 -1
- package/dist/server/services/plan/composer.d.ts.map +1 -1
- package/dist/server/services/plan/composer.js +7 -6
- package/dist/server/services/plan/composer.js.map +1 -1
- package/dist/server/services/plan/config-installer.d.ts.map +1 -1
- package/dist/server/services/plan/config-installer.js +0 -1
- package/dist/server/services/plan/config-installer.js.map +1 -1
- package/dist/server/services/plan/dependency-resolver.d.ts.map +1 -1
- package/dist/server/services/plan/dependency-resolver.js +0 -1
- package/dist/server/services/plan/dependency-resolver.js.map +1 -1
- package/dist/server/services/plan/executor.d.ts +48 -48
- package/dist/server/services/plan/executor.d.ts.map +1 -1
- package/dist/server/services/plan/executor.js +202 -458
- package/dist/server/services/plan/executor.js.map +1 -1
- package/dist/server/services/plan/front-matter.d.ts.map +1 -1
- package/dist/server/services/plan/front-matter.js +0 -1
- package/dist/server/services/plan/front-matter.js.map +1 -1
- package/dist/server/services/plan/issue-classification.d.ts.map +1 -1
- package/dist/server/services/plan/issue-classification.js +0 -1
- package/dist/server/services/plan/issue-classification.js.map +1 -1
- package/dist/server/services/plan/issue-loader.d.ts +16 -0
- package/dist/server/services/plan/issue-loader.d.ts.map +1 -0
- package/dist/server/services/plan/issue-loader.js +45 -0
- package/dist/server/services/plan/issue-loader.js.map +1 -0
- package/dist/server/services/plan/issue-prompt-builder.d.ts.map +1 -1
- package/dist/server/services/plan/issue-prompt-builder.js +0 -1
- package/dist/server/services/plan/issue-prompt-builder.js.map +1 -1
- package/dist/server/services/plan/issue-retry.d.ts +3 -1
- package/dist/server/services/plan/issue-retry.d.ts.map +1 -1
- package/dist/server/services/plan/issue-retry.js +2 -1
- package/dist/server/services/plan/issue-retry.js.map +1 -1
- package/dist/server/services/plan/issue-writer.d.ts +34 -0
- package/dist/server/services/plan/issue-writer.d.ts.map +1 -0
- package/dist/server/services/plan/issue-writer.js +109 -0
- package/dist/server/services/plan/issue-writer.js.map +1 -0
- package/dist/server/services/plan/output-manager.js +2 -2
- package/dist/server/services/plan/output-manager.js.map +1 -1
- package/dist/server/services/plan/parser-core.d.ts.map +1 -1
- package/dist/server/services/plan/parser-core.js +0 -1
- package/dist/server/services/plan/parser-core.js.map +1 -1
- package/dist/server/services/plan/parser-migration.d.ts.map +1 -1
- package/dist/server/services/plan/parser-migration.js +0 -1
- package/dist/server/services/plan/parser-migration.js.map +1 -1
- package/dist/server/services/plan/parser.d.ts.map +1 -1
- package/dist/server/services/plan/parser.js +0 -1
- package/dist/server/services/plan/parser.js.map +1 -1
- package/dist/server/services/plan/progress-log.d.ts +11 -0
- package/dist/server/services/plan/progress-log.d.ts.map +1 -0
- package/dist/server/services/plan/progress-log.js +80 -0
- package/dist/server/services/plan/progress-log.js.map +1 -0
- package/dist/server/services/plan/prompt-builder.d.ts.map +1 -1
- package/dist/server/services/plan/prompt-builder.js +48 -32
- package/dist/server/services/plan/prompt-builder.js.map +1 -1
- package/dist/server/services/plan/readiness-planner.d.ts +15 -0
- package/dist/server/services/plan/readiness-planner.d.ts.map +1 -0
- package/dist/server/services/plan/readiness-planner.js +40 -0
- package/dist/server/services/plan/readiness-planner.js.map +1 -0
- package/dist/server/services/plan/review-gate.d.ts +31 -0
- package/dist/server/services/plan/review-gate.d.ts.map +1 -1
- package/dist/server/services/plan/review-gate.js +52 -3
- package/dist/server/services/plan/review-gate.js.map +1 -1
- package/dist/server/services/plan/state-reconciler.d.ts.map +1 -1
- package/dist/server/services/plan/state-reconciler.js +0 -1
- package/dist/server/services/plan/state-reconciler.js.map +1 -1
- package/dist/server/services/plan/types.d.ts.map +1 -1
- package/dist/server/services/plan/types.js +0 -1
- package/dist/server/services/plan/types.js.map +1 -1
- package/dist/server/services/plan/watcher.d.ts.map +1 -1
- package/dist/server/services/plan/watcher.js +0 -1
- package/dist/server/services/plan/watcher.js.map +1 -1
- package/dist/server/services/platform-credentials.d.ts.map +1 -1
- package/dist/server/services/platform-credentials.js +0 -1
- package/dist/server/services/platform-credentials.js.map +1 -1
- package/dist/server/services/platform-token-lifecycle.d.ts +70 -0
- package/dist/server/services/platform-token-lifecycle.d.ts.map +1 -0
- package/dist/server/services/platform-token-lifecycle.js +156 -0
- package/dist/server/services/platform-token-lifecycle.js.map +1 -0
- package/dist/server/services/platform.d.ts +25 -4
- package/dist/server/services/platform.d.ts.map +1 -1
- package/dist/server/services/platform.js +150 -92
- package/dist/server/services/platform.js.map +1 -1
- package/dist/server/services/sentry.d.ts.map +1 -1
- package/dist/server/services/sentry.js +0 -1
- package/dist/server/services/sentry.js.map +1 -1
- package/dist/server/services/settings.d.ts.map +1 -1
- package/dist/server/services/settings.js +0 -1
- package/dist/server/services/settings.js.map +1 -1
- package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
- package/dist/server/services/terminal/pty-manager.js +0 -1
- package/dist/server/services/terminal/pty-manager.js.map +1 -1
- package/dist/server/services/terminal/pty-utils.d.ts.map +1 -1
- package/dist/server/services/terminal/pty-utils.js +0 -1
- package/dist/server/services/terminal/pty-utils.js.map +1 -1
- package/dist/server/services/websocket/autocomplete.d.ts.map +1 -1
- package/dist/server/services/websocket/autocomplete.js +0 -1
- package/dist/server/services/websocket/autocomplete.js.map +1 -1
- package/dist/server/services/websocket/file-definition-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/file-definition-handlers.js +0 -1
- package/dist/server/services/websocket/file-definition-handlers.js.map +1 -1
- package/dist/server/services/websocket/file-download-handler.d.ts +17 -0
- package/dist/server/services/websocket/file-download-handler.d.ts.map +1 -0
- package/dist/server/services/websocket/file-download-handler.js +164 -0
- package/dist/server/services/websocket/file-download-handler.js.map +1 -0
- package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/file-explorer-handlers.js +0 -1
- package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
- package/dist/server/services/websocket/file-search-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/file-search-handlers.js +0 -1
- package/dist/server/services/websocket/file-search-handlers.js.map +1 -1
- package/dist/server/services/websocket/file-upload-handler.d.ts +2 -3
- package/dist/server/services/websocket/file-upload-handler.d.ts.map +1 -1
- package/dist/server/services/websocket/file-upload-handler.js +4 -7
- package/dist/server/services/websocket/file-upload-handler.js.map +1 -1
- package/dist/server/services/websocket/file-utils.d.ts.map +1 -1
- package/dist/server/services/websocket/file-utils.js +0 -1
- package/dist/server/services/websocket/file-utils.js.map +1 -1
- package/dist/server/services/websocket/git-branch-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-branch-handlers.js +0 -1
- package/dist/server/services/websocket/git-branch-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-diff-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-diff-handlers.js +0 -1
- package/dist/server/services/websocket/git-diff-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-handlers.js +58 -6
- package/dist/server/services/websocket/git-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-head-watcher.d.ts.map +1 -1
- package/dist/server/services/websocket/git-head-watcher.js +0 -1
- package/dist/server/services/websocket/git-head-watcher.js.map +1 -1
- package/dist/server/services/websocket/git-log-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-log-handlers.js +0 -1
- package/dist/server/services/websocket/git-log-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-pr-handlers.js +0 -1
- package/dist/server/services/websocket/git-pr-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-tag-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-tag-handlers.js +0 -1
- package/dist/server/services/websocket/git-tag-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-utils.d.ts +18 -3
- package/dist/server/services/websocket/git-utils.d.ts.map +1 -1
- package/dist/server/services/websocket/git-utils.js +58 -8
- package/dist/server/services/websocket/git-utils.js.map +1 -1
- package/dist/server/services/websocket/git-worktree-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-worktree-handlers.js +258 -16
- package/dist/server/services/websocket/git-worktree-handlers.js.map +1 -1
- package/dist/server/services/websocket/handler-context.d.ts +15 -0
- package/dist/server/services/websocket/handler-context.d.ts.map +1 -1
- package/dist/server/services/websocket/handler-context.js +0 -1
- package/dist/server/services/websocket/handler-context.js.map +1 -1
- package/dist/server/services/websocket/handler.d.ts +7 -0
- package/dist/server/services/websocket/handler.d.ts.map +1 -1
- package/dist/server/services/websocket/handler.js +76 -15
- package/dist/server/services/websocket/handler.js.map +1 -1
- package/dist/server/services/websocket/index.d.ts.map +1 -1
- package/dist/server/services/websocket/index.js +0 -1
- package/dist/server/services/websocket/index.js.map +1 -1
- package/dist/server/services/websocket/msg-id-tracker.d.ts +21 -0
- package/dist/server/services/websocket/msg-id-tracker.d.ts.map +1 -0
- package/dist/server/services/websocket/msg-id-tracker.js +76 -0
- package/dist/server/services/websocket/msg-id-tracker.js.map +1 -0
- package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-board-handlers.js +0 -1
- package/dist/server/services/websocket/plan-board-handlers.js.map +1 -1
- package/dist/server/services/websocket/plan-execution-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-execution-handlers.js +6 -2
- package/dist/server/services/websocket/plan-execution-handlers.js.map +1 -1
- package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-handlers.js +0 -1
- package/dist/server/services/websocket/plan-handlers.js.map +1 -1
- package/dist/server/services/websocket/plan-helpers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-helpers.js +0 -1
- package/dist/server/services/websocket/plan-helpers.js.map +1 -1
- package/dist/server/services/websocket/plan-issue-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-issue-handlers.js +0 -1
- package/dist/server/services/websocket/plan-issue-handlers.js.map +1 -1
- package/dist/server/services/websocket/plan-sprint-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-sprint-handlers.js +0 -1
- package/dist/server/services/websocket/plan-sprint-handlers.js.map +1 -1
- package/dist/server/services/websocket/quality-complexity.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-complexity.js +0 -1
- package/dist/server/services/websocket/quality-complexity.js.map +1 -1
- package/dist/server/services/websocket/quality-grading.d.ts +46 -0
- package/dist/server/services/websocket/quality-grading.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-grading.js +482 -0
- package/dist/server/services/websocket/quality-grading.js.map +1 -0
- package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-handlers.js +15 -4
- package/dist/server/services/websocket/quality-handlers.js.map +1 -1
- package/dist/server/services/websocket/quality-linting.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-linting.js +0 -1
- package/dist/server/services/websocket/quality-linting.js.map +1 -1
- package/dist/server/services/websocket/quality-persistence.d.ts +14 -0
- package/dist/server/services/websocket/quality-persistence.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-persistence.js +28 -12
- package/dist/server/services/websocket/quality-persistence.js.map +1 -1
- package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-review-agent.js +2 -3
- package/dist/server/services/websocket/quality-review-agent.js.map +1 -1
- package/dist/server/services/websocket/quality-service.d.ts +3 -1
- package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-service.js +53 -58
- package/dist/server/services/websocket/quality-service.js.map +1 -1
- package/dist/server/services/websocket/quality-tools.d.ts +1 -1
- package/dist/server/services/websocket/quality-tools.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-tools.js +6 -3
- package/dist/server/services/websocket/quality-tools.js.map +1 -1
- package/dist/server/services/websocket/quality-types.d.ts +18 -2
- package/dist/server/services/websocket/quality-types.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-types.js +0 -1
- package/dist/server/services/websocket/quality-types.js.map +1 -1
- package/dist/server/services/websocket/session-handlers.d.ts +48 -2
- package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/session-handlers.js +204 -66
- package/dist/server/services/websocket/session-handlers.js.map +1 -1
- package/dist/server/services/websocket/session-history.d.ts.map +1 -1
- package/dist/server/services/websocket/session-history.js +0 -1
- package/dist/server/services/websocket/session-history.js.map +1 -1
- package/dist/server/services/websocket/session-initialization.d.ts +2 -2
- package/dist/server/services/websocket/session-initialization.d.ts.map +1 -1
- package/dist/server/services/websocket/session-initialization.js +75 -18
- package/dist/server/services/websocket/session-initialization.js.map +1 -1
- package/dist/server/services/websocket/session-registry.d.ts +29 -1
- package/dist/server/services/websocket/session-registry.d.ts.map +1 -1
- package/dist/server/services/websocket/session-registry.js +53 -5
- package/dist/server/services/websocket/session-registry.js.map +1 -1
- package/dist/server/services/websocket/settings-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/settings-handlers.js +0 -1
- package/dist/server/services/websocket/settings-handlers.js.map +1 -1
- package/dist/server/services/websocket/skill-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/skill-handlers.js +0 -1
- package/dist/server/services/websocket/skill-handlers.js.map +1 -1
- package/dist/server/services/websocket/skill-watcher.d.ts.map +1 -1
- package/dist/server/services/websocket/skill-watcher.js +0 -1
- package/dist/server/services/websocket/skill-watcher.js.map +1 -1
- package/dist/server/services/websocket/tab-broadcast.d.ts +24 -0
- package/dist/server/services/websocket/tab-broadcast.d.ts.map +1 -0
- package/dist/server/services/websocket/tab-broadcast.js +12 -0
- package/dist/server/services/websocket/tab-broadcast.js.map +1 -0
- package/dist/server/services/websocket/tab-event-buffer.d.ts +103 -0
- package/dist/server/services/websocket/tab-event-buffer.d.ts.map +1 -0
- package/dist/server/services/websocket/tab-event-buffer.js +106 -0
- package/dist/server/services/websocket/tab-event-buffer.js.map +1 -0
- package/dist/server/services/websocket/tab-event-replay.d.ts +20 -0
- package/dist/server/services/websocket/tab-event-replay.d.ts.map +1 -0
- package/dist/server/services/websocket/tab-event-replay.js +20 -0
- package/dist/server/services/websocket/tab-event-replay.js.map +1 -0
- package/dist/server/services/websocket/tab-handlers.d.ts +0 -1
- package/dist/server/services/websocket/tab-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/tab-handlers.js +2 -10
- package/dist/server/services/websocket/tab-handlers.js.map +1 -1
- package/dist/server/services/websocket/terminal-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/terminal-handlers.js +39 -4
- package/dist/server/services/websocket/terminal-handlers.js.map +1 -1
- package/dist/server/services/websocket/types.d.ts +17 -8
- package/dist/server/services/websocket/types.d.ts.map +1 -1
- package/dist/server/services/websocket/types.js +8 -7
- package/dist/server/services/websocket/types.js.map +1 -1
- package/dist/server/utils/agent-manager.d.ts.map +1 -1
- package/dist/server/utils/agent-manager.js +0 -1
- package/dist/server/utils/agent-manager.js.map +1 -1
- package/dist/server/utils/paths.d.ts.map +1 -1
- package/dist/server/utils/paths.js +0 -1
- package/dist/server/utils/paths.js.map +1 -1
- package/dist/server/utils/port-manager.d.ts.map +1 -1
- package/dist/server/utils/port-manager.js +0 -1
- package/dist/server/utils/port-manager.js.map +1 -1
- package/dist/server/utils/port.d.ts.map +1 -1
- package/dist/server/utils/port.js +0 -1
- package/dist/server/utils/port.js.map +1 -1
- package/package.json +2 -2
- package/server/README.md +1 -1
- package/server/cli/headless/claude-invoker-process.ts +0 -1
- package/server/cli/headless/claude-invoker-stall.ts +7 -3
- package/server/cli/headless/claude-invoker-stream.ts +0 -1
- package/server/cli/headless/claude-invoker-tools.ts +0 -1
- package/server/cli/headless/claude-invoker.ts +1 -2
- package/server/cli/headless/haiku-assessments.ts +0 -1
- package/server/cli/headless/headless-logger.ts +0 -1
- package/server/cli/headless/index.ts +0 -1
- package/server/cli/headless/native-timeout-detector.ts +0 -1
- package/server/cli/headless/output-utils.ts +0 -1
- package/server/cli/headless/prompt-utils.ts +0 -1
- package/server/cli/headless/resilient-runner.ts +0 -1
- package/server/cli/headless/retry-strategies.ts +0 -1
- package/server/cli/headless/runner.ts +67 -73
- package/server/cli/headless/stall-assessor.ts +9 -5
- package/server/cli/headless/tool-watchdog.ts +0 -1
- package/server/cli/headless/types.ts +1 -2
- package/server/cli/improvisation-attachments.ts +0 -1
- package/server/cli/improvisation-history-store.ts +61 -0
- package/server/cli/improvisation-movements.ts +119 -0
- package/server/cli/improvisation-output-queue.ts +41 -0
- package/server/cli/improvisation-retry.ts +25 -601
- package/server/cli/improvisation-session-manager.ts +74 -161
- package/server/cli/improvisation-types.ts +0 -1
- package/server/cli/retry/retry-best-result.ts +69 -0
- package/server/cli/retry/retry-context-loss.ts +86 -0
- package/server/cli/retry/retry-premature-completion.ts +112 -0
- package/server/cli/retry/retry-recovery-strategies.ts +246 -0
- package/server/cli/retry/retry-resume-strategy.ts +32 -0
- package/server/cli/retry/retry-runner-factory.ts +69 -0
- package/server/cli/retry/retry-tool-results.ts +30 -0
- package/server/cli/retry/retry-types.ts +31 -0
- package/server/index.ts +37 -124
- package/server/mcp/bouncer-cli.ts +0 -1
- package/server/mcp/bouncer-haiku.ts +0 -1
- package/server/mcp/bouncer-integration.ts +0 -1
- package/server/mcp/security-analysis.ts +0 -1
- package/server/mcp/security-audit.ts +0 -1
- package/server/mcp/security-patterns.ts +0 -1
- package/server/mcp/server.ts +0 -1
- package/server/routes/files.ts +0 -1
- package/server/routes/improvise.ts +0 -1
- package/server/routes/index.ts +0 -1
- package/server/routes/instances.ts +0 -1
- package/server/routes/notifications.ts +0 -1
- package/server/server-setup.ts +126 -2
- package/server/services/analytics.ts +0 -1
- package/server/services/auth.ts +0 -1
- package/server/services/client-id.ts +0 -1
- package/server/services/file-explorer-ops.ts +0 -1
- package/server/services/files.ts +0 -1
- package/server/services/instances.ts +0 -1
- package/server/services/pathUtils.ts +0 -1
- package/server/services/plan/agent-loader.ts +0 -1
- package/server/services/plan/agents/assess-stall.md +11 -4
- package/server/services/plan/agents/code-review.md +13 -11
- package/server/services/plan/board-config.ts +121 -0
- package/server/services/plan/composer.ts +7 -6
- package/server/services/plan/config-installer.ts +0 -1
- package/server/services/plan/dependency-resolver.ts +0 -1
- package/server/services/plan/executor.ts +259 -470
- package/server/services/plan/front-matter.ts +0 -1
- package/server/services/plan/issue-classification.ts +0 -1
- package/server/services/plan/issue-loader.ts +63 -0
- package/server/services/plan/issue-prompt-builder.ts +0 -1
- package/server/services/plan/issue-retry.ts +5 -2
- package/server/services/plan/issue-writer.ts +136 -0
- package/server/services/plan/output-manager.ts +2 -2
- package/server/services/plan/parser-core.ts +0 -1
- package/server/services/plan/parser-migration.ts +0 -1
- package/server/services/plan/parser.ts +0 -1
- package/server/services/plan/progress-log.ts +91 -0
- package/server/services/plan/prompt-builder.ts +73 -36
- package/server/services/plan/readiness-planner.ts +49 -0
- package/server/services/plan/review-gate.ts +102 -3
- package/server/services/plan/state-reconciler.ts +0 -1
- package/server/services/plan/types.ts +0 -1
- package/server/services/plan/watcher.ts +0 -1
- package/server/services/platform-credentials.ts +0 -1
- package/server/services/platform-token-lifecycle.ts +171 -0
- package/server/services/platform.ts +168 -105
- package/server/services/sentry.ts +0 -1
- package/server/services/settings.ts +0 -1
- package/server/services/terminal/pty-manager.ts +0 -1
- package/server/services/terminal/pty-utils.ts +0 -1
- package/server/services/websocket/autocomplete.ts +0 -1
- package/server/services/websocket/file-definition-handlers.ts +0 -1
- package/server/services/websocket/file-download-handler.ts +190 -0
- package/server/services/websocket/file-explorer-handlers.ts +0 -1
- package/server/services/websocket/file-search-handlers.ts +0 -1
- package/server/services/websocket/file-upload-handler.ts +6 -5
- package/server/services/websocket/file-utils.ts +0 -1
- package/server/services/websocket/git-branch-handlers.ts +0 -1
- package/server/services/websocket/git-diff-handlers.ts +0 -1
- package/server/services/websocket/git-handlers.ts +66 -10
- package/server/services/websocket/git-head-watcher.ts +0 -1
- package/server/services/websocket/git-log-handlers.ts +0 -1
- package/server/services/websocket/git-pr-handlers.ts +0 -1
- package/server/services/websocket/git-tag-handlers.ts +0 -1
- package/server/services/websocket/git-utils.ts +69 -9
- package/server/services/websocket/git-worktree-handlers.ts +289 -19
- package/server/services/websocket/handler-context.ts +15 -1
- package/server/services/websocket/handler.ts +79 -16
- package/server/services/websocket/index.ts +0 -1
- package/server/services/websocket/msg-id-tracker.ts +83 -0
- package/server/services/websocket/plan-board-handlers.ts +0 -1
- package/server/services/websocket/plan-execution-handlers.ts +6 -2
- package/server/services/websocket/plan-handlers.ts +0 -1
- package/server/services/websocket/plan-helpers.ts +0 -1
- package/server/services/websocket/plan-issue-handlers.ts +0 -1
- package/server/services/websocket/plan-sprint-handlers.ts +0 -1
- package/server/services/websocket/quality-complexity.ts +0 -1
- package/server/services/websocket/quality-grading.ts +611 -0
- package/server/services/websocket/quality-handlers.ts +16 -4
- package/server/services/websocket/quality-linting.ts +0 -1
- package/server/services/websocket/quality-persistence.ts +30 -8
- package/server/services/websocket/quality-review-agent.ts +2 -3
- package/server/services/websocket/quality-service.ts +54 -55
- package/server/services/websocket/quality-tools.ts +11 -3
- package/server/services/websocket/quality-types.ts +21 -3
- package/server/services/websocket/session-handlers.ts +213 -69
- package/server/services/websocket/session-history.ts +0 -1
- package/server/services/websocket/session-initialization.ts +83 -20
- package/server/services/websocket/session-registry.ts +61 -5
- package/server/services/websocket/settings-handlers.ts +0 -1
- package/server/services/websocket/skill-handlers.ts +0 -1
- package/server/services/websocket/skill-watcher.ts +0 -1
- package/server/services/websocket/tab-broadcast.ts +37 -0
- package/server/services/websocket/tab-event-buffer.ts +158 -0
- package/server/services/websocket/tab-event-replay.ts +41 -0
- package/server/services/websocket/tab-handlers.ts +2 -10
- package/server/services/websocket/terminal-handlers.ts +39 -3
- package/server/services/websocket/types.ts +19 -7
- package/server/utils/agent-manager.ts +0 -1
- package/server/utils/paths.ts +0 -1
- package/server/utils/port-manager.ts +0 -1
- package/server/utils/port.ts +0 -1
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
// Copyright (c) 2025-present Mstro, Inc. All rights reserved.
|
|
2
|
-
// Licensed under the MIT License. See LICENSE file for details.
|
|
3
2
|
|
|
4
3
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
5
4
|
import { dirname, join } from 'node:path';
|
|
6
5
|
import { resolvePmDir } from '../plan/parser.js';
|
|
7
6
|
import type { Workspace } from '../plan/types.js';
|
|
8
7
|
import { executeGitCommand, handleGitStatus, spawnWithOutput } from './git-handlers.js';
|
|
8
|
+
import { handleGitLog } from './git-log-handlers.js';
|
|
9
|
+
import { parseGitStatus } from './git-utils.js';
|
|
9
10
|
import type { HandlerContext } from './handler-context.js';
|
|
10
|
-
import type { WebSocketMessage, WorktreeInfo, WSContext } from './types.js';
|
|
11
|
+
import type { GitFileStatus, WebSocketMessage, WorktreeInfo, WSContext } from './types.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A file in the merge target's working tree whose uncommitted state would be
|
|
15
|
+
* lost if the merge proceeds. Used to drive the merge dialog's blocker state
|
|
16
|
+
* before the merge attempt and to translate the post-flight git error if a
|
|
17
|
+
* race lets one slip through.
|
|
18
|
+
*/
|
|
19
|
+
interface MergeBlocker {
|
|
20
|
+
path: string;
|
|
21
|
+
status: GitFileStatus['status'];
|
|
22
|
+
staged: boolean;
|
|
23
|
+
}
|
|
11
24
|
|
|
12
25
|
function persistBoardWorktree(workingDir: string, boardId: string, worktreePath: string | null, branch: string | null): void {
|
|
13
26
|
const pmDir = resolvePmDir(workingDir);
|
|
@@ -43,6 +56,8 @@ export async function handleGitWorktreeMessage(ctx: HandlerContext, ws: WSContex
|
|
|
43
56
|
gitWorktreeMerge: () => handleGitWorktreeMerge(ctx, ws, msg, tabId, gitDir),
|
|
44
57
|
gitMergeAbort: () => handleGitMergeAbort(ctx, ws, tabId, gitDir),
|
|
45
58
|
gitMergeComplete: () => handleGitMergeComplete(ctx, ws, msg, tabId, gitDir),
|
|
59
|
+
gitMergeStashPop: () => handleGitMergeStashPop(ctx, ws, msg, tabId, gitDir),
|
|
60
|
+
gitMergeDiscardBlockers: () => handleGitMergeDiscardBlockers(ctx, ws, msg, tabId, gitDir),
|
|
46
61
|
};
|
|
47
62
|
await handlers[msg.type]?.();
|
|
48
63
|
}
|
|
@@ -214,7 +229,7 @@ export async function handleTabWorktreeSwitch(ctx: HandlerContext, ws: WSContext
|
|
|
214
229
|
persistBoardWorktree(workingDir, resolvedTabId, null, null);
|
|
215
230
|
}
|
|
216
231
|
ctx.send(ws, { type: 'tabWorktreeSwitched', tabId: resolvedTabId, data: { tabId: resolvedTabId, worktreePath: null, branch: null } });
|
|
217
|
-
|
|
232
|
+
refreshScopeAfterWorktreeSwitch(ctx, ws, resolvedTabId, workingDir);
|
|
218
233
|
return;
|
|
219
234
|
}
|
|
220
235
|
|
|
@@ -229,12 +244,38 @@ export async function handleTabWorktreeSwitch(ctx: HandlerContext, ws: WSContext
|
|
|
229
244
|
}
|
|
230
245
|
|
|
231
246
|
ctx.send(ws, { type: 'tabWorktreeSwitched', tabId: resolvedTabId, data: { tabId: resolvedTabId, worktreePath, branch } });
|
|
232
|
-
|
|
247
|
+
refreshScopeAfterWorktreeSwitch(ctx, ws, resolvedTabId, worktreePath);
|
|
233
248
|
} catch (error: unknown) {
|
|
234
249
|
ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
|
|
235
250
|
}
|
|
236
251
|
}
|
|
237
252
|
|
|
253
|
+
/**
|
|
254
|
+
* After a worktree switch, re-fetch everything that's worktree-specific so
|
|
255
|
+
* the client sees a complete, consistent view of the newly-selected workspace
|
|
256
|
+
* from a single `tabWorktreeSwitched` event. Keeping the refresh on the
|
|
257
|
+
* server side means the client has one signal to react to instead of having
|
|
258
|
+
* to orchestrate status/log/... fetches itself.
|
|
259
|
+
*
|
|
260
|
+
* Branches and the worktree list are NOT re-fetched: they're repo-wide, not
|
|
261
|
+
* worktree-specific.
|
|
262
|
+
*
|
|
263
|
+
* Fire-and-forget: the switch itself has already been acknowledged via
|
|
264
|
+
* `tabWorktreeSwitched`. `handleGitStatus` and `handleGitLog` each own their
|
|
265
|
+
* own error handling (they send `gitError` scoped to the correct tabId).
|
|
266
|
+
* Awaiting here would let any unexpected throw escape into the caller's
|
|
267
|
+
* outer try/catch and produce a misleading `gitError` on the original
|
|
268
|
+
* dispatch tabId after success has already been signalled.
|
|
269
|
+
*/
|
|
270
|
+
function refreshScopeAfterWorktreeSwitch(ctx: HandlerContext, ws: WSContext, tabId: string, gitDir: string): void {
|
|
271
|
+
(async () => {
|
|
272
|
+
await handleGitStatus(ctx, ws, tabId, gitDir);
|
|
273
|
+
await handleGitLog(ctx, ws, { type: 'gitLog', tabId, data: { limit: 20 } }, tabId, gitDir);
|
|
274
|
+
})().catch((error: unknown) => {
|
|
275
|
+
console.error('[handleTabWorktreeSwitch] scope refresh failed:', error);
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
238
279
|
async function pushWithUpstreamRetry(
|
|
239
280
|
worktreePath: string,
|
|
240
281
|
pushRemote: string,
|
|
@@ -312,6 +353,64 @@ async function handleGitWorktreeCreatePR(ctx: HandlerContext, ws: WSContext, msg
|
|
|
312
353
|
}
|
|
313
354
|
}
|
|
314
355
|
|
|
356
|
+
/**
|
|
357
|
+
* Files in the target worktree's working tree whose dirty state intersects
|
|
358
|
+
* the set of files the merge would touch. These are the rows that drive the
|
|
359
|
+
* merge dialog's "uncommitted changes on <target>" blocker UI — surfacing
|
|
360
|
+
* them preflight prevents the user from ever hitting git's raw "would be
|
|
361
|
+
* overwritten" error.
|
|
362
|
+
*
|
|
363
|
+
* Detection strategy:
|
|
364
|
+
* - `git status --porcelain=v1` on the target's worktree → all dirty paths
|
|
365
|
+
* - `git diff --name-only target..source` → all paths the merge would touch
|
|
366
|
+
* - intersection = blockers
|
|
367
|
+
*
|
|
368
|
+
* Untracked files are included via the porcelain output (they collide with
|
|
369
|
+
* incoming additions of the same path). Files that are dirty but outside the
|
|
370
|
+
* merge's diff are NOT blockers — git would carry them through cleanly.
|
|
371
|
+
*/
|
|
372
|
+
async function detectMergeBlockers(
|
|
373
|
+
targetWorktreePath: string,
|
|
374
|
+
sourceBranch: string,
|
|
375
|
+
targetBranch: string,
|
|
376
|
+
): Promise<MergeBlocker[]> {
|
|
377
|
+
const statusResult = await executeGitCommand(['status', '--porcelain=v1'], targetWorktreePath);
|
|
378
|
+
if (statusResult.exitCode !== 0) return [];
|
|
379
|
+
const { staged, unstaged, untracked } = parseGitStatus(statusResult.stdout);
|
|
380
|
+
|
|
381
|
+
const diffResult = await executeGitCommand(
|
|
382
|
+
['diff', '--name-only', `${targetBranch}..${sourceBranch}`],
|
|
383
|
+
targetWorktreePath,
|
|
384
|
+
);
|
|
385
|
+
if (diffResult.exitCode !== 0) return [];
|
|
386
|
+
const mergeTouches = new Set(
|
|
387
|
+
diffResult.stdout.split('\n').map(s => s.trim()).filter(Boolean),
|
|
388
|
+
);
|
|
389
|
+
if (mergeTouches.size === 0) return [];
|
|
390
|
+
|
|
391
|
+
// Prefer the worktree (unstaged) status when both index and worktree are
|
|
392
|
+
// dirty — that's what the user would lose. Fall back to staged, then
|
|
393
|
+
// untracked. Order in the result list groups deletions last so the UI can
|
|
394
|
+
// surface them where users expect.
|
|
395
|
+
const seen = new Map<string, MergeBlocker>();
|
|
396
|
+
const consider = (entries: GitFileStatus[]) => {
|
|
397
|
+
for (const entry of entries) {
|
|
398
|
+
if (!mergeTouches.has(entry.path)) continue;
|
|
399
|
+
if (seen.has(entry.path)) continue;
|
|
400
|
+
seen.set(entry.path, { path: entry.path, status: entry.status, staged: entry.staged });
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
consider(unstaged);
|
|
404
|
+
consider(staged);
|
|
405
|
+
consider(untracked);
|
|
406
|
+
|
|
407
|
+
return Array.from(seen.values()).sort((a, b) => {
|
|
408
|
+
if (a.status === 'D' && b.status !== 'D') return 1;
|
|
409
|
+
if (a.status !== 'D' && b.status === 'D') return -1;
|
|
410
|
+
return a.path.localeCompare(b.path);
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
315
414
|
async function handleGitMergePreview(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): Promise<void> {
|
|
316
415
|
try {
|
|
317
416
|
const { sourceBranch, targetBranch } = msg.data || {};
|
|
@@ -347,10 +446,30 @@ async function handleGitMergePreview(ctx: HandlerContext, ws: WSContext, msg: We
|
|
|
347
446
|
return { hash: hash.trim(), message: rest.join('|').trim() };
|
|
348
447
|
});
|
|
349
448
|
|
|
449
|
+
// The merge always lands on the main worktree currently checked out to
|
|
450
|
+
// the target branch. If main isn't on `targetBranch` we can't preflight
|
|
451
|
+
// blockers (the dirty state wouldn't be the relevant one), so skip the
|
|
452
|
+
// check and let the post-flight handler surface the branch-mismatch
|
|
453
|
+
// error like before.
|
|
454
|
+
const mainPath = await resolveMainWorktreePath(workingDir);
|
|
455
|
+
const mainBranchResult = await executeGitCommand(['rev-parse', '--abbrev-ref', 'HEAD'], mainPath);
|
|
456
|
+
const mainOnTarget = mainBranchResult.stdout.trim() === targetBranch;
|
|
457
|
+
const blockers = mainOnTarget
|
|
458
|
+
? await detectMergeBlockers(mainPath, sourceBranch, targetBranch)
|
|
459
|
+
: [];
|
|
460
|
+
|
|
350
461
|
ctx.send(ws, {
|
|
351
462
|
type: 'gitMergePreviewResult',
|
|
352
463
|
tabId,
|
|
353
|
-
data: {
|
|
464
|
+
data: {
|
|
465
|
+
clean,
|
|
466
|
+
conflicts,
|
|
467
|
+
stat,
|
|
468
|
+
commits,
|
|
469
|
+
ahead: commits.length,
|
|
470
|
+
targetWorktreePath: mainPath,
|
|
471
|
+
targetWorktreeBlockers: blockers,
|
|
472
|
+
},
|
|
354
473
|
});
|
|
355
474
|
} catch (error: unknown) {
|
|
356
475
|
ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
|
|
@@ -455,9 +574,103 @@ export function findWorktreePathForBranch(porcelainOutput: string, branchName: s
|
|
|
455
574
|
return null;
|
|
456
575
|
}
|
|
457
576
|
|
|
577
|
+
/**
|
|
578
|
+
* Parse a git stderr buffer for the "your local changes / untracked working
|
|
579
|
+
* tree files would be overwritten by merge" message. Returned paths feed the
|
|
580
|
+
* web client's blocker UI when a merge slips through preflight (race against
|
|
581
|
+
* a teammate's auto-formatter, stale status, etc.).
|
|
582
|
+
*/
|
|
583
|
+
function parseOverwritePaths(stderr: string): string[] {
|
|
584
|
+
const lines = stderr.split('\n');
|
|
585
|
+
const headerIdx = lines.findIndex(l =>
|
|
586
|
+
/your local changes to the following files would be overwritten/i.test(l) ||
|
|
587
|
+
/untracked working tree files would be overwritten/i.test(l),
|
|
588
|
+
);
|
|
589
|
+
if (headerIdx < 0) return [];
|
|
590
|
+
const paths: string[] = [];
|
|
591
|
+
for (let i = headerIdx + 1; i < lines.length; i++) {
|
|
592
|
+
const line = lines[i];
|
|
593
|
+
const trimmed = line.trim();
|
|
594
|
+
if (!trimmed) continue;
|
|
595
|
+
if (/^(merge with strategy|aborting|please commit)/i.test(trimmed)) break;
|
|
596
|
+
// Git indents each path with a tab; tolerate spaces too.
|
|
597
|
+
if (/^[\t ]/.test(line)) {
|
|
598
|
+
paths.push(trimmed);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return paths;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const STASH_MESSAGE_TAG = 'mstro:pre-merge';
|
|
605
|
+
|
|
606
|
+
async function pushMergeStash(targetWorktreePath: string, sourceBranch: string, targetBranch: string): Promise<{ ref: string; sha: string; message: string } | null> {
|
|
607
|
+
const message = `${STASH_MESSAGE_TAG} ${targetBranch} <- ${sourceBranch} @ ${new Date().toISOString()}`;
|
|
608
|
+
const result = await executeGitCommand(['stash', 'push', '-u', '-m', message], targetWorktreePath);
|
|
609
|
+
if (result.exitCode !== 0) return null;
|
|
610
|
+
// "No local changes to save" exits 0 but does not push a stash; detect by
|
|
611
|
+
// checking whether stash@{0}'s message matches what we just wrote.
|
|
612
|
+
const top = await executeGitCommand(['stash', 'list', '-n', '1', '--format=%H%x09%gs'], targetWorktreePath);
|
|
613
|
+
if (top.exitCode !== 0) return null;
|
|
614
|
+
const [sha, ...rest] = top.stdout.trim().split('\t');
|
|
615
|
+
const stashedMsg = rest.join('\t');
|
|
616
|
+
if (!sha || !stashedMsg.includes(STASH_MESSAGE_TAG)) return null;
|
|
617
|
+
return { ref: 'stash@{0}', sha, message };
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
async function popMergeStashBySha(targetWorktreePath: string, sha: string): Promise<{ exitCode: number; error?: string }> {
|
|
621
|
+
const list = await executeGitCommand(['stash', 'list', '--format=%gd%x09%H'], targetWorktreePath);
|
|
622
|
+
if (list.exitCode !== 0) return { exitCode: list.exitCode, error: list.stderr || 'Failed to read stash list' };
|
|
623
|
+
const ref = list.stdout.split('\n').map(l => l.split('\t')).find(([, h]) => h === sha)?.[0];
|
|
624
|
+
if (!ref) return { exitCode: 1, error: 'Stash no longer exists — it may have already been popped or dropped.' };
|
|
625
|
+
const pop = await executeGitCommand(['stash', 'pop', ref], targetWorktreePath);
|
|
626
|
+
return { exitCode: pop.exitCode, error: pop.exitCode !== 0 ? pop.stderr : undefined };
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Decide what shape of merge-failure payload to send the client. Splits the
|
|
631
|
+
* three error modes — index conflict, target-worktree blocker, generic — out
|
|
632
|
+
* of the main handler so each is a self-contained branch.
|
|
633
|
+
*/
|
|
634
|
+
async function buildMergeFailurePayload(
|
|
635
|
+
mainPath: string,
|
|
636
|
+
sourceBranch: string,
|
|
637
|
+
targetBranch: string,
|
|
638
|
+
mergeError: string | undefined,
|
|
639
|
+
): Promise<Record<string, unknown>> {
|
|
640
|
+
const conflictFiles = await detectMergeConflicts(mainPath);
|
|
641
|
+
if (conflictFiles.length > 0) {
|
|
642
|
+
return { success: false, conflictFiles, targetWorktreePath: mainPath };
|
|
643
|
+
}
|
|
644
|
+
const overwritten = parseOverwritePaths(mergeError || '');
|
|
645
|
+
if (overwritten.length > 0) {
|
|
646
|
+
const blockers = await detectMergeBlockers(mainPath, sourceBranch, targetBranch);
|
|
647
|
+
const targetWorktreeBlockers: MergeBlocker[] = blockers.length > 0
|
|
648
|
+
? blockers
|
|
649
|
+
: overwritten.map(p => ({ path: p, status: 'M' as const, staged: false }));
|
|
650
|
+
return { success: false, targetWorktreePath: mainPath, targetWorktreeBlockers };
|
|
651
|
+
}
|
|
652
|
+
return { success: false, error: mergeError || 'Merge failed', targetWorktreePath: mainPath };
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function buildMergeSuccessPayload(
|
|
656
|
+
mainPath: string,
|
|
657
|
+
mergeCommit: string,
|
|
658
|
+
warnings: string[],
|
|
659
|
+
stash: { ref: string; sha: string; message: string } | null,
|
|
660
|
+
): Record<string, unknown> {
|
|
661
|
+
const data: Record<string, unknown> = { success: true, mergeCommit, targetWorktreePath: mainPath };
|
|
662
|
+
if (warnings.length > 0) data.warnings = warnings;
|
|
663
|
+
if (stash) {
|
|
664
|
+
data.stashRef = stash.ref;
|
|
665
|
+
data.stashSha = stash.sha;
|
|
666
|
+
data.stashMessage = stash.message;
|
|
667
|
+
}
|
|
668
|
+
return data;
|
|
669
|
+
}
|
|
670
|
+
|
|
458
671
|
async function handleGitWorktreeMerge(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): Promise<void> {
|
|
459
672
|
try {
|
|
460
|
-
const { sourceBranch, targetBranch, strategy, commitMessage, deleteWorktree, deleteBranch } = msg.data || {};
|
|
673
|
+
const { sourceBranch, targetBranch, strategy, commitMessage, deleteWorktree, deleteBranch, stashFirst } = msg.data || {};
|
|
461
674
|
if (!sourceBranch || !targetBranch || !strategy) {
|
|
462
675
|
ctx.send(ws, { type: 'gitError', tabId, data: { error: 'Source branch, target branch, and strategy are required' } });
|
|
463
676
|
return;
|
|
@@ -467,40 +680,97 @@ async function handleGitWorktreeMerge(ctx: HandlerContext, ws: WSContext, msg: W
|
|
|
467
680
|
|
|
468
681
|
const mainBranchResult = await executeGitCommand(['rev-parse', '--abbrev-ref', 'HEAD'], mainPath);
|
|
469
682
|
if (mainBranchResult.stdout.trim() !== targetBranch) {
|
|
470
|
-
ctx.send(ws, { type: 'gitWorktreeMergeResult', tabId, data: { success: false, error: `Switch the main worktree to "${targetBranch}" before merging
|
|
683
|
+
ctx.send(ws, { type: 'gitWorktreeMergeResult', tabId, data: { success: false, error: `Switch the main worktree to "${targetBranch}" before merging`, targetWorktreePath: mainPath } });
|
|
471
684
|
return;
|
|
472
685
|
}
|
|
473
686
|
|
|
474
|
-
|
|
687
|
+
// Stash first if requested. A no-op result (nothing to stash, e.g. user
|
|
688
|
+
// already cleaned up between preflight and merge) is fine — we still
|
|
689
|
+
// proceed with the merge.
|
|
690
|
+
const stash = stashFirst ? await pushMergeStash(mainPath, sourceBranch, targetBranch) : null;
|
|
475
691
|
|
|
692
|
+
const headBefore = await executeGitCommand(['rev-parse', 'HEAD'], mainPath);
|
|
476
693
|
const mergeResult = await executeMergeStrategy(strategy, sourceBranch, commitMessage, mainPath);
|
|
694
|
+
|
|
477
695
|
if (mergeResult.exitCode !== 0) {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
696
|
+
// Best-effort restore of the user's working tree before reporting.
|
|
697
|
+
// If pop conflicts, the stash stays around and the user can recover it
|
|
698
|
+
// manually — we still surface the original merge error.
|
|
699
|
+
if (stash) await popMergeStashBySha(mainPath, stash.sha).catch(() => undefined);
|
|
700
|
+
const data = await buildMergeFailurePayload(mainPath, sourceBranch, targetBranch, mergeResult.error);
|
|
482
701
|
ctx.send(ws, { type: 'gitWorktreeMergeResult', tabId, data });
|
|
483
702
|
return;
|
|
484
703
|
}
|
|
485
704
|
|
|
486
705
|
const headAfter = await executeGitCommand(['rev-parse', 'HEAD'], mainPath);
|
|
487
706
|
if (headBefore.stdout.trim() === headAfter.stdout.trim()) {
|
|
488
|
-
|
|
707
|
+
if (stash) await popMergeStashBySha(mainPath, stash.sha).catch(() => undefined);
|
|
708
|
+
ctx.send(ws, { type: 'gitWorktreeMergeResult', tabId, data: { success: false, error: `Already up to date — "${sourceBranch}" has no new commits to merge into "${targetBranch}"`, targetWorktreePath: mainPath } });
|
|
489
709
|
return;
|
|
490
710
|
}
|
|
491
711
|
|
|
492
712
|
const commitHashResult = await executeGitCommand(['rev-parse', '--short', 'HEAD'], mainPath);
|
|
493
713
|
const { warnings, removedWorktreePath } = await cleanupAfterMerge(mainPath, sourceBranch, strategy, !!deleteWorktree, !!deleteBranch);
|
|
714
|
+
if (removedWorktreePath) cleanupWorktreeReferences(ctx, workingDir, removedWorktreePath);
|
|
494
715
|
|
|
495
|
-
|
|
496
|
-
|
|
716
|
+
const data = buildMergeSuccessPayload(mainPath, commitHashResult.stdout.trim(), warnings, stash);
|
|
717
|
+
ctx.send(ws, { type: 'gitWorktreeMergeResult', tabId, data });
|
|
718
|
+
} catch (error: unknown) {
|
|
719
|
+
ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
async function handleGitMergeStashPop(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): Promise<void> {
|
|
724
|
+
try {
|
|
725
|
+
const { stashSha, targetWorktreePath } = msg.data || {};
|
|
726
|
+
if (!stashSha) {
|
|
727
|
+
ctx.send(ws, { type: 'gitMergeStashPopped', tabId, data: { success: false, error: 'Missing stash reference' } });
|
|
728
|
+
return;
|
|
497
729
|
}
|
|
730
|
+
const path = targetWorktreePath || (await resolveMainWorktreePath(workingDir));
|
|
731
|
+
const result = await popMergeStashBySha(path, stashSha);
|
|
732
|
+
if (result.exitCode !== 0) {
|
|
733
|
+
ctx.send(ws, { type: 'gitMergeStashPopped', tabId, data: { success: false, error: result.error || 'Failed to pop stash', targetWorktreePath: path } });
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
ctx.send(ws, { type: 'gitMergeStashPopped', tabId, data: { success: true, targetWorktreePath: path } });
|
|
737
|
+
// Refresh status so the file explorer / git view reflects the restored changes.
|
|
738
|
+
handleGitStatus(ctx, ws, tabId, path);
|
|
739
|
+
} catch (error: unknown) {
|
|
740
|
+
ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
|
|
741
|
+
}
|
|
742
|
+
}
|
|
498
743
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
744
|
+
async function handleGitMergeDiscardBlockers(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): Promise<void> {
|
|
745
|
+
try {
|
|
746
|
+
const { paths, targetWorktreePath } = msg.data || {};
|
|
747
|
+
if (!Array.isArray(paths) || paths.length === 0) {
|
|
748
|
+
ctx.send(ws, { type: 'gitMergeBlockersDiscarded', tabId, data: { success: false, error: 'No paths to discard' } });
|
|
749
|
+
return;
|
|
502
750
|
}
|
|
503
|
-
|
|
751
|
+
const path = targetWorktreePath || (await resolveMainWorktreePath(workingDir));
|
|
752
|
+
|
|
753
|
+
// Restore tracked files (modifications, deletions, stages) from HEAD.
|
|
754
|
+
// `git checkout HEAD -- <paths>` overwrites both index and worktree, which
|
|
755
|
+
// is exactly the "discard" semantics the UI promised the user.
|
|
756
|
+
const checkoutResult = await executeGitCommand(['checkout', 'HEAD', '--', ...paths], path);
|
|
757
|
+
// Untracked files won't be touched by `checkout HEAD --` (git refuses with
|
|
758
|
+
// "did not match any file(s) known to git" when ALL paths are untracked,
|
|
759
|
+
// but mixed lists succeed for the tracked subset). Run `git clean -f` on
|
|
760
|
+
// the originally-supplied list to remove any untracked leftovers — git
|
|
761
|
+
// ignores anything that's already gone, so this is safe to run after.
|
|
762
|
+
const cleanResult = await executeGitCommand(['clean', '-f', '--', ...paths], path);
|
|
763
|
+
|
|
764
|
+
if (checkoutResult.exitCode !== 0 && cleanResult.exitCode !== 0) {
|
|
765
|
+
ctx.send(ws, {
|
|
766
|
+
type: 'gitMergeBlockersDiscarded',
|
|
767
|
+
tabId,
|
|
768
|
+
data: { success: false, error: checkoutResult.stderr || cleanResult.stderr || 'Failed to discard changes', targetWorktreePath: path },
|
|
769
|
+
});
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
ctx.send(ws, { type: 'gitMergeBlockersDiscarded', tabId, data: { success: true, targetWorktreePath: path, paths } });
|
|
773
|
+
handleGitStatus(ctx, ws, tabId, path);
|
|
504
774
|
} catch (error: unknown) {
|
|
505
775
|
ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
|
|
506
776
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
// Copyright (c) 2025-present Mstro, Inc. All rights reserved.
|
|
2
|
-
// Licensed under the MIT License. See LICENSE file for details.
|
|
3
2
|
|
|
4
3
|
import type { ChildProcess } from 'node:child_process';
|
|
5
4
|
import type { ImprovisationSessionManager } from '../../cli/improvisation-session-manager.js';
|
|
6
5
|
import type { AutocompleteService } from './autocomplete.js';
|
|
7
6
|
import type { FileUploadHandler } from './file-upload-handler.js';
|
|
8
7
|
import type { GitHeadWatcher } from './git-head-watcher.js';
|
|
8
|
+
import type { MsgIdTracker } from './msg-id-tracker.js';
|
|
9
9
|
import type { SessionRegistry } from './session-registry.js';
|
|
10
10
|
import type { SkillsWatcher } from './skill-watcher.js';
|
|
11
|
+
import type { TabEventBufferRegistry } from './tab-event-buffer.js';
|
|
11
12
|
import type { WebSocketResponse, WSContext } from './types.js';
|
|
12
13
|
|
|
13
14
|
export interface UsageReport {
|
|
@@ -37,6 +38,19 @@ export interface HandlerContext {
|
|
|
37
38
|
fileUploadHandler: FileUploadHandler | null;
|
|
38
39
|
gitHeadWatcher: GitHeadWatcher | null;
|
|
39
40
|
skillsWatcher: SkillsWatcher | null;
|
|
41
|
+
/**
|
|
42
|
+
* Per-tab replay buffer for tab-scoped broadcasts. Populated by
|
|
43
|
+
* `broadcastTabEvent` (see `tab-broadcast.ts`) so a web client rejoining a
|
|
44
|
+
* tab after a reconnect can request replay of anything it missed during
|
|
45
|
+
* the transport gap. See `tab-event-buffer.ts`.
|
|
46
|
+
*/
|
|
47
|
+
tabEventBuffers: TabEventBufferRegistry;
|
|
48
|
+
/**
|
|
49
|
+
* Idempotency tracker for `execute` `msgId`s. Lets the web replay the
|
|
50
|
+
* same prompt across reconnects without causing double execution — the
|
|
51
|
+
* CLI still acks, but skips running the prompt a second time.
|
|
52
|
+
*/
|
|
53
|
+
msgIdTracker: MsgIdTracker;
|
|
40
54
|
|
|
41
55
|
// Registry access
|
|
42
56
|
getRegistry(workingDir: string): SessionRegistry;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// Copyright (c) 2025-present Mstro, Inc. All rights reserved.
|
|
2
|
-
// Licensed under the MIT License. See LICENSE file for details.
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* WebSocket Handler for Improvisation Sessions
|
|
@@ -14,20 +13,24 @@ import { homedir } from 'node:os';
|
|
|
14
13
|
import { dirname, join } from 'node:path';
|
|
15
14
|
import type { ImprovisationSessionManager } from '../../cli/improvisation-session-manager.js';
|
|
16
15
|
import { captureException } from '../sentry.js';
|
|
16
|
+
import { getPTYManager } from '../terminal/pty-manager.js';
|
|
17
17
|
import { AutocompleteService } from './autocomplete.js';
|
|
18
|
+
import { FileDownloadHandler } from './file-download-handler.js';
|
|
18
19
|
import { handleFileExplorerMessage, handleFileMessage } from './file-explorer-handlers.js';
|
|
19
20
|
import { FileUploadHandler } from './file-upload-handler.js';
|
|
20
21
|
import { handleGitMessage } from './git-handlers.js';
|
|
21
22
|
import { GitHeadWatcher } from './git-head-watcher.js';
|
|
22
23
|
import type { HandlerContext, UsageReporter } from './handler-context.js';
|
|
24
|
+
import { MsgIdTracker } from './msg-id-tracker.js';
|
|
23
25
|
import { handlePlanMessage } from './plan-handlers.js';
|
|
24
26
|
import { handleQualityMessage } from './quality-handlers.js';
|
|
25
|
-
import { handleHistoryMessage, handleSessionMessage, initializeTab, resumeHistoricalSession } from './session-handlers.js';
|
|
27
|
+
import { handleHistoryMessage, handleSessionMessage, initializeTab, restoreWorktreeFromRegistry, resumeHistoricalSession } from './session-handlers.js';
|
|
26
28
|
import { SessionRegistry } from './session-registry.js';
|
|
27
29
|
import { generateNotificationSummary, handleGetSettings, handleUpdateSettings } from './settings-handlers.js';
|
|
28
30
|
import { handleListSkills } from './skill-handlers.js';
|
|
29
31
|
import { SkillsWatcher } from './skill-watcher.js';
|
|
30
|
-
import {
|
|
32
|
+
import { TabEventBufferRegistry } from './tab-event-buffer.js';
|
|
33
|
+
import { handleCreateTab, handleGetActiveTabs, handleMarkTabViewed, handleRemoveTab, handleReorderTabs, handleSyncTabMeta } from './tab-handlers.js';
|
|
31
34
|
import { cleanupTerminalSubscribers, handleTerminalMessage } from './terminal-handlers.js';
|
|
32
35
|
import type { FrecencyData, WebSocketMessage, WebSocketResponse, WSContext } from './types.js';
|
|
33
36
|
|
|
@@ -47,8 +50,11 @@ export class WebSocketImproviseHandler implements HandlerContext {
|
|
|
47
50
|
terminalListenerCleanups: Map<string, () => void> = new Map();
|
|
48
51
|
terminalSubscribers: Map<string, Set<WSContext>> = new Map();
|
|
49
52
|
fileUploadHandler: FileUploadHandler | null = null;
|
|
53
|
+
fileDownloadHandler: FileDownloadHandler | null = null;
|
|
50
54
|
gitHeadWatcher: GitHeadWatcher | null = null;
|
|
51
55
|
skillsWatcher: SkillsWatcher | null = null;
|
|
56
|
+
tabEventBuffers: TabEventBufferRegistry = new TabEventBufferRegistry();
|
|
57
|
+
msgIdTracker: MsgIdTracker = new MsgIdTracker();
|
|
52
58
|
|
|
53
59
|
constructor() {
|
|
54
60
|
this.frecencyPath = join(homedir(), '.mstro', 'autocomplete-frecency.json');
|
|
@@ -146,7 +152,7 @@ export class WebSocketImproviseHandler implements HandlerContext {
|
|
|
146
152
|
}
|
|
147
153
|
|
|
148
154
|
/** Dispatch table mapping message types to domain handlers. Built once, looked up per message. */
|
|
149
|
-
private static readonly DISPATCH: Record<string, 'session' | 'history' | 'file' | 'terminal' | 'fileExplorer' | 'git' | 'quality' | 'plan' | 'fileUpload'> = {
|
|
155
|
+
private static readonly DISPATCH: Record<string, 'session' | 'history' | 'file' | 'terminal' | 'fileExplorer' | 'git' | 'quality' | 'plan' | 'fileUpload' | 'fileDownload'> = {
|
|
150
156
|
// Session
|
|
151
157
|
execute: 'session', cancel: 'session', getHistory: 'session', new: 'session', approve: 'session', reject: 'session',
|
|
152
158
|
// History
|
|
@@ -165,6 +171,8 @@ export class WebSocketImproviseHandler implements HandlerContext {
|
|
|
165
171
|
planInit: 'plan', planGetState: 'plan', planListIssues: 'plan', planGetIssue: 'plan', planGetSprint: 'plan', planGetMilestone: 'plan', planCreateIssue: 'plan', planUpdateIssue: 'plan', planDeleteIssue: 'plan', planScaffold: 'plan', planPrompt: 'plan', planExecute: 'plan', planExecuteEpic: 'plan', planPause: 'plan', planStop: 'plan', planResume: 'plan', planCreateBoard: 'plan', planUpdateBoard: 'plan', planArchiveBoard: 'plan', planGetBoard: 'plan', planGetBoardState: 'plan', planReorderBoards: 'plan', planSetActiveBoard: 'plan', planGetBoardArtifacts: 'plan', planCreateSprint: 'plan', planActivateSprint: 'plan', planCompleteSprint: 'plan', planGetSprintArtifacts: 'plan', chatToBoard: 'plan',
|
|
166
172
|
// File upload
|
|
167
173
|
fileUploadStart: 'fileUpload', fileUploadChunk: 'fileUpload', fileUploadComplete: 'fileUpload', fileUploadCancel: 'fileUpload',
|
|
174
|
+
// File download (chunked streaming for large binaries)
|
|
175
|
+
fileDownloadStart: 'fileDownload', fileDownloadCancel: 'fileDownload',
|
|
168
176
|
};
|
|
169
177
|
|
|
170
178
|
private async dispatchMessage(ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string, permission?: 'view'): Promise<void> {
|
|
@@ -174,10 +182,10 @@ export class WebSocketImproviseHandler implements HandlerContext {
|
|
|
174
182
|
this.send(ws, { type: 'pong', tabId });
|
|
175
183
|
return;
|
|
176
184
|
case 'initTab':
|
|
177
|
-
return void await initializeTab(this, ws, tabId, workingDir, msg.data?.tabName);
|
|
185
|
+
return void await initializeTab(this, ws, tabId, workingDir, msg.data?.tabName, msg.data);
|
|
178
186
|
case 'resumeSession':
|
|
179
187
|
if (!msg.data?.historicalSessionId) throw new Error('Historical session ID is required');
|
|
180
|
-
return void await resumeHistoricalSession(this, ws, tabId, workingDir, msg.data.historicalSessionId);
|
|
188
|
+
return void await resumeHistoricalSession(this, ws, tabId, workingDir, msg.data.historicalSessionId, msg.data);
|
|
181
189
|
case 'requestNotificationSummary':
|
|
182
190
|
if (!msg.data?.prompt || !msg.data?.output) throw new Error('Prompt and output are required for notification summary');
|
|
183
191
|
return void await generateNotificationSummary(this, ws, tabId, msg.data.prompt, msg.data.output, workingDir);
|
|
@@ -189,8 +197,6 @@ export class WebSocketImproviseHandler implements HandlerContext {
|
|
|
189
197
|
return handleReorderTabs(this, ws, workingDir, msg.data?.tabOrder);
|
|
190
198
|
case 'syncTabMeta':
|
|
191
199
|
return handleSyncTabMeta(this, ws, msg, tabId, workingDir);
|
|
192
|
-
case 'syncPromptText':
|
|
193
|
-
return handleSyncPromptText(this, ws, msg, tabId);
|
|
194
200
|
case 'removeTab':
|
|
195
201
|
return handleRemoveTab(this, ws, tabId, workingDir);
|
|
196
202
|
case 'markTabViewed':
|
|
@@ -208,6 +214,14 @@ export class WebSocketImproviseHandler implements HandlerContext {
|
|
|
208
214
|
const domain = WebSocketImproviseHandler.DISPATCH[msg.type];
|
|
209
215
|
if (!domain) throw new Error(`Unknown message type: ${msg.type}`);
|
|
210
216
|
|
|
217
|
+
// Hydrate worktree state from the registry before any domain handler
|
|
218
|
+
// reads it, so git/file/autocomplete ops route to the tab's worktree
|
|
219
|
+
// even when they arrive before the initTab handshake completes. The
|
|
220
|
+
// registry is authoritative; the in-memory Map is just a cache.
|
|
221
|
+
if (msg.tabId && !this.gitDirectories.has(tabId)) {
|
|
222
|
+
restoreWorktreeFromRegistry(this, this.getRegistry(workingDir), tabId);
|
|
223
|
+
}
|
|
224
|
+
|
|
211
225
|
// Resolve effective working directory: use worktree path if tab is on a worktree
|
|
212
226
|
const effectiveDir = this.gitDirectories.get(tabId) || workingDir;
|
|
213
227
|
|
|
@@ -220,20 +234,38 @@ export class WebSocketImproviseHandler implements HandlerContext {
|
|
|
220
234
|
case 'git': return handleGitMessage(this, ws, msg, tabId, workingDir);
|
|
221
235
|
case 'quality': return handleQualityMessage(this, ws, msg, tabId, workingDir, permission);
|
|
222
236
|
case 'plan': return handlePlanMessage(this, ws, msg, tabId, workingDir, permission);
|
|
223
|
-
case 'fileUpload': return this.handleFileUploadMessage(ws, msg, tabId,
|
|
237
|
+
case 'fileUpload': return this.handleFileUploadMessage(ws, msg, tabId, effectiveDir, permission);
|
|
238
|
+
case 'fileDownload': return this.handleFileDownloadMessage(ws, msg, tabId, effectiveDir);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private handleFileDownloadMessage(ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): void {
|
|
243
|
+
if (!this.fileDownloadHandler) {
|
|
244
|
+
this.fileDownloadHandler = new FileDownloadHandler(workingDir);
|
|
245
|
+
}
|
|
246
|
+
const handler = this.fileDownloadHandler;
|
|
247
|
+
const send = this.send.bind(this);
|
|
248
|
+
|
|
249
|
+
switch (msg.type) {
|
|
250
|
+
case 'fileDownloadStart':
|
|
251
|
+
handler.handleDownloadStart(ws, send, tabId, msg.data);
|
|
252
|
+
break;
|
|
253
|
+
case 'fileDownloadCancel':
|
|
254
|
+
handler.handleDownloadCancel(ws, send, tabId, msg.data);
|
|
255
|
+
break;
|
|
224
256
|
}
|
|
225
257
|
}
|
|
226
258
|
|
|
227
259
|
private handleFileUploadMessage(ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string, permission?: 'view'): void {
|
|
228
260
|
if (!this.fileUploadHandler) {
|
|
229
|
-
this.fileUploadHandler = new FileUploadHandler(
|
|
261
|
+
this.fileUploadHandler = new FileUploadHandler(this);
|
|
230
262
|
}
|
|
231
263
|
const handler = this.fileUploadHandler;
|
|
232
264
|
const send = this.send.bind(this);
|
|
233
265
|
|
|
234
266
|
switch (msg.type) {
|
|
235
267
|
case 'fileUploadStart':
|
|
236
|
-
handler.handleUploadStart(ws, send, tabId, msg.data, permission);
|
|
268
|
+
handler.handleUploadStart(ws, send, tabId, msg.data, workingDir, permission);
|
|
237
269
|
break;
|
|
238
270
|
case 'fileUploadChunk':
|
|
239
271
|
handler.handleUploadChunk(ws, send, tabId, msg.data);
|
|
@@ -262,6 +294,10 @@ export class WebSocketImproviseHandler implements HandlerContext {
|
|
|
262
294
|
this.fileUploadHandler.destroy();
|
|
263
295
|
this.fileUploadHandler = null;
|
|
264
296
|
}
|
|
297
|
+
if (this.fileDownloadHandler) {
|
|
298
|
+
this.fileDownloadHandler.destroy();
|
|
299
|
+
this.fileDownloadHandler = null;
|
|
300
|
+
}
|
|
265
301
|
if (this.gitHeadWatcher) {
|
|
266
302
|
this.gitHeadWatcher.stop();
|
|
267
303
|
this.gitHeadWatcher = null;
|
|
@@ -270,18 +306,45 @@ export class WebSocketImproviseHandler implements HandlerContext {
|
|
|
270
306
|
this.skillsWatcher.stop();
|
|
271
307
|
this.skillsWatcher = null;
|
|
272
308
|
}
|
|
309
|
+
|
|
310
|
+
// Close orphan PTYs when no web client is watching any more.
|
|
311
|
+
//
|
|
312
|
+
// Prior behavior: PTYs survived until the user typed `exit` or the
|
|
313
|
+
// CLI process died. A page refresh / browser tab close / view-switch
|
|
314
|
+
// would leak the pty, and after a few of these the user would have
|
|
315
|
+
// tens of zombie shells competing for I/O bandwidth, which manifests
|
|
316
|
+
// as the app feeling "unresponsive" — interactive operations starve
|
|
317
|
+
// because the relay socket is saturated streaming output for ptys
|
|
318
|
+
// that no UI is rendering.
|
|
319
|
+
//
|
|
320
|
+
// The active-session preservation in `cleanupConnectionResources`
|
|
321
|
+
// above is intentionally separate: improvise sessions can produce
|
|
322
|
+
// useful work while the browser is closed (Claude Code keeps running
|
|
323
|
+
// in the background); a shell process can't, by construction. So we
|
|
324
|
+
// preserve the former and reap the latter.
|
|
325
|
+
const ptyManager = getPTYManager();
|
|
326
|
+
const activeTerminals = ptyManager.getActiveTerminals();
|
|
327
|
+
if (activeTerminals.length > 0) {
|
|
328
|
+
console.log(`[handler] No web subscribers — closing ${activeTerminals.length} orphan PTY${activeTerminals.length === 1 ? '' : 's'}`);
|
|
329
|
+
ptyManager.closeAll();
|
|
330
|
+
}
|
|
273
331
|
}
|
|
274
332
|
}
|
|
275
333
|
|
|
276
334
|
private cleanupConnectionResources(tabMap: Map<string, string>): void {
|
|
277
|
-
//
|
|
335
|
+
// Preserve actively-executing sessions across web reconnects. The runner
|
|
336
|
+
// is still producing output, and the new web connection will re-attach
|
|
337
|
+
// via session-handlers.ts::getSession (or initializeTab → reattachSession)
|
|
338
|
+
// which rebinds listeners and replays executionEventLog. Destroying the
|
|
339
|
+
// session here would orphan the runner and silently drop all streamed
|
|
340
|
+
// output for the rest of the prompt.
|
|
278
341
|
const sessionIds = new Set(tabMap.values());
|
|
279
342
|
for (const sessionId of sessionIds) {
|
|
280
343
|
const session = this.sessions.get(sessionId);
|
|
281
|
-
if (session)
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
344
|
+
if (!session) continue;
|
|
345
|
+
if (session.isExecuting) continue;
|
|
346
|
+
session.destroy();
|
|
347
|
+
this.sessions.delete(sessionId);
|
|
285
348
|
}
|
|
286
349
|
// Kill search processes owned by this connection's tabs
|
|
287
350
|
for (const tabId of tabMap.keys()) {
|