mstro-app 0.3.5 → 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 (59) hide show
  1. package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
  2. package/dist/server/cli/headless/claude-invoker.js +5 -10
  3. package/dist/server/cli/headless/claude-invoker.js.map +1 -1
  4. package/dist/server/cli/improvisation-session-manager.d.ts +4 -0
  5. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  6. package/dist/server/cli/improvisation-session-manager.js +39 -1
  7. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  8. package/dist/server/services/terminal/pty-manager.d.ts +19 -0
  9. package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
  10. package/dist/server/services/terminal/pty-manager.js +48 -1
  11. package/dist/server/services/terminal/pty-manager.js.map +1 -1
  12. package/dist/server/services/websocket/file-upload-handler.d.ts +44 -0
  13. package/dist/server/services/websocket/file-upload-handler.d.ts.map +1 -0
  14. package/dist/server/services/websocket/file-upload-handler.js +185 -0
  15. package/dist/server/services/websocket/file-upload-handler.js.map +1 -0
  16. package/dist/server/services/websocket/git-handlers.d.ts +1 -1
  17. package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
  18. package/dist/server/services/websocket/git-handlers.js +3 -3
  19. package/dist/server/services/websocket/git-handlers.js.map +1 -1
  20. package/dist/server/services/websocket/git-worktree-handlers.d.ts +1 -1
  21. package/dist/server/services/websocket/git-worktree-handlers.d.ts.map +1 -1
  22. package/dist/server/services/websocket/git-worktree-handlers.js +40 -2
  23. package/dist/server/services/websocket/git-worktree-handlers.js.map +1 -1
  24. package/dist/server/services/websocket/handler-context.d.ts +3 -0
  25. package/dist/server/services/websocket/handler-context.d.ts.map +1 -1
  26. package/dist/server/services/websocket/handler.d.ts +4 -0
  27. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  28. package/dist/server/services/websocket/handler.js +31 -0
  29. package/dist/server/services/websocket/handler.js.map +1 -1
  30. package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
  31. package/dist/server/services/websocket/session-handlers.js +69 -20
  32. package/dist/server/services/websocket/session-handlers.js.map +1 -1
  33. package/dist/server/services/websocket/session-registry.d.ts +6 -0
  34. package/dist/server/services/websocket/session-registry.d.ts.map +1 -1
  35. package/dist/server/services/websocket/session-registry.js +16 -0
  36. package/dist/server/services/websocket/session-registry.js.map +1 -1
  37. package/dist/server/services/websocket/tab-handlers.d.ts.map +1 -1
  38. package/dist/server/services/websocket/tab-handlers.js +33 -24
  39. package/dist/server/services/websocket/tab-handlers.js.map +1 -1
  40. package/dist/server/services/websocket/terminal-handlers.d.ts +4 -0
  41. package/dist/server/services/websocket/terminal-handlers.d.ts.map +1 -1
  42. package/dist/server/services/websocket/terminal-handlers.js +35 -4
  43. package/dist/server/services/websocket/terminal-handlers.js.map +1 -1
  44. package/dist/server/services/websocket/types.d.ts +2 -2
  45. package/dist/server/services/websocket/types.d.ts.map +1 -1
  46. package/package.json +1 -1
  47. package/server/cli/headless/claude-invoker.ts +5 -11
  48. package/server/cli/improvisation-session-manager.ts +42 -1
  49. package/server/services/terminal/pty-manager.ts +57 -2
  50. package/server/services/websocket/file-upload-handler.ts +259 -0
  51. package/server/services/websocket/git-handlers.ts +3 -3
  52. package/server/services/websocket/git-worktree-handlers.ts +47 -3
  53. package/server/services/websocket/handler-context.ts +3 -0
  54. package/server/services/websocket/handler.ts +33 -0
  55. package/server/services/websocket/session-handlers.ts +79 -20
  56. package/server/services/websocket/session-registry.ts +18 -0
  57. package/server/services/websocket/tab-handlers.ts +44 -23
  58. package/server/services/websocket/terminal-handlers.ts +40 -4
  59. package/server/services/websocket/types.ts +14 -2
@@ -7,6 +7,43 @@ import type { HandlerContext } from './handler-context.js';
7
7
  import { buildOutputHistory, setupSessionListeners } from './session-handlers.js';
8
8
  import type { WebSocketMessage, WSContext } from './types.js';
9
9
 
