mstro-app 0.1.53 → 0.1.56

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 (57) hide show
  1. package/bin/mstro.js +3 -1
  2. package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
  3. package/dist/server/cli/headless/claude-invoker.js +151 -0
  4. package/dist/server/cli/headless/claude-invoker.js.map +1 -1
  5. package/dist/server/cli/headless/runner.d.ts.map +1 -1
  6. package/dist/server/cli/headless/runner.js +7 -1
  7. package/dist/server/cli/headless/runner.js.map +1 -1
  8. package/dist/server/cli/headless/stall-assessor.d.ts +30 -0
  9. package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -0
  10. package/dist/server/cli/headless/stall-assessor.js +184 -0
  11. package/dist/server/cli/headless/stall-assessor.js.map +1 -0
  12. package/dist/server/cli/headless/types.d.ts +9 -1
  13. package/dist/server/cli/headless/types.d.ts.map +1 -1
  14. package/dist/server/cli/improvisation-session-manager.d.ts +21 -2
  15. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  16. package/dist/server/cli/improvisation-session-manager.js +65 -5
  17. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  18. package/dist/server/index.js +4 -1
  19. package/dist/server/index.js.map +1 -1
  20. package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
  21. package/dist/server/mcp/bouncer-integration.js +32 -0
  22. package/dist/server/mcp/bouncer-integration.js.map +1 -1
  23. package/dist/server/services/platform.d.ts.map +1 -1
  24. package/dist/server/services/platform.js +8 -5
  25. package/dist/server/services/platform.js.map +1 -1
  26. package/dist/server/services/settings.d.ts +25 -0
  27. package/dist/server/services/settings.d.ts.map +1 -0
  28. package/dist/server/services/settings.js +72 -0
  29. package/dist/server/services/settings.js.map +1 -0
  30. package/dist/server/services/websocket/autocomplete.d.ts.map +1 -1
  31. package/dist/server/services/websocket/autocomplete.js +12 -15
  32. package/dist/server/services/websocket/autocomplete.js.map +1 -1
  33. package/dist/server/services/websocket/handler.d.ts +99 -2
  34. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  35. package/dist/server/services/websocket/handler.js +627 -184
  36. package/dist/server/services/websocket/handler.js.map +1 -1
  37. package/dist/server/services/websocket/session-registry.d.ts +38 -0
  38. package/dist/server/services/websocket/session-registry.d.ts.map +1 -0
  39. package/dist/server/services/websocket/session-registry.js +154 -0
  40. package/dist/server/services/websocket/session-registry.js.map +1 -0
  41. package/dist/server/services/websocket/types.d.ts +2 -2
  42. package/dist/server/services/websocket/types.d.ts.map +1 -1
  43. package/package.json +1 -1
  44. package/server/cli/headless/RESEARCH.md +627 -0
  45. package/server/cli/headless/claude-invoker.ts +192 -1
  46. package/server/cli/headless/runner.ts +7 -1
  47. package/server/cli/headless/stall-assessor.ts +245 -0
  48. package/server/cli/headless/types.ts +9 -1
  49. package/server/cli/improvisation-session-manager.ts +73 -5
  50. package/server/index.ts +4 -1
  51. package/server/mcp/bouncer-integration.ts +32 -0
  52. package/server/services/platform.ts +8 -5
  53. package/server/services/settings.ts +89 -0
  54. package/server/services/websocket/autocomplete.ts +18 -14
  55. package/server/services/websocket/handler.ts +687 -200
  56. package/server/services/websocket/session-registry.ts +180 -0
  57. package/server/services/websocket/types.ts +31 -2
