mstro-app 0.3.4 → 0.3.6

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.
Files changed (68) hide show
  1. package/bin/mstro.js +15 -2
  2. package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
  3. package/dist/server/cli/headless/claude-invoker.js +5 -10
  4. package/dist/server/cli/headless/claude-invoker.js.map +1 -1
  5. package/dist/server/cli/improvisation-session-manager.d.ts +4 -0
  6. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  7. package/dist/server/cli/improvisation-session-manager.js +39 -1
  8. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  9. package/dist/server/services/platform.d.ts.map +1 -1
  10. package/dist/server/services/platform.js +2 -13
  11. package/dist/server/services/platform.js.map +1 -1
  12. package/dist/server/services/terminal/pty-manager.d.ts +19 -0
  13. package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
  14. package/dist/server/services/terminal/pty-manager.js +48 -1
  15. package/dist/server/services/terminal/pty-manager.js.map +1 -1
  16. package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -1
  17. package/dist/server/services/websocket/file-explorer-handlers.js +17 -1
  18. package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
  19. package/dist/server/services/websocket/file-upload-handler.d.ts +44 -0
  20. package/dist/server/services/websocket/file-upload-handler.d.ts.map +1 -0
  21. package/dist/server/services/websocket/file-upload-handler.js +185 -0
  22. package/dist/server/services/websocket/file-upload-handler.js.map +1 -0
  23. package/dist/server/services/websocket/git-handlers.d.ts +1 -1
  24. package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
  25. package/dist/server/services/websocket/git-handlers.js +3 -3
  26. package/dist/server/services/websocket/git-handlers.js.map +1 -1
  27. package/dist/server/services/websocket/git-worktree-handlers.d.ts +1 -1
  28. package/dist/server/services/websocket/git-worktree-handlers.d.ts.map +1 -1
  29. package/dist/server/services/websocket/git-worktree-handlers.js +40 -2
  30. package/dist/server/services/websocket/git-worktree-handlers.js.map +1 -1
  31. package/dist/server/services/websocket/handler-context.d.ts +3 -0
  32. package/dist/server/services/websocket/handler-context.d.ts.map +1 -1
  33. package/dist/server/services/websocket/handler.d.ts +4 -0
  34. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  35. package/dist/server/services/websocket/handler.js +31 -0
  36. package/dist/server/services/websocket/handler.js.map +1 -1
  37. package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
  38. package/dist/server/services/websocket/session-handlers.js +69 -20
  39. package/dist/server/services/websocket/session-handlers.js.map +1 -1
  40. package/dist/server/services/websocket/session-registry.d.ts +6 -0
  41. package/dist/server/services/websocket/session-registry.d.ts.map +1 -1
  42. package/dist/server/services/websocket/session-registry.js +16 -0
  43. package/dist/server/services/websocket/session-registry.js.map +1 -1
  44. package/dist/server/services/websocket/tab-handlers.d.ts.map +1 -1
  45. package/dist/server/services/websocket/tab-handlers.js +33 -24
  46. package/dist/server/services/websocket/tab-handlers.js.map +1 -1
  47. package/dist/server/services/websocket/terminal-handlers.d.ts +4 -0
  48. package/dist/server/services/websocket/terminal-handlers.d.ts.map +1 -1
  49. package/dist/server/services/websocket/terminal-handlers.js +35 -4
  50. package/dist/server/services/websocket/terminal-handlers.js.map +1 -1
  51. package/dist/server/services/websocket/types.d.ts +2 -2
  52. package/dist/server/services/websocket/types.d.ts.map +1 -1
  53. package/package.json +1 -1
  54. package/server/cli/headless/claude-invoker.ts +5 -11
  55. package/server/cli/improvisation-session-manager.ts +42 -1
  56. package/server/services/platform.ts +2 -12
  57. package/server/services/terminal/pty-manager.ts +57 -2
  58. package/server/services/websocket/file-explorer-handlers.ts +16 -1
  59. package/server/services/websocket/file-upload-handler.ts +259 -0
  60. package/server/services/websocket/git-handlers.ts +3 -3
  61. package/server/services/websocket/git-worktree-handlers.ts +47 -3
  62. package/server/services/websocket/handler-context.ts +3 -0
  63. package/server/services/websocket/handler.ts +33 -0
  64. package/server/services/websocket/session-handlers.ts +79 -20
  65. package/server/services/websocket/session-registry.ts +18 -0
  66. package/server/services/websocket/tab-handlers.ts +44 -23
  67. package/server/services/websocket/terminal-handlers.ts +40 -4
  68. package/server/services/websocket/types.ts +15 -2
@@ -15,7 +15,7 @@ export interface WSContext {
15
15
  _ws?: unknown;
16
16
  }