10
+ function buildActiveTabData(
11
+ regTab: { tabName: string; createdAt: string; order: number; hasUnviewedCompletion?: boolean; sessionId: string },
12
+ session: ImprovisationSessionManager,
13
+ worktreePath: string | undefined,
14
+ worktreeBranch: string | undefined,
15
+ ): Record<string, unknown> {
16
+ return {
17
+ tabName: regTab.tabName,
18
+ createdAt: regTab.createdAt,
19
+ order: regTab.order,
20
+ hasUnviewedCompletion: regTab.hasUnviewedCompletion,
21
+ sessionInfo: session.getSessionInfo(),
22
+ isExecuting: session.isExecuting,
23
+ outputHistory: buildOutputHistory(session),
24
+ executionEvents: session.isExecuting ? session.getExecutionEventLog() : undefined,
25
+ ...(session.isExecuting && session.executionStartTimestamp ? { executionStartTimestamp: session.executionStartTimestamp } : {}),
26
+ ...(worktreePath ? { worktreePath, worktreeBranch } : {}),
27
+ };
28
+ }
29
+
30
+ function buildInactiveTabData(
31
+ regTab: { tabName: string; createdAt: string; order: number; hasUnviewedCompletion?: boolean; sessionId: string },
32
+ worktreePath: string | undefined,
33
+ worktreeBranch: string | undefined,
34
+ ): Record<string, unknown> {
35
+ return {
36
+ tabName: regTab.tabName,
37
+ createdAt: regTab.createdAt,
38
+ order: regTab.order,
39
+ hasUnviewedCompletion: regTab.hasUnviewedCompletion,
40
+ sessionId: regTab.sessionId,
41
+ isExecuting: false,
42
+ outputHistory: [],
43
+ ...(worktreePath ? { worktreePath, worktreeBranch } : {}),
44
+ };
45
+ }
46
+
10
47
  export function handleGetActiveTabs(ctx: HandlerContext, ws: WSContext, workingDir: string): void {
11
48
  const registry = ctx.getRegistry(workingDir);
12
49
  const allTabs = registry.getAllTabs();
@@ -14,29 +51,11 @@ export function handleGetActiveTabs(ctx: HandlerContext, ws: WSContext, workingD
14
51
  const tabs: Record<string, unknown> = {};
15
52
  for (const [tabId, regTab] of Object.entries(allTabs)) {
16
53
  const session = ctx.sessions.get(regTab.sessionId);
17
- if (session) {
18
- tabs[tabId] = {
19
- tabName: regTab.tabName,
20
- createdAt: regTab.createdAt,
21
- order: regTab.order,
22
- hasUnviewedCompletion: regTab.hasUnviewedCompletion,
23
- sessionInfo: session.getSessionInfo(),
24
- isExecuting: session.isExecuting,
25
- outputHistory: buildOutputHistory(session),
26
- executionEvents: session.isExecuting ? session.getExecutionEventLog() : undefined,
27
- ...(session.isExecuting && session.executionStartTimestamp ? { executionStartTimestamp: session.executionStartTimestamp } : {}),
28
- };
29
- } else {
30
- tabs[tabId] = {
31
- tabName: regTab.tabName,
32
- createdAt: regTab.createdAt,
33
- order: regTab.order,
34
- hasUnviewedCompletion: regTab.hasUnviewedCompletion,
35
- sessionId: regTab.sessionId,
36
- isExecuting: false,
37
- outputHistory: [],
38
- };
39
- }
54
+ const worktreePath = ctx.gitDirectories.get(tabId);
55
+ const worktreeBranch = ctx.gitBranches.get(tabId);
56
+ tabs[tabId] = session
57
+ ? buildActiveTabData(regTab, session, worktreePath, worktreeBranch)
58
+ : buildInactiveTabData(regTab, worktreePath, worktreeBranch);
40
59
  }
41
60
 
42
61
  ctx.send(ws, { type: 'activeTabs', data: { tabs } });
@@ -65,6 +84,8 @@ export function handleSyncPromptText(ctx: HandlerContext, _ws: WSContext, msg: W
65
84
  export function handleRemoveTab(ctx: HandlerContext, _ws: WSContext, tabId: string, workingDir: string): void {
66
85
  const registry = ctx.getRegistry(workingDir);
67
86
  registry.unregisterTab(tabId);
87
+ ctx.gitDirectories.delete(tabId);
88
+ ctx.gitBranches.delete(tabId);
68
89
 
69
90
  ctx.broadcastToAll({
70
91
  type: 'tabRemoved',
@@ -1,6 +1,7 @@
1
1
  // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
2
  // Licensed under the MIT License. See LICENSE file for details.
3
3
 
4
+ import { platform } from 'node:os';
4
5
  import { AnalyticsEvents, trackEvent } from '../analytics.js';
5
6
  import { getPTYManager } from '../terminal/pty-manager.js';
6
7
  import type { HandlerContext } from './handler-context.js';
@@ -58,7 +59,7 @@ function handleTerminalInit(
58
59
  setupTerminalBroadcastListeners(ctx, terminalId);
59
60
 
60
61
  try {
61
- const { shell, cwd, isReconnect } = ptyManager.create(
62
+ const { shell, cwd, isReconnect, platform } = ptyManager.create(
62
63
  terminalId,
63
64
  workingDir,
64
65
  cols || 80,
@@ -77,8 +78,17 @@ function handleTerminalInit(
77
78
  ctx.send(ws, {
78
79
  type: 'terminalReady',
79
80
  terminalId,
80
- data: { shell, cwd, isReconnect }
81
+ data: { shell, cwd, isReconnect, platform }
81
82
  });
83
+
84
+ // Send scrollback buffer so reconnecting clients see prior output
85
+ if (isReconnect) {
86
+ const scrollback = ptyManager.getScrollback(terminalId);
87
+ if (scrollback) {
88
+ ctx.send(ws, { type: 'terminalScrollback', terminalId, data: { scrollback } });
89
+ }
90
+ }
91
+
82
92
  trackEvent(AnalyticsEvents.TERMINAL_SESSION_CREATED, {
83
93
  shell,
84
94
  is_reconnect: isReconnect,
@@ -116,10 +126,17 @@ function handleTerminalReconnect(ctx: HandlerContext, ws: WSContext, terminalId:
116
126
  data: {
117
127
  shell: sessionInfo.shell,
118
128
  cwd: sessionInfo.cwd,
119
- isReconnect: true
129
+ isReconnect: true,
130
+ platform: platform(),
120
131
  }
121
132
  });
122
133
 
134
+ // Send scrollback buffer so reconnecting clients see prior output
135
+ const scrollback = ptyManager.getScrollback(terminalId);
136
+ if (scrollback) {
137
+ ctx.send(ws, { type: 'terminalScrollback', terminalId, data: { scrollback } });
138
+ }
139
+
123
140
  ptyManager.resize(terminalId, sessionInfo.cols, sessionInfo.rows);
124
141
  }
125
142
 
@@ -269,9 +286,28 @@ function setupTerminalBroadcastListeners(ctx: HandlerContext, terminalId: string
269
286
  /**
270
287
  * Clean up terminal subscribers for a disconnected WS context.
271
288
  * Called from handler.ts handleClose().
289
+ *
290
+ * After removing the ws, also cleans up any subscriber Sets that are now empty
291
+ * and their associated PTY event listeners — preventing stale state accumulation
292
+ * across repeated connect/disconnect cycles (WebSocket hiccups).
272
293
  */
273
294
  export function cleanupTerminalSubscribers(ctx: HandlerContext, ws: WSContext): void {
274
- for (const subs of ctx.terminalSubscribers.values()) {
295
+ const emptyTerminals: string[] = [];
296
+
297
+ for (const [terminalId, subs] of ctx.terminalSubscribers) {
275
298
  subs.delete(ws);
299
+ if (subs.size === 0) {
300
+ emptyTerminals.push(terminalId);
301
+ }
302
+ }
303
+
304
+ // Clean up empty subscriber sets and their PTY event listeners
305
+ for (const terminalId of emptyTerminals) {
306
+ ctx.terminalSubscribers.delete(terminalId);
307
+ const cleanup = ctx.terminalListenerCleanups.get(terminalId);
308
+ if (cleanup) {
309
+ cleanup();
310
+ ctx.terminalListenerCleanups.delete(terminalId);
311
+ }
276
312
  }
277
313
  }
@@ -86,6 +86,7 @@ export interface WebSocketMessage {
86
86
  // Worktree operations
87
87
  | 'gitWorktreeList'
88
88
  | 'gitWorktreeCreate'
89
+ | 'gitWorktreeCreateAndAssign'
89
90
  | 'gitWorktreeRemove'
90
91
  | 'tabWorktreeSwitch'
91
92
  | 'gitWorktreePush'
@@ -105,7 +106,12 @@ export interface WebSocketMessage {
105
106
  | 'markTabViewed'
106
107
  // Settings message types
107
108
  | 'getSettings'
108
- | 'updateSettings';
109
+ | 'updateSettings'
110
+ // File upload message types (chunked remote uploads)
111
+ | 'fileUploadStart'
112
+ | 'fileUploadChunk'
113
+ | 'fileUploadComplete'
114
+ | 'fileUploadCancel';
109
115
  tabId?: string;
110
116
  terminalId?: string;
111
117
  // biome-ignore lint/suspicious/noExplicitAny: message envelope carries heterogeneous payloads
@@ -158,6 +164,7 @@ export interface WebSocketResponse {
158
164
  | 'contentSearchError'
159
165
  | 'definitionResult'
160
166
  | 'fileError'
167
+ | 'terminalScrollback'
161
168
  // Terminal sync response types
162
169
  | 'terminalCreated'
163
170
  | 'terminalClosed'
@@ -190,6 +197,7 @@ export interface WebSocketResponse {
190
197
  // Worktree response types
191
198
  | 'gitWorktreeListResult'
192
199
  | 'gitWorktreeCreated'
200
+ | 'gitWorktreeCreatedAndAssigned'
193
201
  | 'gitWorktreeRemoved'
194
202
  | 'tabWorktreeSwitched'
195
203
  | 'gitWorktreePushed'
@@ -210,7 +218,11 @@ export interface WebSocketResponse {
210
218
  | 'tabStateChanged'
211
219
  // Settings response types
212
220
  | 'settings'
213
- | 'settingsUpdated';
221
+ | 'settingsUpdated'
222
+ // File upload response types
223
+ | 'fileUploadAck'
224
+ | 'fileUploadReady'
225
+ | 'fileUploadError';
214
226
  tabId?: string;
215
227
  terminalId?: string;
216
228
  // biome-ignore lint/suspicious/noExplicitAny: message envelope carries heterogeneous payloads