@@ -0,0 +1,180 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ /**
5
+ * Session Registry — Persistent tab-to-session mapping
6
+ *
7
+ * Survives WebSocket disconnections and reconnections. When a web client
8
+ * refreshes or a new client connects, the registry allows the handler to
9
+ * reattach tabs to their existing in-memory sessions (or resume from disk).
10
+ *
11
+ * Backed by .mstro/session-registry.json in the working directory.
12
+ */
13
+
14
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
15
+ import { join } from 'node:path'
16
+
17
+ export interface RegisteredTab {
18
+ sessionId: string
19
+ tabName: string
20
+ createdAt: string
21
+ lastActivityAt: string
22
+ order: number
23
+ hasUnviewedCompletion: boolean
24
+ }
25
+
26
+ interface RegistryData {
27
+ tabs: Record<string, RegisteredTab>
28
+ }
29
+
30
+ export class SessionRegistry {
31
+ private registryPath: string
32
+ private data: RegistryData
33
+
34
+ constructor(workingDir: string) {
35
+ const mstroDir = join(workingDir, '.mstro')
36
+ if (!existsSync(mstroDir)) {
37
+ mkdirSync(mstroDir, { recursive: true })
38
+ }
39
+ this.registryPath = join(mstroDir, 'session-registry.json')
40
+ this.data = this.load()
41
+ }
42
+
43
+ private load(): RegistryData {
44
+ try {
45
+ if (existsSync(this.registryPath)) {
46
+ const raw: RegistryData = JSON.parse(readFileSync(this.registryPath, 'utf-8'))
47
+ // Backfill `order` for legacy data that lacks it
48
+ const needsOrder = Object.values(raw.tabs).some((t) => t.order === undefined)
49
+ if (needsOrder) {
50
+ const sorted = Object.entries(raw.tabs).sort(([, a], [, b]) =>
51
+ (a.createdAt || '').localeCompare(b.createdAt || '')
52
+ )
53
+ sorted.forEach(([, tab], i) => {
54
+ if (tab.order === undefined) tab.order = i
55
+ })
56
+ }
57
+ // Backfill `hasUnviewedCompletion` for legacy data
58
+ for (const tab of Object.values(raw.tabs)) {
59
+ if (tab.hasUnviewedCompletion === undefined) tab.hasUnviewedCompletion = false
60
+ }
61
+ return raw
62
+ }
63
+ } catch (error) {
64
+ console.error('[SessionRegistry] Error loading registry:', error)
65
+ }
66
+ return { tabs: {} }
67
+ }
68
+
69
+ private save(): void {
70
+ try {
71
+ writeFileSync(this.registryPath, JSON.stringify(this.data, null, 2))
72
+ } catch (error) {
73
+ console.error('[SessionRegistry] Error saving registry:', error)
74
+ }
75
+ }
76
+
77
+ private getNextOrder(): number {
78
+ let max = -1
79
+ for (const tab of Object.values(this.data.tabs)) {
80
+ if (tab.order > max) max = tab.order
81
+ }
82
+ return max + 1
83
+ }
84
+
85
+ registerTab(tabId: string, sessionId: string, tabName?: string): void {
86
+ const now = new Date().toISOString()
87
+ this.data.tabs[tabId] = {
88
+ sessionId,
89
+ tabName: tabName || `Chat ${this.getNextChatNumber()}`,
90
+ createdAt: this.data.tabs[tabId]?.createdAt || now,
91
+ lastActivityAt: now,
92
+ order: this.data.tabs[tabId]?.order ?? this.getNextOrder(),
93
+ hasUnviewedCompletion: this.data.tabs[tabId]?.hasUnviewedCompletion ?? false,
94
+ }
95
+ this.save()
96
+ }
97
+
98
+ /**
99
+ * Find the next available "Chat N" number by scanning existing tab names.
100
+ */
101
+ private getNextChatNumber(): number {
102
+ const usedNumbers = new Set<number>()
103
+ for (const tab of Object.values(this.data.tabs)) {
104
+ const match = tab.tabName.match(/^Chat (\d+)$/)
105
+ if (match) {
106
+ usedNumbers.add(parseInt(match[1], 10))
107
+ }
108
+ }
109
+ let n = 1
110
+ while (usedNumbers.has(n)) n++
111
+ return n
112
+ }
113
+
114
+ unregisterTab(tabId: string): void {
115
+ delete this.data.tabs[tabId]
116
+ this.save()
117
+ }
118
+
119
+ getTabSession(tabId: string): string | undefined {
120
+ return this.data.tabs[tabId]?.sessionId
121
+ }
122
+
123
+ getTab(tabId: string): RegisteredTab | undefined {
124
+ return this.data.tabs[tabId]
125
+ }
126
+
127
+ getAllTabs(): Record<string, RegisteredTab> {
128
+ return { ...this.data.tabs }
129
+ }
130
+
131
+ updateTabName(tabId: string, name: string): void {
132
+ if (this.data.tabs[tabId]) {
133
+ this.data.tabs[tabId].tabName = name
134
+ this.save()
135
+ }
136
+ }
137
+
138
+ touchTab(tabId: string): void {
139
+ if (this.data.tabs[tabId]) {
140
+ this.data.tabs[tabId].lastActivityAt = new Date().toISOString()
141
+ this.save()
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Update session ID for a tab (e.g., when "new session" is started)
147
+ */
148
+ updateTabSession(tabId: string, sessionId: string): void {
149
+ if (this.data.tabs[tabId]) {
150
+ this.data.tabs[tabId].sessionId = sessionId
151
+ this.data.tabs[tabId].lastActivityAt = new Date().toISOString()
152
+ this.save()
153
+ }
154
+ }
155
+
156
+ markTabViewed(tabId: string): void {
157
+ if (this.data.tabs[tabId]) {
158
+ this.data.tabs[tabId].hasUnviewedCompletion = false
159
+ this.save()
160
+ }
161
+ }
162
+
163
+ markTabUnviewed(tabId: string): void {
164
+ if (this.data.tabs[tabId]) {
165
+ this.data.tabs[tabId].hasUnviewedCompletion = true
166
+ this.save()
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Reorder tabs. Accepts an ordered array of tabIds and reassigns order values.
172
+ */
173
+ reorderTabs(tabOrder: string[]): void {
174
+ for (let i = 0; i < tabOrder.length; i++) {
175
+ const tab = this.data.tabs[tabOrder[i]]
176
+ if (tab) tab.order = i
177
+ }
178
+ this.save()
179
+ }
180
+ }
@@ -56,6 +56,7 @@ export interface WebSocketMessage {
56
56
  | 'createDirectory'
57
57
  | 'deleteFile'
58
58
  | 'renameFile'
59
+ | 'notifyFileOpened'
59
60
  // Git message types
60
61
  | 'gitStatus'
61
62
  | 'gitStage'
@@ -65,7 +66,18 @@ export interface WebSocketMessage {
65
66
  | 'gitPush'
66
67
  | 'gitLog'
67
68
  | 'gitDiscoverRepos'
68
- | 'gitSetDirectory';
69
+ | 'gitSetDirectory'
70
+ // Session sync message types
71
+ | 'getActiveTabs'
72
+ | 'createTab'
73
+ | 'reorderTabs'
74
+ | 'syncTabMeta'
75
+ | 'syncPromptText'
76
+ | 'removeTab'
77
+ | 'markTabViewed'
78
+ // Settings message types
79
+ | 'getSettings'
80
+ | 'updateSettings';
69
81
  tabId?: string;
70
82
  terminalId?: string;
71
83
  data?: any;
@@ -109,6 +121,11 @@ export interface WebSocketResponse {
109
121
  | 'directoryCreated'
110
122
  | 'fileDeleted'
111
123
  | 'fileRenamed'
124
+ | 'fileOpened'
125
+ | 'fileContentChanged'
126
+ // Terminal sync response types
127
+ | 'terminalCreated'
128
+ | 'terminalClosed'
112
129
  // Git response types
113
130
  | 'gitStatus'
114
131
  | 'gitStaged'
@@ -119,7 +136,19 @@ export interface WebSocketResponse {
119
136
  | 'gitLog'
120
137
  | 'gitError'
121
138
  | 'gitReposDiscovered'
122
- | 'gitDirectorySet';
139
+ | 'gitDirectorySet'
140
+ // Session sync response types
141
+ | 'activeTabs'
142
+ | 'tabCreated'
143
+ | 'tabRemoved'
144
+ | 'tabRenamed'
145
+ | 'tabsReordered'
146
+ | 'promptTextSync'
147
+ | 'tabViewed'
148
+ | 'tabStateChanged'
149
+ // Settings response types
150
+ | 'settings'
151
+ | 'settingsUpdated';
123
152
  tabId?: string;
124
153
  terminalId?: string;
125
154
  data?: any;