17
17
  export interface WebSocketMessage {
18
- type: 'execute' | 'cancel' | 'getHistory' | 'getSessions' | 'getSessionsCount' | 'deleteSession' | 'getSessionById' | 'clearHistory' | 'searchHistory' | 'new' | 'autocomplete' | 'readFile' | 'ping' | 'initTab' | 'resumeSession' | 'approve' | 'reject' | 'recordSelection' | 'requestNotificationSummary' | 'terminalInit' | 'terminalReconnect' | 'terminalList' | 'terminalInput' | 'terminalResize' | 'terminalClose' | 'listDirectory' | 'writeFile' | 'createFile' | 'createDirectory' | 'deleteFile' | 'renameFile' | 'notifyFileOpened' | 'searchFileContents' | 'cancelSearch' | 'findDefinition' | 'gitStatus' | 'gitStage' | 'gitUnstage' | 'gitCommit' | 'gitCommitWithAI' | 'gitPush' | 'gitPull' | 'gitLog' | 'gitDiscoverRepos' | 'gitSetDirectory' | 'gitGetRemoteInfo' | 'gitCreatePR' | 'gitGeneratePRDescription' | 'gitListBranches' | 'gitCheckout' | 'gitCreateBranch' | 'gitDeleteBranch' | 'gitDiff' | 'gitListTags' | 'gitCreateTag' | 'gitPushTag' | 'gitWorktreeList' | 'gitWorktreeCreate' | 'gitWorktreeRemove' | 'tabWorktreeSwitch' | 'gitWorktreePush' | 'gitWorktreeCreatePR' | 'gitMergePreview' | 'gitWorktreeMerge' | 'gitMergeAbort' | 'gitMergeComplete' | 'getActiveTabs' | 'createTab' | 'reorderTabs' | 'syncTabMeta' | 'syncPromptText' | 'removeTab' | 'markTabViewed' | 'getSettings' | 'updateSettings';
18
+ type: 'execute' | 'cancel' | 'getHistory' | 'getSessions' | 'getSessionsCount' | 'deleteSession' | 'getSessionById' | 'clearHistory' | 'searchHistory' | 'new' | 'autocomplete' | 'readFile' | 'ping' | 'initTab' | 'resumeSession' | 'approve' | 'reject' | 'recordSelection' | 'requestNotificationSummary' | 'terminalInit' | 'terminalReconnect' | 'terminalList' | 'terminalInput' | 'terminalResize' | 'terminalClose' | 'listDirectory' | 'writeFile' | 'createFile' | 'createDirectory' | 'deleteFile' | 'renameFile' | 'notifyFileOpened' | 'searchFileContents' | 'cancelSearch' | 'findDefinition' | 'gitStatus' | 'gitStage' | 'gitUnstage' | 'gitCommit' | 'gitCommitWithAI' | 'gitPush' | 'gitPull' | 'gitLog' | 'gitDiscoverRepos' | 'gitSetDirectory' | 'gitGetRemoteInfo' | 'gitCreatePR' | 'gitGeneratePRDescription' | 'gitListBranches' | 'gitCheckout' | 'gitCreateBranch' | 'gitDeleteBranch' | 'gitDiff' | 'gitListTags' | 'gitCreateTag' | 'gitPushTag' | 'gitWorktreeList' | 'gitWorktreeCreate' | 'gitWorktreeCreateAndAssign' | 'gitWorktreeRemove' | 'tabWorktreeSwitch' | 'gitWorktreePush' | 'gitWorktreeCreatePR' | 'gitMergePreview' | 'gitWorktreeMerge' | 'gitMergeAbort' | 'gitMergeComplete' | 'getActiveTabs' | 'createTab' | 'reorderTabs' | 'syncTabMeta' | 'syncPromptText' | 'removeTab' | 'markTabViewed' | 'getSettings' | 'updateSettings' | 'fileUploadStart' | 'fileUploadChunk' | 'fileUploadComplete' | 'fileUploadCancel';
19
19
  tabId?: string;
20
20
  terminalId?: string;
21
21
  data?: any;
@@ -23,7 +23,7 @@ export interface WebSocketMessage {
23
23
  _permission?: 'control' | 'view';
24
24
  }
25
25
  export interface WebSocketResponse {
26
- type: 'output' | 'thinking' | 'movementStart' | 'movementComplete' | 'movementError' | 'sessionUpdate' | 'history' | 'sessions' | 'sessionsCount' | 'sessionDeleted' | 'sessionData' | 'historyCleared' | 'searchResults' | 'newSession' | 'autocomplete' | 'fileContent' | 'error' | 'pong' | 'tabInitialized' | 'approvalRequired' | 'toolUse' | 'streamingTokens' | 'notificationSummary' | 'terminalOutput' | 'terminalReady' | 'terminalExit' | 'terminalError' | 'terminalList' | 'directoryListing' | 'fileWritten' | 'fileCreated' | 'directoryCreated' | 'fileDeleted' | 'fileRenamed' | 'fileOpened' | 'fileContentChanged' | 'contentSearchResults' | 'contentSearchComplete' | 'contentSearchError' | 'definitionResult' | 'terminalCreated' | 'terminalClosed' | 'gitStatus' | 'gitStaged' | 'gitUnstaged' | 'gitCommitted' | 'gitCommitMessage' | 'gitPushed' | 'gitPulled' | 'gitLog' | 'gitError' | 'gitReposDiscovered' | 'gitDirectorySet' | 'gitRemoteInfo' | 'gitPRCreated' | 'gitPRDescription' | 'gitBranchList' | 'gitCheckedOut' | 'gitBranchCreated' | 'gitBranchDeleted' | 'gitDiffResult' | 'gitTagList' | 'gitTagCreated' | 'gitTagPushed' | 'gitWorktreeListResult' | 'gitWorktreeCreated' | 'gitWorktreeRemoved' | 'tabWorktreeSwitched' | 'gitWorktreePushed' | 'gitWorktreePRCreated' | 'gitMergePreviewResult' | 'gitWorktreeMergeResult' | 'gitMergeAborted' | 'gitMergeCompleted' | 'activeTabs' | 'tabCreated' | 'tabRemoved' | 'tabRenamed' | 'tabsReordered' | 'promptTextSync' | 'tabViewed' | 'tabStateChanged' | 'settings' | 'settingsUpdated';
26
+ type: 'output' | 'thinking' | 'movementStart' | 'movementComplete' | 'movementError' | 'sessionUpdate' | 'history' | 'sessions' | 'sessionsCount' | 'sessionDeleted' | 'sessionData' | 'historyCleared' | 'searchResults' | 'newSession' | 'autocomplete' | 'fileContent' | 'error' | 'pong' | 'tabInitialized' | 'approvalRequired' | 'toolUse' | 'streamingTokens' | 'notificationSummary' | 'terminalOutput' | 'terminalReady' | 'terminalExit' | 'terminalError' | 'terminalList' | 'directoryListing' | 'fileWritten' | 'fileCreated' | 'directoryCreated' | 'fileDeleted' | 'fileRenamed' | 'fileOpened' | 'fileContentChanged' | 'contentSearchResults' | 'contentSearchComplete' | 'contentSearchError' | 'definitionResult' | 'fileError' | 'terminalScrollback' | 'terminalCreated' | 'terminalClosed' | 'gitStatus' | 'gitStaged' | 'gitUnstaged' | 'gitCommitted' | 'gitCommitMessage' | 'gitPushed' | 'gitPulled' | 'gitLog' | 'gitError' | 'gitReposDiscovered' | 'gitDirectorySet' | 'gitRemoteInfo' | 'gitPRCreated' | 'gitPRDescription' | 'gitBranchList' | 'gitCheckedOut' | 'gitBranchCreated' | 'gitBranchDeleted' | 'gitDiffResult' | 'gitTagList' | 'gitTagCreated' | 'gitTagPushed' | 'gitWorktreeListResult' | 'gitWorktreeCreated' | 'gitWorktreeCreatedAndAssigned' | 'gitWorktreeRemoved' | 'tabWorktreeSwitched' | 'gitWorktreePushed' | 'gitWorktreePRCreated' | 'gitMergePreviewResult' | 'gitWorktreeMergeResult' | 'gitMergeAborted' | 'gitMergeCompleted' | 'activeTabs' | 'tabCreated' | 'tabRemoved' | 'tabRenamed' | 'tabsReordered' | 'promptTextSync' | 'tabViewed' | 'tabStateChanged' | 'settings' | 'settingsUpdated' | 'fileUploadAck' | 'fileUploadReady' | 'fileUploadError';
27
27
  tabId?: string;
28
28
  terminalId?: string;
29
29
  data?: any;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../server/services/websocket/types.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AAEH;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IACjC,KAAK,IAAI,IAAI,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAElB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,GAAG,CAAC,EAAE,OAAO,CAAA;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EACA,SAAS,GACT,QAAQ,GACR,YAAY,GACZ,aAAa,GACb,kBAAkB,GAClB,eAAe,GACf,gBAAgB,GAChB,cAAc,GACd,eAAe,GACf,KAAK,GACL,cAAc,GACd,UAAU,GACV,MAAM,GACN,SAAS,GACT,eAAe,GACf,SAAS,GACT,QAAQ,GACR,iBAAiB,GACjB,4BAA4B,GAC5B,cAAc,GACd,mBAAmB,GACnB,cAAc,GACd,eAAe,GACf,gBAAgB,GAChB,eAAe,GAEf,eAAe,GACf,WAAW,GACX,YAAY,GACZ,iBAAiB,GACjB,YAAY,GACZ,YAAY,GACZ,kBAAkB,GAClB,oBAAoB,GACpB,cAAc,GACd,gBAAgB,GAEhB,WAAW,GACX,UAAU,GACV,YAAY,GACZ,WAAW,GACX,iBAAiB,GACjB,SAAS,GACT,SAAS,GACT,QAAQ,GACR,kBAAkB,GAClB,iBAAiB,GACjB,kBAAkB,GAClB,aAAa,GACb,0BAA0B,GAE1B,iBAAiB,GACjB,aAAa,GACb,iBAAiB,GACjB,iBAAiB,GAEjB,SAAS,GAET,aAAa,GACb,cAAc,GACd,YAAY,GAEZ,iBAAiB,GACjB,mBAAmB,GACnB,mBAAmB,GACnB,mBAAmB,GACnB,iBAAiB,GACjB,qBAAqB,GAErB,iBAAiB,GACjB,kBAAkB,GAClB,eAAe,GACf,kBAAkB,GAElB,eAAe,GACf,WAAW,GACX,aAAa,GACb,aAAa,GACb,gBAAgB,GAChB,WAAW,GACX,eAAe,GAEf,aAAa,GACb,gBAAgB,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,2EAA2E;IAC3E,WAAW,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EACA,QAAQ,GACR,UAAU,GACV,eAAe,GACf,kBAAkB,GAClB,eAAe,GACf,eAAe,GACf,SAAS,GACT,UAAU,GACV,eAAe,GACf,gBAAgB,GAChB,aAAa,GACb,gBAAgB,GAChB,eAAe,GACf,YAAY,GACZ,cAAc,GACd,aAAa,GACb,OAAO,GACP,MAAM,GACN,gBAAgB,GAChB,kBAAkB,GAClB,SAAS,GACT,iBAAiB,GACjB,qBAAqB,GACrB,gBAAgB,GAChB,eAAe,GACf,cAAc,GACd,eAAe,GACf,cAAc,GAEd,kBAAkB,GAClB,aAAa,GACb,aAAa,GACb,kBAAkB,GAClB,aAAa,GACb,aAAa,GACb,YAAY,GACZ,oBAAoB,GACpB,sBAAsB,GACtB,uBAAuB,GACvB,oBAAoB,GACpB,kBAAkB,GAElB,iBAAiB,GACjB,gBAAgB,GAEhB,WAAW,GACX,WAAW,GACX,aAAa,GACb,cAAc,GACd,kBAAkB,GAClB,WAAW,GACX,WAAW,GACX,QAAQ,GACR,UAAU,GACV,oBAAoB,GACpB,iBAAiB,GACjB,eAAe,GACf,cAAc,GACd,kBAAkB,GAElB,eAAe,GACf,eAAe,GACf,kBAAkB,GAClB,kBAAkB,GAElB,eAAe,GAEf,YAAY,GACZ,eAAe,GACf,cAAc,GAEd,uBAAuB,GACvB,oBAAoB,GACpB,oBAAoB,GACpB,qBAAqB,GACrB,mBAAmB,GACnB,sBAAsB,GAEtB,uBAAuB,GACvB,wBAAwB,GACxB,iBAAiB,GACjB,mBAAmB,GAEnB,YAAY,GACZ,YAAY,GACZ,YAAY,GACZ,YAAY,GACZ,eAAe,GACf,gBAAgB,GAChB,WAAW,GACX,iBAAiB,GAEjB,UAAU,GACV,iBAAiB,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAGD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACzC;AAGD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,CAAC;CACnC;AAGD,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,KAAK,CAAC;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9F,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAMD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,cAAc,EAAE,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAMD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IAChD,iCAAiC;IACjC,MAAM,EAAE,OAAO,CAAC;IAChB,wCAAwC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,0BAA0B;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,OAAO,EAAE,OAAO,CAAC;IACjB,mBAAmB;IACnB,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,8BAA8B;IAC9B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,sBAAsB;IACtB,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,WAAW,EAAE,OAAO,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,kBAAkB;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,kBAAkB;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,0CAA0C;IAC1C,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,uDAAuD;IACvD,aAAa,EAAE,OAAO,CAAC;IACvB,gDAAgD;IAChD,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,iCAAiC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,OAAO,EAAE,OAAO,CAAC;CAClB;AAMD,MAAM,WAAW,cAAc;IAC7B,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,QAAQ,EAAE,OAAO,CAAC;IAClB,uDAAuD;IACvD,SAAS,EAAE,OAAO,CAAC;IACnB,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAMD,MAAM,WAAW,WAAW;IAC1B,eAAe;IACf,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;CACjB;AAMD,MAAM,WAAW,YAAY;IAC3B,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,MAAM,EAAE,OAAO,CAAC;IAChB,wCAAwC;IACxC,MAAM,EAAE,OAAO,CAAC;IAChB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAMD,MAAM,WAAW,kBAAkB;IACjC,4CAA4C;IAC5C,KAAK,EAAE,OAAO,CAAC;IACf,qCAAqC;IACrC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7C,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,kCAAkC;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,wCAAwC;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gCAAgC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../server/services/websocket/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mstro-app",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "Run Claude Code from any browser - streams live sessions from your machine to mstro.app",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -137,15 +137,10 @@ async function runStallAssessment(
137
137
  /** Regex matching Claude Code's internal tool timeout messages */
138
138
  const NATIVE_TIMEOUT_PATTERN = /^(\w+) timed out — (continuing|retrying) with (\d+) results? preserved$/;
139
139
 
140
- /** Quick prefix check: does incomplete text look like it might be a timeout? */
141
- const TIMEOUT_PREFIX_PATTERN = /^(\w+) timed/;
142
-
143
- /** Known tool names that Claude Code may report timeouts for */
144
- const NATIVE_TIMEOUT_TOOL_NAMES = new Set([
145
- 'Read', 'Grep', 'Glob', 'Edit', 'Write', 'Bash',
146
- 'WebFetch', 'WebSearch', 'Task', 'TodoRead', 'TodoWrite',
147
- 'NotebookEdit', 'MultiEdit',
148
- ]);
140
+ /** Quick prefix check: does incomplete text look like it might be a timeout?
141
+ * Matches any capitalized tool name followed by " timed" — no hardcoded set
142
+ * needed because the full NATIVE_TIMEOUT_PATTERN validates on the next chunk. */
143
+ const TIMEOUT_PREFIX_PATTERN = /^[A-Z]\w* timed/;
149
144
 
150
145
  interface NativeTimeoutEvent {
151
146
  toolName: string;
@@ -203,8 +198,7 @@ class NativeTimeoutDetector {
203
198
 
204
199
  // Handle incomplete trailing text
205
200
  if (incomplete) {
206
- const prefixMatch = incomplete.match(TIMEOUT_PREFIX_PATTERN);
207
- if (prefixMatch && NATIVE_TIMEOUT_TOOL_NAMES.has(prefixMatch[1])) {
201
+ if (TIMEOUT_PREFIX_PATTERN.test(incomplete)) {
208
202
  // Looks like the start of a timeout message — hold it
209
203
  this.lineBuffer = incomplete;
210
204
  } else {
@@ -283,6 +283,13 @@ export class ImprovisationSessionManager extends EventEmitter {
283
283
 
284
284
  const paths: string[] = [];
285
285
  for (const attachment of attachments) {
286
+ // Pre-uploaded files are already on disk from chunked upload
287
+ if ((attachment as FileAttachment & { _preUploaded?: boolean })._preUploaded) {
288
+ if (existsSync(attachment.filePath)) {
289
+ paths.push(attachment.filePath);
290
+ }
291
+ continue;
292
+ }
286
293
  const filePath = join(attachDir, attachment.fileName);
287
294
  try {
288
295
  // All paste content arrives as base64 — decode to binary
@@ -485,16 +492,50 @@ export class ImprovisationSessionManager extends EventEmitter {
485
492
  return result;
486
493
  }
487
494
 
495
+ /** MIME types that the Claude API can accept as image content blocks */
496
+ private static readonly SUPPORTED_IMAGE_MIMES = new Set([
497
+ 'image/jpeg', 'image/png', 'image/gif', 'image/webp',
498
+ ]);
499
+
500
+ /** Hydrate pre-uploaded images from disk and downgrade unsupported formats */
501
+ private hydrateAndFilterAttachments(attachments: FileAttachment[]): void {
502
+ for (const attachment of attachments) {
503
+ // Pre-uploaded images need their content read from disk
504
+ const preUploaded = (attachment as FileAttachment & { _preUploaded?: boolean })._preUploaded;
505
+ if (preUploaded && attachment.isImage && !attachment.content && existsSync(attachment.filePath)) {
506
+ try {
507
+ attachment.content = readFileSync(attachment.filePath).toString('base64');
508
+ } catch (err) {
509
+ console.error(`Failed to read pre-uploaded image ${attachment.filePath}:`, err);
510
+ attachment.isImage = false;
511
+ }
512
+ }
513
+
514
+ // Downgrade unsupported image formats (SVG, BMP, TIFF, ICO, etc.) to text attachments
515
+ if (attachment.isImage) {
516
+ const mime = (attachment.mimeType || '').toLowerCase();
517
+ if (mime && !ImprovisationSessionManager.SUPPORTED_IMAGE_MIMES.has(mime)) {
518
+ attachment.isImage = false;
519
+ }
520
+ }
521
+ }
522
+ }
523
+
488
524
  /** Prepare prompt with attachments and limit image count */
489
525
  private preparePromptAndAttachments(
490
526
  userPrompt: string,
491
527
  attachments: FileAttachment[] | undefined,
492
528
  ): { prompt: string; imageAttachments: FileAttachment[] | undefined } {
529
+ if (attachments) {
530
+ this.hydrateAndFilterAttachments(attachments);
531
+ }
532
+
493
533
  const diskPaths = attachments ? this.persistAttachments(attachments) : [];
494
534
  const prompt = this.buildPromptWithAttachments(userPrompt, attachments, diskPaths);
495
535
 
496
536
  const MAX_IMAGE_ATTACHMENTS = 20;
497
- const allImages = attachments?.filter(a => a.isImage);
537
+ // Only include images that have valid content
538
+ const allImages = attachments?.filter(a => a.isImage && a.content);
498
539
  let imageAttachments = allImages;
499
540
  if (allImages && allImages.length > MAX_IMAGE_ATTACHMENTS) {
500
541
  imageAttachments = allImages.slice(-MAX_IMAGE_ATTACHMENTS);
@@ -102,18 +102,8 @@ if (typeof WebSocket !== 'undefined') {
102
102
  WebSocketImpl = WS as unknown as typeof WebSocket
103
103
  }
104
104
 
105
- // Read SERVER_URL from ~/.mstro/.env if it exists (for local dev)
106
- function getServerUrl(): string {
107
- try {
108
- const envPath = join(MSTRO_DIR, '.env')
109
- const content = readFileSync(envPath, 'utf-8')
110
- const match = content.match(/^SERVER_URL=(.+)$/m)
111
- if (match) return match[1].trim()
112
- } catch {}
113
- return 'https://api.mstro.app'
114
- }
115
-
116
- const DEFAULT_PLATFORM_URL = process.env.PLATFORM_URL || getServerUrl()
105
+ // PLATFORM_URL is set via --server / --dev flag in mstro.js
106
+ const DEFAULT_PLATFORM_URL = process.env.PLATFORM_URL || 'https://api.mstro.app'
117
107
 
118
108
  interface ConnectionCallbacks {
119
109
  onConnected?: (connectionId: string) => void
@@ -102,6 +102,47 @@ export function getPtyInstallInstructions(): string {
102
102
  // Import type separately for type-checking (doesn't require the module to load)
103
103
  type IPty = import('node-pty').IPty;
104
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
+
105
146
  export interface PTYSession {
106
147
  id: string;
107
148
  pty: IPty;
@@ -117,6 +158,8 @@ export interface PTYSession {
117
158
  // Output coalescing: buffer small chunks into fewer WS messages
118
159
  _outputBuffer: string;
119
160
  _outputTimer: ReturnType<typeof setTimeout> | null;
161
+ // Scrollback ring buffer for replay on reconnect
162
+ scrollback: ScrollbackBuffer;
120
163
  }
121
164
 
122
165
  /**
@@ -201,7 +244,7 @@ export class PTYManager extends EventEmitter {
201
244
  rows: number = 24,
202
245
  requestedShell?: string,
203
246
  options?: { sandboxed?: boolean }
204
- ): { shell: string; cwd: string; isReconnect: boolean } {
247
+ ): { shell: string; cwd: string; isReconnect: boolean; platform: string } {
205
248
  // Check if node-pty is available
206
249
  if (!pty) {
207
250
  throw new Error(`PTY_NOT_AVAILABLE:${getPtyInstallInstructions()}`);
@@ -221,6 +264,7 @@ export class PTYManager extends EventEmitter {
221
264
  shell: existingSession.shell,
222
265
  cwd: existingSession.cwd,
223
266
  isReconnect: true,
267
+ platform: platform(),
224
268
  };
225
269
  }
226
270
 
@@ -259,6 +303,7 @@ export class PTYManager extends EventEmitter {
259
303
  rows,
260
304
  _outputBuffer: '',
261
305
  _outputTimer: null,
306
+ scrollback: new ScrollbackBuffer(SCROLLBACK_MAX_BYTES),
262
307
  };
263
308
  this.terminals.set(terminalId, session);
264
309
 
@@ -288,6 +333,7 @@ export class PTYManager extends EventEmitter {
288
333
  };
289
334
 
290
335
  ptyProcess.onData((data: string) => {
336
+ session.scrollback.append(data);
291
337
  session.lastActivityAt = Date.now();
292
338
  session._outputBuffer += data;
293
339
  // Flush immediately if buffer exceeds high-water mark
@@ -310,7 +356,7 @@ export class PTYManager extends EventEmitter {
310
356
  this.terminals.delete(terminalId);
311
357
  });
312
358
 
313
- return { shell: session.shell, cwd, isReconnect: false };
359
+ return { shell: session.shell, cwd, isReconnect: false, platform: platform() };
314
360
  } catch (error: unknown) {
315
361
  console.error(`[PTYManager] Failed to create terminal ${terminalId}:`, error);
316
362
  this.emit('error', terminalId, error instanceof Error ? error.message : 'Failed to create terminal');
@@ -387,6 +433,15 @@ export class PTYManager extends EventEmitter {
387
433
  }
388
434
  }
389
435
 
436
+ /**
437
+ * Get scrollback buffer contents for replay on reconnect
438
+ */
439
+ getScrollback(terminalId: string): string | null {
440
+ const session = this.terminals.get(terminalId);
441
+ if (!session) return null;
442
+ return session.scrollback.getContents();
443
+ }
444
+
390
445
  /**
391
446
  * Get terminal session info
392
447
  */
@@ -74,7 +74,22 @@ export function handleFileExplorerMessage(ctx: HandlerContext, ws: WSContext, ms
74
74
  cancelSearch: () => handleCancelSearch(ctx, tabId),
75
75
  findDefinition: () => handleFindDefinition(ctx, ws, msg, tabId, workingDir),
76
76
  };
77
- handlers[msg.type]?.();
77
+ const handler = handlers[msg.type];
78
+ if (!handler) return;
79
+
80
+ try {
81
+ handler();
82
+ } catch (error: unknown) {
83
+ // Send a domain-specific fileError so the web client can resolve pending
84
+ // promises instead of letting the generic handler send { type: 'error' }
85
+ // which no file-explorer listener handles (causing orphaned promises).
86
+ const errorMessage = error instanceof Error ? error.message : String(error);
87
+ ctx.send(ws, {
88
+ type: 'fileError',
89
+ tabId,
90
+ data: { operation: msg.type, path: msg.data?.dirPath || msg.data?.filePath || '', error: errorMessage },
91
+ });
92
+ }
78
93
  }
79
94
 
80
95
  function handleListDirectory(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): void {
@@ -0,0 +1,259 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ /**
5
+ * Chunked File Upload Handler
6
+ *
7
+ * Receives files in chunks over WebSocket from remote web clients,
8
+ * writes them to .mstro/tmp/attachments/{tabId}/, and sends progress acks back.
9
+ */
10
+
11
+ import type { WriteStream } from 'node:fs';
12
+ import { createWriteStream, existsSync, mkdirSync, rmSync, statSync } from 'node:fs';
13
+ import { join } from 'node:path';
14
+ import type { WebSocketResponse, WSContext } from './types.js';
15
+
16
+ interface UploadState {
17
+ uploadId: string;
18
+ fileName: string;
19
+ fileSize: number;
20
+ mimeType: string;
21
+ isImage: boolean;
22
+ totalChunks: number;
23
+ receivedChunks: number;
24
+ filePath: string;
25
+ stream: WriteStream;
26
+ lastActivity: number;
27
+ }
28
+
29
+ /** Completed upload that's ready to be referenced in an execute message */
30
+ export interface CompletedUpload {
31
+ uploadId: string;
32
+ fileName: string;
33
+ filePath: string;
34
+ isImage: boolean;
35
+ mimeType: string;
36
+ fileSize: number;
37
+ }
38
+
39
+ const UPLOAD_TIMEOUT_MS = 120_000; // 2 minutes idle timeout
40
+ const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB
41
+
42
+ export class FileUploadHandler {
43
+ private activeUploads = new Map<string, UploadState>();
44
+ private completedUploads = new Map<string, CompletedUpload[]>(); // tabId -> completed uploads
45
+ private cleanupInterval: ReturnType<typeof setInterval>;
46
+
47
+ constructor(private workingDir: string) {
48
+ // Periodically clean up stale uploads
49
+ this.cleanupInterval = setInterval(() => this.cleanupStaleUploads(), 30_000);
50
+ }
51
+
52
+ /** Get completed uploads for a tab and clear them */
53
+ getAndClearCompletedUploads(tabId: string): CompletedUpload[] {
54
+ const uploads = this.completedUploads.get(tabId) || [];
55
+ this.completedUploads.delete(tabId);
56
+ return uploads;
57
+ }
58
+
59
+ /** Get completed uploads for a tab without clearing */
60
+ getCompletedUploads(tabId: string): CompletedUpload[] {
61
+ return this.completedUploads.get(tabId) || [];
62
+ }
63
+
64
+ handleUploadStart(
65
+ ws: WSContext,
66
+ send: (ws: WSContext, response: WebSocketResponse) => void,
67
+ tabId: string,
68
+ data: { uploadId: string; fileName: string; fileSize: number; mimeType: string; isImage: boolean; totalChunks: number }
69
+ ): void {
70
+ const { uploadId, fileName, fileSize, mimeType, isImage, totalChunks } = data;
71
+
72
+ // Validate file size
73
+ if (fileSize > MAX_FILE_SIZE) {
74
+ send(ws, {
75
+ type: 'fileUploadError' as WebSocketResponse['type'],
76
+ tabId,
77
+ data: { uploadId, error: `File too large: ${(fileSize / 1024 / 1024).toFixed(1)}MB exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit` }
78
+ });
79
+ return;
80
+ }
81
+
82
+ // Create attachment directory
83
+ const attachDir = join(this.workingDir, '.mstro', 'tmp', 'attachments', tabId);
84
+ if (!existsSync(attachDir)) {
85
+ mkdirSync(attachDir, { recursive: true });
86
+ }
87
+
88
+ // Handle duplicate file names
89
+ let targetFileName = fileName;
90
+ let counter = 1;
91
+ while (existsSync(join(attachDir, targetFileName))) {
92
+ const ext = fileName.lastIndexOf('.') !== -1 ? fileName.slice(fileName.lastIndexOf('.')) : '';
93
+ const base = fileName.lastIndexOf('.') !== -1 ? fileName.slice(0, fileName.lastIndexOf('.')) : fileName;
94
+ targetFileName = `${base}-${counter}${ext}`;
95
+ counter++;
96
+ }
97
+
98
+ const filePath = join(attachDir, targetFileName);
99
+ const stream = createWriteStream(filePath);
100
+
101
+ const uploadState: UploadState = {
102
+ uploadId,
103
+ fileName: targetFileName,
104
+ fileSize,
105
+ mimeType,
106
+ isImage,
107
+ totalChunks,
108
+ receivedChunks: 0,
109
+ filePath,
110
+ stream,
111
+ lastActivity: Date.now(),
112
+ };
113
+
114
+ this.activeUploads.set(uploadId, uploadState);
115
+
116
+ // Send ack for start
117
+ send(ws, {
118
+ type: 'fileUploadAck' as WebSocketResponse['type'],
119
+ tabId,
120
+ data: { uploadId, chunkIndex: -1, status: 'ok' }
121
+ });
122
+ }
123
+
124
+ handleUploadChunk(
125
+ ws: WSContext,
126
+ send: (ws: WSContext, response: WebSocketResponse) => void,
127
+ tabId: string,
128
+ data: { uploadId: string; chunkIndex: number; content: string }
129
+ ): void {
130
+ const { uploadId, chunkIndex, content } = data;
131
+ const upload = this.activeUploads.get(uploadId);
132
+
133
+ if (!upload) {
134
+ send(ws, {
135
+ type: 'fileUploadError' as WebSocketResponse['type'],
136
+ tabId,
137
+ data: { uploadId, error: 'Upload not found or expired' }
138
+ });
139
+ return;
140
+ }
141
+
142
+ try {
143
+ const buffer = Buffer.from(content, 'base64');
144
+ upload.stream.write(buffer);
145
+ upload.receivedChunks++;
146
+ upload.lastActivity = Date.now();
147
+
148
+ send(ws, {
149
+ type: 'fileUploadAck' as WebSocketResponse['type'],
150
+ tabId,
151
+ data: { uploadId, chunkIndex, status: 'ok' }
152
+ });
153
+ } catch (err) {
154
+ const errorMsg = err instanceof Error ? err.message : String(err);
155
+ send(ws, {
156
+ type: 'fileUploadError' as WebSocketResponse['type'],
157
+ tabId,
158
+ data: { uploadId, error: `Chunk write failed: ${errorMsg}` }
159
+ });
160
+ this.cancelUpload(uploadId);
161
+ }
162
+ }
163
+
164
+ handleUploadComplete(
165
+ ws: WSContext,
166
+ send: (ws: WSContext, response: WebSocketResponse) => void,
167
+ tabId: string,
168
+ data: { uploadId: string }
169
+ ): void {
170
+ const { uploadId } = data;
171
+ const upload = this.activeUploads.get(uploadId);
172
+
173
+ if (!upload) {
174
+ send(ws, {
175
+ type: 'fileUploadError' as WebSocketResponse['type'],
176
+ tabId,
177
+ data: { uploadId, error: 'Upload not found or expired' }
178
+ });
179
+ return;
180
+ }
181
+
182
+ upload.stream.end(() => {
183
+ // Verify file was written
184
+ try {
185
+ const stat = statSync(upload.filePath);
186
+ const completed: CompletedUpload = {
187
+ uploadId,
188
+ fileName: upload.fileName,
189
+ filePath: upload.filePath,
190
+ isImage: upload.isImage,
191
+ mimeType: upload.mimeType,
192
+ fileSize: stat.size,
193
+ };
194
+
195
+ // Store completed upload for this tab
196
+ const tabUploads = this.completedUploads.get(tabId) || [];
197
+ tabUploads.push(completed);
198
+ this.completedUploads.set(tabId, tabUploads);
199
+
200
+ this.activeUploads.delete(uploadId);
201
+
202
+ send(ws, {
203
+ type: 'fileUploadReady' as WebSocketResponse['type'],
204
+ tabId,
205
+ data: { uploadId, filePath: upload.filePath, fileName: upload.fileName }
206
+ });
207
+ } catch (err) {
208
+ const errorMsg = err instanceof Error ? err.message : String(err);
209
+ send(ws, {
210
+ type: 'fileUploadError' as WebSocketResponse['type'],
211
+ tabId,
212
+ data: { uploadId, error: `File verification failed: ${errorMsg}` }
213
+ });
214
+ this.activeUploads.delete(uploadId);
215
+ }
216
+ });
217
+ }
218
+
219
+ handleUploadCancel(
220
+ _ws: WSContext,
221
+ _send: (ws: WSContext, response: WebSocketResponse) => void,
222
+ _tabId: string,
223
+ data: { uploadId: string }
224
+ ): void {
225
+ this.cancelUpload(data.uploadId);
226
+ }
227
+
228
+ private cancelUpload(uploadId: string): void {
229
+ const upload = this.activeUploads.get(uploadId);
230
+ if (!upload) return;
231
+
232
+ try {
233
+ upload.stream.destroy();
234
+ if (existsSync(upload.filePath)) {
235
+ rmSync(upload.filePath, { force: true });
236
+ }
237
+ } catch {
238
+ // Ignore cleanup errors
239
+ }
240
+ this.activeUploads.delete(uploadId);
241
+ }
242
+
243
+ private cleanupStaleUploads(): void {
244
+ const now = Date.now();
245
+ for (const [uploadId, upload] of this.activeUploads) {
246
+ if (now - upload.lastActivity > UPLOAD_TIMEOUT_MS) {
247
+ console.warn(`[FileUploadHandler] Upload ${uploadId} timed out, cleaning up`);
248
+ this.cancelUpload(uploadId);
249
+ }
250
+ }
251
+ }
252
+
253
+ destroy(): void {
254
+ clearInterval(this.cleanupInterval);
255
+ for (const uploadId of this.activeUploads.keys()) {
256
+ this.cancelUpload(uploadId);
257
+ }
258
+ }
259
+ }
@@ -188,13 +188,13 @@ const GIT_PR_TYPES = new Set([
188
188
 
189
189
  // Worktree/merge message types that route to git-worktree-handlers
190
190
  const GIT_WORKTREE_TYPES = new Set([
191
- 'gitWorktreeList', 'gitWorktreeCreate', 'gitWorktreeRemove',
191
+ 'gitWorktreeList', 'gitWorktreeCreate', 'gitWorktreeCreateAndAssign', 'gitWorktreeRemove',
192
192
  'tabWorktreeSwitch', 'gitWorktreePush', 'gitWorktreeCreatePR',
193
193
  'gitMergePreview', 'gitWorktreeMerge', 'gitMergeAbort', 'gitMergeComplete',
194
194
  ]);
195
195
 
196
196
  /** Route git messages to appropriate sub-handler */
197
- export function handleGitMessage(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): void {
197
+ export async function handleGitMessage(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): Promise<void> {
198
198
  const gitDir = ctx.gitDirectories.get(tabId) || workingDir;
199
199
 
200
200
  if (GIT_PR_TYPES.has(msg.type)) {
@@ -202,7 +202,7 @@ export function handleGitMessage(ctx: HandlerContext, ws: WSContext, msg: WebSoc
202
202
  return;
203
203
  }
204
204
  if (GIT_WORKTREE_TYPES.has(msg.type)) {
205
- handleGitWorktreeMessage(ctx, ws, msg, tabId, gitDir, workingDir);
205
+ await handleGitWorktreeMessage(ctx, ws, msg, tabId, gitDir, workingDir);
206
206
  return;
207
207
  }
208
208