mstro-app 0.4.2 → 0.4.4
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/bin/mstro.js +119 -40
- package/dist/server/cli/headless/claude-invoker-process.d.ts +11 -0
- package/dist/server/cli/headless/claude-invoker-process.d.ts.map +1 -0
- package/dist/server/cli/headless/claude-invoker-process.js +140 -0
- package/dist/server/cli/headless/claude-invoker-process.js.map +1 -0
- package/dist/server/cli/headless/claude-invoker-stall.d.ts +40 -0
- package/dist/server/cli/headless/claude-invoker-stall.d.ts.map +1 -0
- package/dist/server/cli/headless/claude-invoker-stall.js +98 -0
- package/dist/server/cli/headless/claude-invoker-stall.js.map +1 -0
- package/dist/server/cli/headless/claude-invoker-stream.d.ts +44 -0
- package/dist/server/cli/headless/claude-invoker-stream.d.ts.map +1 -0
- package/dist/server/cli/headless/claude-invoker-stream.js +276 -0
- package/dist/server/cli/headless/claude-invoker-stream.js.map +1 -0
- package/dist/server/cli/headless/claude-invoker-tools.d.ts +21 -0
- package/dist/server/cli/headless/claude-invoker-tools.d.ts.map +1 -0
- package/dist/server/cli/headless/claude-invoker-tools.js +137 -0
- package/dist/server/cli/headless/claude-invoker-tools.js.map +1 -0
- package/dist/server/cli/headless/claude-invoker.d.ts +6 -4
- package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker.js +10 -804
- package/dist/server/cli/headless/claude-invoker.js.map +1 -1
- package/dist/server/cli/headless/haiku-assessments.d.ts +62 -0
- package/dist/server/cli/headless/haiku-assessments.d.ts.map +1 -0
- package/dist/server/cli/headless/haiku-assessments.js +281 -0
- package/dist/server/cli/headless/haiku-assessments.js.map +1 -0
- package/dist/server/cli/headless/headless-logger.d.ts +3 -2
- package/dist/server/cli/headless/headless-logger.d.ts.map +1 -1
- package/dist/server/cli/headless/headless-logger.js +28 -5
- package/dist/server/cli/headless/headless-logger.js.map +1 -1
- package/dist/server/cli/headless/native-timeout-detector.d.ts +44 -0
- package/dist/server/cli/headless/native-timeout-detector.d.ts.map +1 -0
- package/dist/server/cli/headless/native-timeout-detector.js +99 -0
- package/dist/server/cli/headless/native-timeout-detector.js.map +1 -0
- package/dist/server/cli/headless/stall-assessor.d.ts +2 -110
- package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
- package/dist/server/cli/headless/stall-assessor.js +65 -457
- package/dist/server/cli/headless/stall-assessor.js.map +1 -1
- package/dist/server/cli/headless/types.d.ts +4 -1
- package/dist/server/cli/headless/types.d.ts.map +1 -1
- package/dist/server/cli/improvisation-attachments.d.ts +21 -0
- package/dist/server/cli/improvisation-attachments.d.ts.map +1 -0
- package/dist/server/cli/improvisation-attachments.js +116 -0
- package/dist/server/cli/improvisation-attachments.js.map +1 -0
- package/dist/server/cli/improvisation-retry.d.ts +52 -0
- package/dist/server/cli/improvisation-retry.d.ts.map +1 -0
- package/dist/server/cli/improvisation-retry.js +434 -0
- package/dist/server/cli/improvisation-retry.js.map +1 -0
- package/dist/server/cli/improvisation-session-manager.d.ts +10 -266
- package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
- package/dist/server/cli/improvisation-session-manager.js +117 -1079
- package/dist/server/cli/improvisation-session-manager.js.map +1 -1
- package/dist/server/cli/improvisation-types.d.ts +86 -0
- package/dist/server/cli/improvisation-types.d.ts.map +1 -0
- package/dist/server/cli/improvisation-types.js +10 -0
- package/dist/server/cli/improvisation-types.js.map +1 -0
- package/dist/server/cli/prompt-builders.d.ts +68 -0
- package/dist/server/cli/prompt-builders.d.ts.map +1 -0
- package/dist/server/cli/prompt-builders.js +312 -0
- package/dist/server/cli/prompt-builders.js.map +1 -0
- package/dist/server/index.js +33 -212
- package/dist/server/index.js.map +1 -1
- package/dist/server/mcp/bouncer-haiku.d.ts +10 -0
- package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -0
- package/dist/server/mcp/bouncer-haiku.js +152 -0
- package/dist/server/mcp/bouncer-haiku.js.map +1 -0
- package/dist/server/mcp/bouncer-integration.d.ts +3 -4
- package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
- package/dist/server/mcp/bouncer-integration.js +50 -196
- package/dist/server/mcp/bouncer-integration.js.map +1 -1
- package/dist/server/mcp/security-analysis.d.ts +38 -0
- package/dist/server/mcp/security-analysis.d.ts.map +1 -0
- package/dist/server/mcp/security-analysis.js +183 -0
- package/dist/server/mcp/security-analysis.js.map +1 -0
- package/dist/server/mcp/security-audit.d.ts +1 -1
- package/dist/server/mcp/security-audit.d.ts.map +1 -1
- package/dist/server/mcp/security-patterns.d.ts +1 -25
- package/dist/server/mcp/security-patterns.d.ts.map +1 -1
- package/dist/server/mcp/security-patterns.js +55 -260
- package/dist/server/mcp/security-patterns.js.map +1 -1
- package/dist/server/server-setup.d.ts +22 -0
- package/dist/server/server-setup.d.ts.map +1 -0
- package/dist/server/server-setup.js +101 -0
- package/dist/server/server-setup.js.map +1 -0
- package/dist/server/services/file-explorer-ops.d.ts +24 -0
- package/dist/server/services/file-explorer-ops.d.ts.map +1 -0
- package/dist/server/services/file-explorer-ops.js +211 -0
- package/dist/server/services/file-explorer-ops.js.map +1 -0
- package/dist/server/services/files.d.ts +2 -85
- package/dist/server/services/files.d.ts.map +1 -1
- package/dist/server/services/files.js +7 -427
- package/dist/server/services/files.js.map +1 -1
- 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 +118 -32
- package/dist/server/services/plan/composer.js.map +1 -1
- package/dist/server/services/plan/config-installer.d.ts +25 -0
- package/dist/server/services/plan/config-installer.d.ts.map +1 -0
- package/dist/server/services/plan/config-installer.js +182 -0
- package/dist/server/services/plan/config-installer.js.map +1 -0
- package/dist/server/services/plan/dependency-resolver.d.ts +1 -1
- package/dist/server/services/plan/dependency-resolver.d.ts.map +1 -1
- package/dist/server/services/plan/dependency-resolver.js +4 -1
- package/dist/server/services/plan/dependency-resolver.js.map +1 -1
- package/dist/server/services/plan/executor.d.ts +38 -74
- package/dist/server/services/plan/executor.d.ts.map +1 -1
- package/dist/server/services/plan/executor.js +274 -460
- package/dist/server/services/plan/executor.js.map +1 -1
- package/dist/server/services/plan/front-matter.d.ts +18 -0
- package/dist/server/services/plan/front-matter.d.ts.map +1 -0
- package/dist/server/services/plan/front-matter.js +44 -0
- package/dist/server/services/plan/front-matter.js.map +1 -0
- package/dist/server/services/plan/output-manager.d.ts +22 -0
- package/dist/server/services/plan/output-manager.d.ts.map +1 -0
- package/dist/server/services/plan/output-manager.js +97 -0
- package/dist/server/services/plan/output-manager.js.map +1 -0
- package/dist/server/services/plan/parser-core.d.ts +20 -0
- package/dist/server/services/plan/parser-core.d.ts.map +1 -0
- package/dist/server/services/plan/parser-core.js +350 -0
- package/dist/server/services/plan/parser-core.js.map +1 -0
- package/dist/server/services/plan/parser-migration.d.ts +5 -0
- package/dist/server/services/plan/parser-migration.d.ts.map +1 -0
- package/dist/server/services/plan/parser-migration.js +124 -0
- package/dist/server/services/plan/parser-migration.js.map +1 -0
- package/dist/server/services/plan/parser.d.ts +11 -3
- package/dist/server/services/plan/parser.d.ts.map +1 -1
- package/dist/server/services/plan/parser.js +184 -369
- package/dist/server/services/plan/parser.js.map +1 -1
- package/dist/server/services/plan/prompt-builder.d.ts +17 -0
- package/dist/server/services/plan/prompt-builder.d.ts.map +1 -0
- package/dist/server/services/plan/prompt-builder.js +137 -0
- package/dist/server/services/plan/prompt-builder.js.map +1 -0
- package/dist/server/services/plan/review-gate.d.ts +28 -0
- package/dist/server/services/plan/review-gate.d.ts.map +1 -0
- package/dist/server/services/plan/review-gate.js +191 -0
- package/dist/server/services/plan/review-gate.js.map +1 -0
- package/dist/server/services/plan/state-reconciler.d.ts +1 -1
- package/dist/server/services/plan/state-reconciler.d.ts.map +1 -1
- package/dist/server/services/plan/state-reconciler.js +59 -7
- package/dist/server/services/plan/state-reconciler.js.map +1 -1
- package/dist/server/services/plan/types.d.ts +68 -0
- package/dist/server/services/plan/types.d.ts.map +1 -1
- package/dist/server/services/platform-credentials.d.ts +24 -0
- package/dist/server/services/platform-credentials.d.ts.map +1 -0
- package/dist/server/services/platform-credentials.js +68 -0
- package/dist/server/services/platform-credentials.js.map +1 -0
- package/dist/server/services/platform.d.ts +1 -31
- package/dist/server/services/platform.d.ts.map +1 -1
- package/dist/server/services/platform.js +11 -109
- package/dist/server/services/platform.js.map +1 -1
- package/dist/server/services/terminal/pty-manager.d.ts +7 -97
- package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
- package/dist/server/services/terminal/pty-manager.js +53 -266
- package/dist/server/services/terminal/pty-manager.js.map +1 -1
- package/dist/server/services/terminal/pty-utils.d.ts +57 -0
- package/dist/server/services/terminal/pty-utils.d.ts.map +1 -0
- package/dist/server/services/terminal/pty-utils.js +141 -0
- package/dist/server/services/terminal/pty-utils.js.map +1 -0
- package/dist/server/services/websocket/file-definition-handlers.d.ts +4 -0
- package/dist/server/services/websocket/file-definition-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/file-definition-handlers.js +153 -0
- package/dist/server/services/websocket/file-definition-handlers.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 +52 -391
- package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
- package/dist/server/services/websocket/file-search-handlers.d.ts +5 -0
- package/dist/server/services/websocket/file-search-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/file-search-handlers.js +238 -0
- package/dist/server/services/websocket/file-search-handlers.js.map +1 -0
- package/dist/server/services/websocket/file-utils.js +3 -3
- package/dist/server/services/websocket/file-utils.js.map +1 -1
- package/dist/server/services/websocket/git-branch-handlers.d.ts +7 -0
- package/dist/server/services/websocket/git-branch-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/git-branch-handlers.js +110 -0
- package/dist/server/services/websocket/git-branch-handlers.js.map +1 -0
- package/dist/server/services/websocket/git-diff-handlers.d.ts +6 -0
- package/dist/server/services/websocket/git-diff-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/git-diff-handlers.js +123 -0
- package/dist/server/services/websocket/git-diff-handlers.js.map +1 -0
- package/dist/server/services/websocket/git-handlers.d.ts +2 -31
- package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-handlers.js +35 -541
- package/dist/server/services/websocket/git-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-log-handlers.d.ts +6 -0
- package/dist/server/services/websocket/git-log-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/git-log-handlers.js +128 -0
- package/dist/server/services/websocket/git-log-handlers.js.map +1 -0
- package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-pr-handlers.js +13 -53
- package/dist/server/services/websocket/git-pr-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-tag-handlers.d.ts +6 -0
- package/dist/server/services/websocket/git-tag-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/git-tag-handlers.js +76 -0
- package/dist/server/services/websocket/git-tag-handlers.js.map +1 -0
- package/dist/server/services/websocket/git-utils.d.ts +43 -0
- package/dist/server/services/websocket/git-utils.d.ts.map +1 -0
- package/dist/server/services/websocket/git-utils.js +201 -0
- package/dist/server/services/websocket/git-utils.js.map +1 -0
- package/dist/server/services/websocket/handler.d.ts +2 -0
- package/dist/server/services/websocket/handler.d.ts.map +1 -1
- package/dist/server/services/websocket/handler.js +37 -112
- package/dist/server/services/websocket/handler.js.map +1 -1
- package/dist/server/services/websocket/plan-board-handlers.d.ts +11 -0
- package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-board-handlers.js +218 -0
- package/dist/server/services/websocket/plan-board-handlers.js.map +1 -0
- package/dist/server/services/websocket/plan-execution-handlers.d.ts +9 -0
- package/dist/server/services/websocket/plan-execution-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-execution-handlers.js +142 -0
- package/dist/server/services/websocket/plan-execution-handlers.js.map +1 -0
- package/dist/server/services/websocket/plan-handlers.d.ts +7 -2
- package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-handlers.js +21 -462
- package/dist/server/services/websocket/plan-handlers.js.map +1 -1
- package/dist/server/services/websocket/plan-helpers.d.ts +19 -0
- package/dist/server/services/websocket/plan-helpers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-helpers.js +199 -0
- package/dist/server/services/websocket/plan-helpers.js.map +1 -0
- package/dist/server/services/websocket/plan-issue-handlers.d.ts +12 -0
- package/dist/server/services/websocket/plan-issue-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-issue-handlers.js +162 -0
- package/dist/server/services/websocket/plan-issue-handlers.js.map +1 -0
- package/dist/server/services/websocket/plan-sprint-handlers.d.ts +7 -0
- package/dist/server/services/websocket/plan-sprint-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-sprint-handlers.js +206 -0
- package/dist/server/services/websocket/plan-sprint-handlers.js.map +1 -0
- package/dist/server/services/websocket/quality-complexity.d.ts +14 -0
- package/dist/server/services/websocket/quality-complexity.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-complexity.js +262 -0
- package/dist/server/services/websocket/quality-complexity.js.map +1 -0
- package/dist/server/services/websocket/quality-fix-agent.d.ts +16 -0
- package/dist/server/services/websocket/quality-fix-agent.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-fix-agent.js +140 -0
- package/dist/server/services/websocket/quality-fix-agent.js.map +1 -0
- package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-handlers.js +34 -346
- package/dist/server/services/websocket/quality-handlers.js.map +1 -1
- package/dist/server/services/websocket/quality-linting.d.ts +9 -0
- package/dist/server/services/websocket/quality-linting.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-linting.js +178 -0
- package/dist/server/services/websocket/quality-linting.js.map +1 -0
- package/dist/server/services/websocket/quality-review-agent.d.ts +19 -0
- package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-review-agent.js +206 -0
- package/dist/server/services/websocket/quality-review-agent.js.map +1 -0
- package/dist/server/services/websocket/quality-service.d.ts +3 -51
- package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-service.js +9 -651
- package/dist/server/services/websocket/quality-service.js.map +1 -1
- package/dist/server/services/websocket/quality-tools.d.ts +23 -0
- package/dist/server/services/websocket/quality-tools.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-tools.js +208 -0
- package/dist/server/services/websocket/quality-tools.js.map +1 -0
- package/dist/server/services/websocket/quality-types.d.ts +59 -0
- package/dist/server/services/websocket/quality-types.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-types.js +101 -0
- package/dist/server/services/websocket/quality-types.js.map +1 -0
- package/dist/server/services/websocket/session-handlers.d.ts +3 -4
- package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/session-handlers.js +3 -378
- package/dist/server/services/websocket/session-handlers.js.map +1 -1
- package/dist/server/services/websocket/session-history.d.ts +4 -0
- package/dist/server/services/websocket/session-history.d.ts.map +1 -0
- package/dist/server/services/websocket/session-history.js +208 -0
- package/dist/server/services/websocket/session-history.js.map +1 -0
- package/dist/server/services/websocket/session-initialization.d.ts +5 -0
- package/dist/server/services/websocket/session-initialization.d.ts.map +1 -0
- package/dist/server/services/websocket/session-initialization.js +163 -0
- package/dist/server/services/websocket/session-initialization.js.map +1 -0
- package/dist/server/services/websocket/types.d.ts +12 -2
- package/dist/server/services/websocket/types.d.ts.map +1 -1
- package/package.json +1 -2
- package/server/cli/headless/claude-invoker-process.ts +204 -0
- package/server/cli/headless/claude-invoker-stall.ts +164 -0
- package/server/cli/headless/claude-invoker-stream.ts +353 -0
- package/server/cli/headless/claude-invoker-tools.ts +187 -0
- package/server/cli/headless/claude-invoker.ts +15 -1092
- package/server/cli/headless/haiku-assessments.ts +365 -0
- package/server/cli/headless/headless-logger.ts +26 -5
- package/server/cli/headless/native-timeout-detector.ts +117 -0
- package/server/cli/headless/stall-assessor.ts +65 -618
- package/server/cli/headless/types.ts +4 -1
- package/server/cli/improvisation-attachments.ts +148 -0
- package/server/cli/improvisation-retry.ts +602 -0
- package/server/cli/improvisation-session-manager.ts +140 -1349
- package/server/cli/improvisation-types.ts +98 -0
- package/server/cli/prompt-builders.ts +370 -0
- package/server/index.ts +35 -246
- package/server/mcp/bouncer-haiku.ts +182 -0
- package/server/mcp/bouncer-integration.ts +87 -248
- package/server/mcp/security-analysis.ts +217 -0
- package/server/mcp/security-audit.ts +1 -1
- package/server/mcp/security-patterns.ts +60 -283
- package/server/server-setup.ts +114 -0
- package/server/services/file-explorer-ops.ts +293 -0
- package/server/services/files.ts +20 -532
- package/server/services/plan/composer.ts +140 -35
- package/server/services/plan/config-installer.ts +187 -0
- package/server/services/plan/dependency-resolver.ts +4 -1
- package/server/services/plan/executor.ts +281 -488
- package/server/services/plan/front-matter.ts +48 -0
- package/server/services/plan/output-manager.ts +113 -0
- package/server/services/plan/parser-core.ts +406 -0
- package/server/services/plan/parser-migration.ts +128 -0
- package/server/services/plan/parser.ts +188 -394
- package/server/services/plan/prompt-builder.ts +161 -0
- package/server/services/plan/review-gate.ts +212 -0
- package/server/services/plan/state-reconciler.ts +68 -7
- package/server/services/plan/types.ts +101 -1
- package/server/services/platform-credentials.ts +83 -0
- package/server/services/platform.ts +16 -131
- package/server/services/terminal/pty-manager.ts +66 -313
- package/server/services/terminal/pty-utils.ts +176 -0
- package/server/services/websocket/file-definition-handlers.ts +165 -0
- package/server/services/websocket/file-explorer-handlers.ts +37 -452
- package/server/services/websocket/file-search-handlers.ts +291 -0
- package/server/services/websocket/file-utils.ts +3 -3
- package/server/services/websocket/git-branch-handlers.ts +130 -0
- package/server/services/websocket/git-diff-handlers.ts +140 -0
- package/server/services/websocket/git-handlers.ts +40 -625
- package/server/services/websocket/git-log-handlers.ts +149 -0
- package/server/services/websocket/git-pr-handlers.ts +17 -62
- package/server/services/websocket/git-tag-handlers.ts +91 -0
- package/server/services/websocket/git-utils.ts +230 -0
- package/server/services/websocket/handler.ts +39 -112
- package/server/services/websocket/plan-board-handlers.ts +277 -0
- package/server/services/websocket/plan-execution-handlers.ts +184 -0
- package/server/services/websocket/plan-handlers.ts +23 -544
- package/server/services/websocket/plan-helpers.ts +215 -0
- package/server/services/websocket/plan-issue-handlers.ts +204 -0
- package/server/services/websocket/plan-sprint-handlers.ts +252 -0
- package/server/services/websocket/quality-complexity.ts +294 -0
- package/server/services/websocket/quality-fix-agent.ts +181 -0
- package/server/services/websocket/quality-handlers.ts +36 -404
- package/server/services/websocket/quality-linting.ts +187 -0
- package/server/services/websocket/quality-review-agent.ts +246 -0
- package/server/services/websocket/quality-service.ts +11 -762
- package/server/services/websocket/quality-tools.ts +209 -0
- package/server/services/websocket/quality-types.ts +169 -0
- package/server/services/websocket/session-handlers.ts +5 -437
- package/server/services/websocket/session-history.ts +222 -0
- package/server/services/websocket/session-initialization.ts +209 -0
- package/server/services/websocket/types.ts +46 -2
|
@@ -2,241 +2,56 @@
|
|
|
2
2
|
// Licensed under the MIT License. See LICENSE file for details.
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* PTY Manager - Manages pseudo-terminal sessions for shell access
|
|
5
|
+
* PTY Manager - Manages pseudo-terminal sessions for shell access.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Supports session persistence:
|
|
11
|
-
* - Sessions survive WebSocket disconnections
|
|
12
|
-
* - Scrollback buffer is maintained for replay on reconnect
|
|
13
|
-
* - Sessions can be reattached without losing running processes
|
|
14
|
-
*
|
|
15
|
-
* NOTE: node-pty is an optional dependency requiring native compilation.
|
|
16
|
-
* Terminal features gracefully degrade when node-pty is not available.
|
|
7
|
+
* Utilities (node-pty loading, shell detection, ScrollbackBuffer, types)
|
|
8
|
+
* live in pty-utils.ts. This file owns session lifecycle orchestration.
|
|
17
9
|
*/
|
|
18
10
|
|
|
19
11
|
import { EventEmitter } from 'node:events';
|
|
20
|
-
import { createRequire } from 'node:module';
|
|
21
12
|
import { homedir, platform } from 'node:os';
|
|
22
13
|
import { sanitizeEnvForSandbox } from '../sandbox-utils.js';
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
* Check if node-pty is available
|
|
38
|
-
*/
|
|
39
|
-
export function isPtyAvailable(): boolean {
|
|
40
|
-
return pty !== null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Re-attempt loading node-pty at runtime.
|
|
45
|
-
* Called after `mstro setup-terminal` compiles the native module
|
|
46
|
-
* so the running server can pick it up without a restart.
|
|
47
|
-
*
|
|
48
|
-
* Uses createRequire (CJS) to bypass ESM's module cache — a failed
|
|
49
|
-
* ESM import is permanently cached, but CJS require cache entries
|
|
50
|
-
* can be deleted and re-required.
|
|
51
|
-
*/
|
|
52
|
-
export async function reloadPty(): Promise<boolean> {
|
|
53
|
-
if (pty) return true;
|
|
54
|
-
try {
|
|
55
|
-
const require = createRequire(import.meta.url);
|
|
56
|
-
// Clear any cached failure so require() retries the native load
|
|
57
|
-
const resolved = require.resolve('node-pty');
|
|
58
|
-
delete require.cache[resolved];
|
|
59
|
-
pty = require('node-pty');
|
|
60
|
-
_ptyLoadError = null;
|
|
61
|
-
console.log('[PTYManager] node-pty loaded successfully after reload');
|
|
62
|
-
return true;
|
|
63
|
-
} catch (error: unknown) {
|
|
64
|
-
_ptyLoadError = error instanceof Error ? error.message : 'Failed to load node-pty';
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Get installation instructions for node-pty based on platform
|
|
71
|
-
*/
|
|
72
|
-
export function getPtyInstallInstructions(): string {
|
|
73
|
-
const os = platform();
|
|
74
|
-
|
|
75
|
-
let instructions = `Terminal feature requires native compilation of node-pty.\n\n`;
|
|
76
|
-
instructions += `To enable this feature:\n\n`;
|
|
77
|
-
|
|
78
|
-
if (os === 'darwin') {
|
|
79
|
-
instructions += `1. Install Xcode Command Line Tools:\n`;
|
|
80
|
-
instructions += ` xcode-select --install\n\n`;
|
|
81
|
-
} else if (os === 'win32') {
|
|
82
|
-
instructions += `1. Install Windows Build Tools:\n`;
|
|
83
|
-
instructions += ` npm install -g windows-build-tools\n\n`;
|
|
84
|
-
} else {
|
|
85
|
-
// Linux
|
|
86
|
-
instructions += `1. Install build tools:\n`;
|
|
87
|
-
instructions += ` # Debian/Ubuntu:\n`;
|
|
88
|
-
instructions += ` sudo apt install build-essential python3\n\n`;
|
|
89
|
-
instructions += ` # Fedora/RHEL:\n`;
|
|
90
|
-
instructions += ` sudo dnf install gcc-c++ make python3\n\n`;
|
|
91
|
-
instructions += ` # Arch:\n`;
|
|
92
|
-
instructions += ` sudo pacman -S base-devel python\n\n`;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
instructions += `2. Rebuild native modules:\n`;
|
|
96
|
-
instructions += ` npm rebuild node-pty\n\n`;
|
|
97
|
-
instructions += `3. Restart mstro\n`;
|
|
98
|
-
|
|
99
|
-
return instructions;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Import type separately for type-checking (doesn't require the module to load)
|
|
103
|
-
type IPty = import('node-pty').IPty;
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Fixed-size buffer that retains the most recent PTY output for replay on reconnect.
|
|
107
|
-
* Stores raw string chunks and evicts oldest data when the total exceeds maxBytes.
|
|
108
|
-
*/
|
|
109
|
-
class ScrollbackBuffer {
|
|
110
|
-
private chunks: string[] = [];
|
|
111
|
-
private totalLength = 0;
|
|
112
|
-
private maxBytes: number;
|
|
113
|
-
|
|
114
|
-
constructor(maxBytes: number) {
|
|
115
|
-
this.maxBytes = maxBytes;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
append(data: string): void {
|
|
119
|
-
this.chunks.push(data);
|
|
120
|
-
this.totalLength += data.length;
|
|
121
|
-
// Evict oldest chunks until under budget
|
|
122
|
-
while (this.totalLength > this.maxBytes && this.chunks.length > 1) {
|
|
123
|
-
const removed = this.chunks.shift()!;
|
|
124
|
-
this.totalLength -= removed.length;
|
|
125
|
-
}
|
|
126
|
-
// If a single chunk exceeds max, truncate from the front
|
|
127
|
-
if (this.totalLength > this.maxBytes && this.chunks.length === 1) {
|
|
128
|
-
const excess = this.totalLength - this.maxBytes;
|
|
129
|
-
this.chunks[0] = this.chunks[0].slice(excess);
|
|
130
|
-
this.totalLength = this.chunks[0].length;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
getContents(): string {
|
|
135
|
-
return this.chunks.join('');
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
clear(): void {
|
|
139
|
-
this.chunks = [];
|
|
140
|
-
this.totalLength = 0;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const SCROLLBACK_MAX_BYTES = 256 * 1024; // 256KB
|
|
145
|
-
|
|
146
|
-
export interface PTYSession {
|
|
147
|
-
id: string;
|
|
148
|
-
pty: IPty;
|
|
149
|
-
shell: string;
|
|
150
|
-
cwd: string;
|
|
151
|
-
// Timestamp when session was created
|
|
152
|
-
createdAt: number;
|
|
153
|
-
// Last activity timestamp
|
|
154
|
-
lastActivityAt: number;
|
|
155
|
-
// Current dimensions
|
|
156
|
-
cols: number;
|
|
157
|
-
rows: number;
|
|
158
|
-
// Output coalescing: buffer small chunks into fewer WS messages
|
|
159
|
-
_outputBuffer: string;
|
|
160
|
-
_outputTimer: ReturnType<typeof setTimeout> | null;
|
|
161
|
-
// Scrollback ring buffer for replay on reconnect
|
|
162
|
-
scrollback: ScrollbackBuffer;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Detect the user's default shell
|
|
167
|
-
*/
|
|
168
|
-
function detectShell(): string {
|
|
169
|
-
const shell = process.env.SHELL;
|
|
170
|
-
|
|
171
|
-
if (shell) {
|
|
172
|
-
return shell;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Platform-specific defaults
|
|
176
|
-
if (platform() === 'win32') {
|
|
177
|
-
return process.env.COMSPEC || 'powershell.exe';
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return '/bin/bash';
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Get shell name from path
|
|
185
|
-
*/
|
|
186
|
-
function getShellName(shellPath: string): string {
|
|
187
|
-
const parts = shellPath.split(/[/\\]/);
|
|
188
|
-
return parts[parts.length - 1] || 'shell';
|
|
189
|
-
}
|
|
14
|
+
import type { PTYSession } from './pty-utils.js';
|
|
15
|
+
import {
|
|
16
|
+
detectShell,
|
|
17
|
+
getPty,
|
|
18
|
+
getPtyInstallInstructions,
|
|
19
|
+
getShellName,
|
|
20
|
+
isPtyAvailable,
|
|
21
|
+
SCROLLBACK_MAX_BYTES,
|
|
22
|
+
ScrollbackBuffer,
|
|
23
|
+
} from './pty-utils.js';
|
|
24
|
+
|
|
25
|
+
export type { PTYSession } from './pty-utils.js';
|
|
26
|
+
// Re-export public API for backward compatibility
|
|
27
|
+
export { isPtyAvailable, reloadPty } from './pty-utils.js';
|
|
190
28
|
|
|
191
29
|
export class PTYManager extends EventEmitter {
|
|
192
30
|
private terminals: Map<string, PTYSession> = new Map();
|
|
193
31
|
|
|
194
32
|
constructor() {
|
|
195
33
|
super();
|
|
196
|
-
// Each terminal adds 3 listeners (output, exit, error) to this singleton.
|
|
197
|
-
// With multiple terminals, the default limit of 10 is easily exceeded.
|
|
198
34
|
this.setMaxListeners(50);
|
|
199
35
|
}
|
|
200
36
|
|
|
201
|
-
/**
|
|
202
|
-
* Check if a terminal session exists and is still running
|
|
203
|
-
*/
|
|
204
37
|
exists(terminalId: string): boolean {
|
|
205
38
|
return this.terminals.has(terminalId);
|
|
206
39
|
}
|
|
207
40
|
|
|
208
|
-
/**
|
|
209
|
-
* Get session info for reconnection
|
|
210
|
-
* Returns null if session doesn't exist
|
|
211
|
-
*/
|
|
212
41
|
getSessionInfo(terminalId: string): { shell: string; cwd: string; cols: number; rows: number } | null {
|
|
213
42
|
const session = this.terminals.get(terminalId);
|
|
214
43
|
if (!session) return null;
|
|
215
|
-
return {
|
|
216
|
-
shell: session.shell,
|
|
217
|
-
cwd: session.cwd,
|
|
218
|
-
cols: session.cols,
|
|
219
|
-
rows: session.rows,
|
|
220
|
-
};
|
|
44
|
+
return { shell: session.shell, cwd: session.cwd, cols: session.cols, rows: session.rows };
|
|
221
45
|
}
|
|
222
46
|
|
|
223
|
-
/**
|
|
224
|
-
* Check if PTY functionality is available
|
|
225
|
-
*/
|
|
226
47
|
isPtyAvailable(): boolean {
|
|
227
48
|
return isPtyAvailable();
|
|
228
49
|
}
|
|
229
50
|
|
|
230
|
-
/**
|
|
231
|
-
* Get installation instructions if PTY is not available
|
|
232
|
-
*/
|
|
233
51
|
getPtyInstallInstructions(): string {
|
|
234
52
|
return getPtyInstallInstructions();
|
|
235
53
|
}
|
|
236
54
|
|
|
237
|
-
/**
|
|
238
|
-
* Create a new terminal session
|
|
239
|
-
*/
|
|
240
55
|
create(
|
|
241
56
|
terminalId: string,
|
|
242
57
|
workingDir: string,
|
|
@@ -245,52 +60,30 @@ export class PTYManager extends EventEmitter {
|
|
|
245
60
|
requestedShell?: string,
|
|
246
61
|
options?: { sandboxed?: boolean }
|
|
247
62
|
): { shell: string; cwd: string; isReconnect: boolean; platform: string } {
|
|
248
|
-
|
|
63
|
+
const pty = getPty();
|
|
249
64
|
if (!pty) {
|
|
250
65
|
throw new Error(`PTY_NOT_AVAILABLE:${getPtyInstallInstructions()}`);
|
|
251
66
|
}
|
|
252
67
|
|
|
253
|
-
//
|
|
68
|
+
// Reconnect to existing session
|
|
254
69
|
if (this.terminals.has(terminalId)) {
|
|
255
70
|
const existingSession = this.terminals.get(terminalId)!;
|
|
256
|
-
|
|
257
|
-
// Always resize on reconnect to trigger SIGWINCH, which causes the
|
|
258
|
-
// shell to redraw its prompt line for the reconnected client
|
|
259
71
|
existingSession.pty.resize(cols, rows);
|
|
260
72
|
existingSession.cols = cols;
|
|
261
73
|
existingSession.rows = rows;
|
|
262
|
-
|
|
263
|
-
return {
|
|
264
|
-
shell: existingSession.shell,
|
|
265
|
-
cwd: existingSession.cwd,
|
|
266
|
-
isReconnect: true,
|
|
267
|
-
platform: platform(),
|
|
268
|
-
};
|
|
74
|
+
return { shell: existingSession.shell, cwd: existingSession.cwd, isReconnect: true, platform: platform() };
|
|
269
75
|
}
|
|
270
76
|
|
|
271
77
|
const shell = requestedShell || detectShell();
|
|
272
78
|
const cwd = workingDir || homedir();
|
|
273
79
|
|
|
274
|
-
|
|
275
80
|
try {
|
|
276
|
-
// Build env: sandboxed sessions get stripped secrets and HOME=projectDir
|
|
277
81
|
const baseEnv = options?.sandboxed
|
|
278
82
|
? sanitizeEnvForSandbox(process.env, cwd)
|
|
279
83
|
: { ...process.env, HOME: homedir() };
|
|
280
|
-
const env = {
|
|
281
|
-
...baseEnv,
|
|
282
|
-
TERM: 'xterm-256color',
|
|
283
|
-
COLORTERM: 'truecolor',
|
|
284
|
-
};
|
|
84
|
+
const env = { ...baseEnv, TERM: 'xterm-256color', COLORTERM: 'truecolor' };
|
|
285
85
|
|
|
286
|
-
|
|
287
|
-
const ptyProcess = pty.spawn(shell, [], {
|
|
288
|
-
name: 'xterm-256color',
|
|
289
|
-
cols,
|
|
290
|
-
rows,
|
|
291
|
-
cwd,
|
|
292
|
-
env,
|
|
293
|
-
});
|
|
86
|
+
const ptyProcess = pty.spawn(shell, [], { name: 'xterm-256color', cols, rows, cwd, env });
|
|
294
87
|
|
|
295
88
|
const session: PTYSession = {
|
|
296
89
|
id: terminalId,
|
|
@@ -307,56 +100,7 @@ export class PTYManager extends EventEmitter {
|
|
|
307
100
|
};
|
|
308
101
|
this.terminals.set(terminalId, session);
|
|
309
102
|
|
|
310
|
-
|
|
311
|
-
// On macOS, node-pty emits many tiny chunks (sometimes single bytes) and zsh
|
|
312
|
-
// wraps echoed chars in multi-part ANSI sequences (RPROMPT, syntax highlighting).
|
|
313
|
-
// A longer window on macOS ensures these multi-part sequences arrive as one chunk,
|
|
314
|
-
// which the browser's predictive echo can match correctly.
|
|
315
|
-
// 32ms on macOS captures full zsh redraw cycles (RPROMPT + syntax highlighting)
|
|
316
|
-
// that 24ms often split across coalesce boundaries.
|
|
317
|
-
const OUTPUT_COALESCE_MS = platform() === 'darwin' ? 32 : 8;
|
|
318
|
-
// High-water mark: flush immediately when buffer exceeds this size
|
|
319
|
-
// to prevent unbounded memory growth during high-output commands (e.g. `yes`)
|
|
320
|
-
const OUTPUT_HIGH_WATER = 64 * 1024; // 64KB
|
|
321
|
-
// Maximum chunk size per WebSocket message to prevent browser overload
|
|
322
|
-
const OUTPUT_CHUNK_SIZE = 64 * 1024;
|
|
323
|
-
|
|
324
|
-
const flushOutputBuffer = () => {
|
|
325
|
-
if (session._outputTimer) {
|
|
326
|
-
clearTimeout(session._outputTimer);
|
|
327
|
-
session._outputTimer = null;
|
|
328
|
-
}
|
|
329
|
-
const buffered = session._outputBuffer;
|
|
330
|
-
session._outputBuffer = '';
|
|
331
|
-
// Chunk large output to prevent single massive WebSocket frames
|
|
332
|
-
for (let i = 0; i < buffered.length; i += OUTPUT_CHUNK_SIZE) {
|
|
333
|
-
this.emit('output', terminalId, buffered.slice(i, i + OUTPUT_CHUNK_SIZE));
|
|
334
|
-
}
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
ptyProcess.onData((data: string) => {
|
|
338
|
-
session.scrollback.append(data);
|
|
339
|
-
session.lastActivityAt = Date.now();
|
|
340
|
-
session._outputBuffer += data;
|
|
341
|
-
// Flush immediately if buffer exceeds high-water mark
|
|
342
|
-
if (session._outputBuffer.length >= OUTPUT_HIGH_WATER) {
|
|
343
|
-
flushOutputBuffer();
|
|
344
|
-
} else if (!session._outputTimer) {
|
|
345
|
-
session._outputTimer = setTimeout(flushOutputBuffer, OUTPUT_COALESCE_MS);
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
// Handle exit — flush any buffered output first
|
|
350
|
-
ptyProcess.onExit(({ exitCode }) => {
|
|
351
|
-
if (session._outputBuffer) {
|
|
352
|
-
flushOutputBuffer();
|
|
353
|
-
} else if (session._outputTimer) {
|
|
354
|
-
clearTimeout(session._outputTimer);
|
|
355
|
-
session._outputTimer = null;
|
|
356
|
-
}
|
|
357
|
-
this.emit('exit', terminalId, exitCode);
|
|
358
|
-
this.terminals.delete(terminalId);
|
|
359
|
-
});
|
|
103
|
+
this.attachOutputHandlers(session, terminalId);
|
|
360
104
|
|
|
361
105
|
return { shell: session.shell, cwd, isReconnect: false, platform: platform() };
|
|
362
106
|
} catch (error: unknown) {
|
|
@@ -366,16 +110,53 @@ export class PTYManager extends EventEmitter {
|
|
|
366
110
|
}
|
|
367
111
|
}
|
|
368
112
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
113
|
+
private attachOutputHandlers(session: PTYSession, terminalId: string): void {
|
|
114
|
+
// Output coalescing constants
|
|
115
|
+
const OUTPUT_COALESCE_MS = platform() === 'darwin' ? 32 : 8;
|
|
116
|
+
const OUTPUT_HIGH_WATER = 64 * 1024;
|
|
117
|
+
const OUTPUT_CHUNK_SIZE = 64 * 1024;
|
|
118
|
+
|
|
119
|
+
const flushOutputBuffer = () => {
|
|
120
|
+
if (session._outputTimer) {
|
|
121
|
+
clearTimeout(session._outputTimer);
|
|
122
|
+
session._outputTimer = null;
|
|
123
|
+
}
|
|
124
|
+
const buffered = session._outputBuffer;
|
|
125
|
+
session._outputBuffer = '';
|
|
126
|
+
for (let i = 0; i < buffered.length; i += OUTPUT_CHUNK_SIZE) {
|
|
127
|
+
this.emit('output', terminalId, buffered.slice(i, i + OUTPUT_CHUNK_SIZE));
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
session.pty.onData((data: string) => {
|
|
132
|
+
session.scrollback.append(data);
|
|
133
|
+
session.lastActivityAt = Date.now();
|
|
134
|
+
session._outputBuffer += data;
|
|
135
|
+
if (session._outputBuffer.length >= OUTPUT_HIGH_WATER) {
|
|
136
|
+
flushOutputBuffer();
|
|
137
|
+
} else if (!session._outputTimer) {
|
|
138
|
+
session._outputTimer = setTimeout(flushOutputBuffer, OUTPUT_COALESCE_MS);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
session.pty.onExit(({ exitCode }) => {
|
|
143
|
+
if (session._outputBuffer) {
|
|
144
|
+
flushOutputBuffer();
|
|
145
|
+
} else if (session._outputTimer) {
|
|
146
|
+
clearTimeout(session._outputTimer);
|
|
147
|
+
session._outputTimer = null;
|
|
148
|
+
}
|
|
149
|
+
this.emit('exit', terminalId, exitCode);
|
|
150
|
+
this.terminals.delete(terminalId);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
372
154
|
write(terminalId: string, data: string): boolean {
|
|
373
155
|
const session = this.terminals.get(terminalId);
|
|
374
156
|
if (!session) {
|
|
375
157
|
console.warn(`[PTYManager] Terminal ${terminalId} not found for write`);
|
|
376
158
|
return false;
|
|
377
159
|
}
|
|
378
|
-
|
|
379
160
|
try {
|
|
380
161
|
session.pty.write(data);
|
|
381
162
|
return true;
|
|
@@ -386,16 +167,12 @@ export class PTYManager extends EventEmitter {
|
|
|
386
167
|
}
|
|
387
168
|
}
|
|
388
169
|
|
|
389
|
-
/**
|
|
390
|
-
* Resize terminal
|
|
391
|
-
*/
|
|
392
170
|
resize(terminalId: string, cols: number, rows: number): boolean {
|
|
393
171
|
const session = this.terminals.get(terminalId);
|
|
394
172
|
if (!session) {
|
|
395
173
|
console.warn(`[PTYManager] Terminal ${terminalId} not found for resize`);
|
|
396
174
|
return false;
|
|
397
175
|
}
|
|
398
|
-
|
|
399
176
|
try {
|
|
400
177
|
session.pty.resize(cols, rows);
|
|
401
178
|
return true;
|
|
@@ -405,18 +182,10 @@ export class PTYManager extends EventEmitter {
|
|
|
405
182
|
}
|
|
406
183
|
}
|
|
407
184
|
|
|
408
|
-
/**
|
|
409
|
-
* Close terminal session
|
|
410
|
-
*/
|
|
411
185
|
close(terminalId: string): boolean {
|
|
412
186
|
const session = this.terminals.get(terminalId);
|
|
413
|
-
if (!session)
|
|
414
|
-
return false;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
|
|
187
|
+
if (!session) return false;
|
|
418
188
|
try {
|
|
419
|
-
// Flush any coalesced output before closing
|
|
420
189
|
if (session._outputTimer) {
|
|
421
190
|
clearTimeout(session._outputTimer);
|
|
422
191
|
if (session._outputBuffer) {
|
|
@@ -435,45 +204,29 @@ export class PTYManager extends EventEmitter {
|
|
|
435
204
|
}
|
|
436
205
|
}
|
|
437
206
|
|
|
438
|
-
/**
|
|
439
|
-
* Get scrollback buffer contents for replay on reconnect
|
|
440
|
-
*/
|
|
441
207
|
getScrollback(terminalId: string): string | null {
|
|
442
208
|
const session = this.terminals.get(terminalId);
|
|
443
209
|
if (!session) return null;
|
|
444
210
|
return session.scrollback.getContents();
|
|
445
211
|
}
|
|
446
212
|
|
|
447
|
-
/**
|
|
448
|
-
* Get terminal session info
|
|
449
|
-
*/
|
|
450
213
|
getSession(terminalId: string): PTYSession | undefined {
|
|
451
214
|
return this.terminals.get(terminalId);
|
|
452
215
|
}
|
|
453
216
|
|
|
454
|
-
/**
|
|
455
|
-
* Check if terminal exists
|
|
456
|
-
*/
|
|
457
217
|
has(terminalId: string): boolean {
|
|
458
218
|
return this.terminals.has(terminalId);
|
|
459
219
|
}
|
|
460
220
|
|
|
461
|
-
/**
|
|
462
|
-
* Get all active terminal IDs
|
|
463
|
-
*/
|
|
464
221
|
getActiveTerminals(): string[] {
|
|
465
222
|
return Array.from(this.terminals.keys());
|
|
466
223
|
}
|
|
467
224
|
|
|
468
|
-
/**
|
|
469
|
-
* Close all terminals
|
|
470
|
-
*/
|
|
471
225
|
closeAll(): void {
|
|
472
226
|
for (const terminalId of this.terminals.keys()) {
|
|
473
227
|
this.close(terminalId);
|
|
474
228
|
}
|
|
475
229
|
}
|
|
476
|
-
|
|
477
230
|
}
|
|
478
231
|
|
|
479
232
|
// Singleton instance
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// Copyright (c) 2025-present Mstro, Inc. All rights reserved.
|
|
2
|
+
// Licensed under the MIT License. See LICENSE file for details.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* PTY Utilities — node-pty loading, shell detection, scrollback buffer, and types.
|
|
6
|
+
*
|
|
7
|
+
* Separated from pty-manager.ts so the PTYManager class stays focused
|
|
8
|
+
* on session lifecycle orchestration.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { createRequire } from 'node:module';
|
|
12
|
+
import { platform } from 'node:os';
|
|
13
|
+
|
|
14
|
+
// ── node-pty loading ──────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
// Try to load node-pty (optional native dependency)
|
|
17
|
+
let pty: typeof import('node-pty') | null = null;
|
|
18
|
+
let _ptyLoadError: string | null = null;
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
pty = await import('node-pty');
|
|
22
|
+
} catch (error: unknown) {
|
|
23
|
+
_ptyLoadError = error instanceof Error ? error.message : 'Failed to load node-pty';
|
|
24
|
+
console.warn('[PTYManager] node-pty not available - terminal features disabled');
|
|
25
|
+
console.warn('[PTYManager] To enable terminals, run: mstro setup-terminal');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getPty(): typeof import('node-pty') | null {
|
|
29
|
+
return pty;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Check if node-pty is available
|
|
34
|
+
*/
|
|
35
|
+
export function isPtyAvailable(): boolean {
|
|
36
|
+
return pty !== null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Re-attempt loading node-pty at runtime.
|
|
41
|
+
* Called after `mstro setup-terminal` compiles the native module
|
|
42
|
+
* so the running server can pick it up without a restart.
|
|
43
|
+
*
|
|
44
|
+
* Uses createRequire (CJS) to bypass ESM's module cache — a failed
|
|
45
|
+
* ESM import is permanently cached, but CJS require cache entries
|
|
46
|
+
* can be deleted and re-required.
|
|
47
|
+
*/
|
|
48
|
+
export async function reloadPty(): Promise<boolean> {
|
|
49
|
+
if (pty) return true;
|
|
50
|
+
try {
|
|
51
|
+
const require = createRequire(import.meta.url);
|
|
52
|
+
const resolved = require.resolve('node-pty');
|
|
53
|
+
delete require.cache[resolved];
|
|
54
|
+
pty = require('node-pty');
|
|
55
|
+
_ptyLoadError = null;
|
|
56
|
+
console.log('[PTYManager] node-pty loaded successfully after reload');
|
|
57
|
+
return true;
|
|
58
|
+
} catch (error: unknown) {
|
|
59
|
+
_ptyLoadError = error instanceof Error ? error.message : 'Failed to load node-pty';
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get installation instructions for node-pty based on platform
|
|
66
|
+
*/
|
|
67
|
+
export function getPtyInstallInstructions(): string {
|
|
68
|
+
const os = platform();
|
|
69
|
+
|
|
70
|
+
let instructions = `Terminal feature requires native compilation of node-pty.\n\n`;
|
|
71
|
+
instructions += `To enable this feature:\n\n`;
|
|
72
|
+
|
|
73
|
+
if (os === 'darwin') {
|
|
74
|
+
instructions += `1. Install Xcode Command Line Tools:\n`;
|
|
75
|
+
instructions += ` xcode-select --install\n\n`;
|
|
76
|
+
} else if (os === 'win32') {
|
|
77
|
+
instructions += `1. Install Windows Build Tools:\n`;
|
|
78
|
+
instructions += ` npm install -g windows-build-tools\n\n`;
|
|
79
|
+
} else {
|
|
80
|
+
instructions += `1. Install build tools:\n`;
|
|
81
|
+
instructions += ` # Debian/Ubuntu:\n`;
|
|
82
|
+
instructions += ` sudo apt install build-essential python3\n\n`;
|
|
83
|
+
instructions += ` # Fedora/RHEL:\n`;
|
|
84
|
+
instructions += ` sudo dnf install gcc-c++ make python3\n\n`;
|
|
85
|
+
instructions += ` # Arch:\n`;
|
|
86
|
+
instructions += ` sudo pacman -S base-devel python\n\n`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
instructions += `2. Rebuild native modules:\n`;
|
|
90
|
+
instructions += ` npm rebuild node-pty\n\n`;
|
|
91
|
+
instructions += `3. Restart mstro\n`;
|
|
92
|
+
|
|
93
|
+
return instructions;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── Shell detection ───────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Detect the user's default shell
|
|
100
|
+
*/
|
|
101
|
+
export function detectShell(): string {
|
|
102
|
+
const shell = process.env.SHELL;
|
|
103
|
+
if (shell) return shell;
|
|
104
|
+
if (platform() === 'win32') {
|
|
105
|
+
return process.env.COMSPEC || 'powershell.exe';
|
|
106
|
+
}
|
|
107
|
+
return '/bin/bash';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get shell name from path
|
|
112
|
+
*/
|
|
113
|
+
export function getShellName(shellPath: string): string {
|
|
114
|
+
const parts = shellPath.split(/[/\\]/);
|
|
115
|
+
return parts[parts.length - 1] || 'shell';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ── Scrollback buffer ─────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
export const SCROLLBACK_MAX_BYTES = 256 * 1024; // 256KB
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Fixed-size buffer that retains the most recent PTY output for replay on reconnect.
|
|
124
|
+
* Stores raw string chunks and evicts oldest data when the total exceeds maxBytes.
|
|
125
|
+
*/
|
|
126
|
+
export class ScrollbackBuffer {
|
|
127
|
+
private chunks: string[] = [];
|
|
128
|
+
private totalLength = 0;
|
|
129
|
+
private maxBytes: number;
|
|
130
|
+
|
|
131
|
+
constructor(maxBytes: number) {
|
|
132
|
+
this.maxBytes = maxBytes;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
append(data: string): void {
|
|
136
|
+
this.chunks.push(data);
|
|
137
|
+
this.totalLength += data.length;
|
|
138
|
+
while (this.totalLength > this.maxBytes && this.chunks.length > 1) {
|
|
139
|
+
const removed = this.chunks.shift()!;
|
|
140
|
+
this.totalLength -= removed.length;
|
|
141
|
+
}
|
|
142
|
+
if (this.totalLength > this.maxBytes && this.chunks.length === 1) {
|
|
143
|
+
const excess = this.totalLength - this.maxBytes;
|
|
144
|
+
this.chunks[0] = this.chunks[0].slice(excess);
|
|
145
|
+
this.totalLength = this.chunks[0].length;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
getContents(): string {
|
|
150
|
+
return this.chunks.join('');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
clear(): void {
|
|
154
|
+
this.chunks = [];
|
|
155
|
+
this.totalLength = 0;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ── Types ─────────────────────────────────────────────────────
|
|
160
|
+
|
|
161
|
+
// Import type separately for type-checking (doesn't require the module to load)
|
|
162
|
+
type IPty = import('node-pty').IPty;
|
|
163
|
+
|
|
164
|
+
export interface PTYSession {
|
|
165
|
+
id: string;
|
|
166
|
+
pty: IPty;
|
|
167
|
+
shell: string;
|
|
168
|
+
cwd: string;
|
|
169
|
+
createdAt: number;
|
|
170
|
+
lastActivityAt: number;
|
|
171
|
+
cols: number;
|
|
172
|
+
rows: number;
|
|
173
|
+
_outputBuffer: string;
|
|
174
|
+
_outputTimer: ReturnType<typeof setTimeout> | null;
|
|
175
|
+
scrollback: ScrollbackBuffer;
|
|
176
|
+
}
|