@brimveyn/aimux 1.2.5 → 1.3.0
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/README.md +29 -8
- package/package.json +4 -4
- package/src/app-runtime/backend-runtime-events.ts +9 -0
- package/src/app-runtime/side-effects.ts +8 -0
- package/src/config/loader.ts +2 -2
- package/src/config.ts +3 -3
- package/src/daemon/daemon.ts +262 -118
- package/src/daemon/runtime-paths.ts +34 -2
- package/src/index.tsx +11 -3
- package/src/ipc/manager-protocol.ts +347 -0
- package/src/ipc/protocol.ts +55 -3
- package/src/platform/daemon-control.ts +42 -11
- package/src/profile-paths.ts +27 -0
- package/src/pty/pty-manager.ts +24 -0
- package/src/restart-daemon.ts +10 -10
- package/src/session-backend/bootstrap.ts +197 -46
- package/src/session-backend/remote-session-backend.ts +126 -59
- package/src/state/session-catalog.ts +5 -4
- package/src/state/snippet-catalog.ts +4 -4
- package/src/terminal-manager/manager-client.ts +360 -0
- package/src/terminal-manager/terminal-manager.ts +272 -0
- package/src/update.ts +3 -3
package/src/daemon/daemon.ts
CHANGED
|
@@ -1,22 +1,28 @@
|
|
|
1
|
-
import { createServer, type Socket } from 'node:net'
|
|
1
|
+
import { connect, createServer, type Socket } from 'node:net'
|
|
2
2
|
|
|
3
3
|
import { logDebug } from '../debug/input-log'
|
|
4
4
|
import {
|
|
5
5
|
type ClientRequest,
|
|
6
6
|
encodeMessage,
|
|
7
|
+
getProcessVersion,
|
|
8
|
+
IPC_PROTOCOL_MIN_VERSION,
|
|
7
9
|
IPC_PROTOCOL_VERSION,
|
|
8
10
|
MessageDecoder,
|
|
11
|
+
negotiateProtocolVersion,
|
|
9
12
|
parseClientRequest,
|
|
10
13
|
type ServerEvent,
|
|
11
14
|
type ServerResponse,
|
|
12
15
|
} from '../ipc/protocol'
|
|
13
|
-
import {
|
|
16
|
+
import { findSocketProcessPid, spawnDetachedTerminalManager } from '../platform/daemon-control'
|
|
17
|
+
import { TerminalManagerClient } from '../terminal-manager/manager-client'
|
|
14
18
|
import {
|
|
15
|
-
|
|
19
|
+
getIpcDaemonSocketPath,
|
|
20
|
+
getSocketSecurityIssue,
|
|
21
|
+
getTerminalManagerSocketPath,
|
|
16
22
|
removeDaemonSocketIfExists,
|
|
17
|
-
|
|
23
|
+
removeTerminalManagerSocketIfExists,
|
|
24
|
+
tightenSocketPermissions,
|
|
18
25
|
} from './runtime-paths'
|
|
19
|
-
import { SessionManager } from './session-manager'
|
|
20
26
|
|
|
21
27
|
function send(socket: Socket, message: ServerResponse | ServerEvent): void {
|
|
22
28
|
socket.write(encodeMessage(message))
|
|
@@ -34,25 +40,88 @@ function requireSession(socket: Socket, attachedSessions: Map<Socket, string>):
|
|
|
34
40
|
return sessionId
|
|
35
41
|
}
|
|
36
42
|
|
|
43
|
+
function requireNegotiatedVersion(socket: Socket, versions: Map<Socket, number>): number {
|
|
44
|
+
const version = versions.get(socket)
|
|
45
|
+
if (version === undefined) {
|
|
46
|
+
throw new Error('Protocol handshake required before attach')
|
|
47
|
+
}
|
|
48
|
+
return version
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function canConnectToSocket(socketPath: string): Promise<boolean> {
|
|
52
|
+
const securityIssue = getSocketSecurityIssue(socketPath)
|
|
53
|
+
if (securityIssue) {
|
|
54
|
+
logDebug('daemon.socketUnhealthy', { issue: securityIssue, socketPath })
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return new Promise<boolean>((resolve) => {
|
|
59
|
+
const socket = connect(socketPath)
|
|
60
|
+
const finish = (result: boolean) => {
|
|
61
|
+
socket.removeAllListeners()
|
|
62
|
+
socket.destroy()
|
|
63
|
+
resolve(result)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
socket.once('connect', () => finish(true))
|
|
67
|
+
socket.once('error', () => finish(false))
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function ensureTerminalManagerReady(manager: TerminalManagerClient): Promise<void> {
|
|
72
|
+
try {
|
|
73
|
+
logDebug('daemon.ensureTerminalManager.connectExisting.start', {
|
|
74
|
+
socketPath: getTerminalManagerSocketPath(),
|
|
75
|
+
})
|
|
76
|
+
await manager.connect()
|
|
77
|
+
logDebug('daemon.ensureTerminalManager.connectExisting.success')
|
|
78
|
+
return
|
|
79
|
+
} catch (error) {
|
|
80
|
+
logDebug('daemon.ensureTerminalManager.connectExisting.failed', {
|
|
81
|
+
error: error instanceof Error ? error.message : String(error),
|
|
82
|
+
})
|
|
83
|
+
const socketPath = getTerminalManagerSocketPath()
|
|
84
|
+
if (!(await canConnectToSocket(socketPath))) {
|
|
85
|
+
removeTerminalManagerSocketIfExists()
|
|
86
|
+
logDebug('daemon.ensureTerminalManager.spawn.start', { socketPath })
|
|
87
|
+
const ok = await spawnDetachedTerminalManager()
|
|
88
|
+
if (!ok) {
|
|
89
|
+
throw new Error('Failed to start terminal manager')
|
|
90
|
+
}
|
|
91
|
+
logDebug('daemon.ensureTerminalManager.spawn.success', { socketPath })
|
|
92
|
+
}
|
|
93
|
+
await manager.connect()
|
|
94
|
+
logDebug('daemon.ensureTerminalManager.connectAfterSpawn.success')
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
37
98
|
export async function runDaemon(): Promise<void> {
|
|
38
|
-
const socketPath =
|
|
99
|
+
const socketPath = getIpcDaemonSocketPath()
|
|
39
100
|
logDebug('daemon.start', { pid: process.pid, socketPath })
|
|
40
101
|
|
|
41
|
-
const existingPid = await
|
|
102
|
+
const existingPid = await findSocketProcessPid(socketPath)
|
|
42
103
|
if (existingPid !== null && existingPid !== process.pid) {
|
|
43
104
|
logDebug('daemon.alreadyRunning', { existingPid })
|
|
44
105
|
process.stderr.write(`aimux daemon already running (pid ${existingPid})\n`)
|
|
45
106
|
process.exit(1)
|
|
46
107
|
}
|
|
47
108
|
|
|
48
|
-
logDebug('daemon.removeStaleSocket', { socketPath })
|
|
49
109
|
removeDaemonSocketIfExists()
|
|
50
110
|
|
|
51
|
-
const
|
|
111
|
+
const manager = new TerminalManagerClient()
|
|
112
|
+
await ensureTerminalManagerReady(manager)
|
|
113
|
+
|
|
52
114
|
const sockets = new Set<Socket>()
|
|
53
115
|
const attachedSessions = new Map<Socket, string>()
|
|
116
|
+
const negotiatedVersions = new Map<Socket, number>()
|
|
54
117
|
|
|
55
|
-
|
|
118
|
+
manager.on('render', (sessionId, tabId, viewport, terminalModes) => {
|
|
119
|
+
logDebug('daemon.manager.render', {
|
|
120
|
+
attachedSocketCount: sockets.size,
|
|
121
|
+
sessionId,
|
|
122
|
+
tabId,
|
|
123
|
+
viewportLines: viewport.lines.length,
|
|
124
|
+
})
|
|
56
125
|
const event: ServerEvent = { payload: { tabId, terminalModes, viewport }, type: 'tabRender' }
|
|
57
126
|
for (const socket of sockets) {
|
|
58
127
|
if (attachedSessions.get(socket) === sessionId) {
|
|
@@ -60,7 +129,8 @@ export async function runDaemon(): Promise<void> {
|
|
|
60
129
|
}
|
|
61
130
|
}
|
|
62
131
|
})
|
|
63
|
-
|
|
132
|
+
manager.on('exit', (sessionId, tabId, exitCode) => {
|
|
133
|
+
logDebug('daemon.manager.exit', { exitCode, sessionId, tabId })
|
|
64
134
|
const event: ServerEvent = { payload: { exitCode, tabId }, type: 'tabExit' }
|
|
65
135
|
for (const socket of sockets) {
|
|
66
136
|
if (attachedSessions.get(socket) === sessionId) {
|
|
@@ -68,7 +138,8 @@ export async function runDaemon(): Promise<void> {
|
|
|
68
138
|
}
|
|
69
139
|
}
|
|
70
140
|
})
|
|
71
|
-
|
|
141
|
+
manager.on('error', (sessionId, tabId, message) => {
|
|
142
|
+
logDebug('daemon.manager.error', { message, sessionId, tabId })
|
|
72
143
|
const event: ServerEvent = { payload: { message, tabId }, type: 'tabError' }
|
|
73
144
|
for (const socket of sockets) {
|
|
74
145
|
if (attachedSessions.get(socket) === sessionId) {
|
|
@@ -83,117 +154,195 @@ export async function runDaemon(): Promise<void> {
|
|
|
83
154
|
const decoder = new MessageDecoder<ClientRequest>(parseClientRequest)
|
|
84
155
|
|
|
85
156
|
socket.on('data', (chunk) => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
message.payload.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
157
|
+
void (async () => {
|
|
158
|
+
try {
|
|
159
|
+
for (const message of decoder.push(chunk)) {
|
|
160
|
+
try {
|
|
161
|
+
switch (message.type) {
|
|
162
|
+
case 'hello': {
|
|
163
|
+
logDebug('daemon.request.hello', {
|
|
164
|
+
maxVersion: message.payload.maxVersion,
|
|
165
|
+
minVersion: message.payload.minVersion,
|
|
166
|
+
})
|
|
167
|
+
const selectedVersion = negotiateProtocolVersion(
|
|
168
|
+
message.payload.minVersion,
|
|
169
|
+
message.payload.maxVersion,
|
|
170
|
+
IPC_PROTOCOL_MIN_VERSION,
|
|
171
|
+
IPC_PROTOCOL_VERSION
|
|
172
|
+
)
|
|
173
|
+
if (selectedVersion === null) {
|
|
174
|
+
send(socket, {
|
|
175
|
+
id: message.id,
|
|
176
|
+
payload: { message: 'No compatible app protocol version' },
|
|
177
|
+
type: 'error',
|
|
178
|
+
})
|
|
179
|
+
break
|
|
180
|
+
}
|
|
181
|
+
negotiatedVersions.set(socket, selectedVersion)
|
|
182
|
+
logDebug('daemon.request.hello.success', { selectedVersion })
|
|
183
|
+
send(socket, {
|
|
184
|
+
id: message.id,
|
|
185
|
+
payload: {
|
|
186
|
+
maxVersion: IPC_PROTOCOL_VERSION,
|
|
187
|
+
minVersion: IPC_PROTOCOL_MIN_VERSION,
|
|
188
|
+
processVersion: getProcessVersion(),
|
|
189
|
+
selectedVersion,
|
|
190
|
+
},
|
|
191
|
+
type: 'helloResult',
|
|
192
|
+
})
|
|
193
|
+
break
|
|
194
|
+
}
|
|
195
|
+
case 'attach': {
|
|
196
|
+
logDebug('daemon.request.attach.start', {
|
|
197
|
+
cols: message.payload.cols,
|
|
198
|
+
rows: message.payload.rows,
|
|
199
|
+
sessionId: message.payload.sessionId,
|
|
200
|
+
snapshotTabs: message.payload.workspaceSnapshot?.tabs.length ?? 0,
|
|
201
|
+
})
|
|
202
|
+
const negotiatedVersion = requireNegotiatedVersion(socket, negotiatedVersions)
|
|
203
|
+
if (message.payload.protocolVersion !== negotiatedVersion) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
`Protocol mismatch: client v${message.payload.protocolVersion}, daemon v${negotiatedVersion}`
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
attachedSessions.set(socket, message.payload.sessionId)
|
|
210
|
+
const attachResult = await manager.attachSession({
|
|
211
|
+
cols: message.payload.cols,
|
|
212
|
+
rows: message.payload.rows,
|
|
213
|
+
sessionId: message.payload.sessionId,
|
|
214
|
+
workspaceSnapshot: message.payload.workspaceSnapshot,
|
|
215
|
+
})
|
|
216
|
+
send(socket, {
|
|
217
|
+
id: message.id,
|
|
218
|
+
payload: {
|
|
219
|
+
activeTabId: attachResult.activeTabId,
|
|
220
|
+
protocolVersion: negotiatedVersion,
|
|
221
|
+
tabs: attachResult.tabs,
|
|
222
|
+
},
|
|
223
|
+
type: 'attachResult',
|
|
224
|
+
})
|
|
225
|
+
logDebug('daemon.request.attach.success', {
|
|
226
|
+
activeTabId: attachResult.activeTabId,
|
|
227
|
+
sessionId: message.payload.sessionId,
|
|
228
|
+
tabs: attachResult.tabs.length,
|
|
229
|
+
})
|
|
230
|
+
break
|
|
231
|
+
}
|
|
232
|
+
case 'createTab': {
|
|
233
|
+
const sessionId = requireSession(socket, attachedSessions)
|
|
234
|
+
requireNegotiatedVersion(socket, negotiatedVersions)
|
|
235
|
+
logDebug('daemon.request.createTab.start', {
|
|
236
|
+
command: message.payload.command,
|
|
237
|
+
sessionId,
|
|
238
|
+
tabId: message.payload.tabId,
|
|
239
|
+
title: message.payload.title,
|
|
240
|
+
})
|
|
241
|
+
await manager.createTab({ ...message.payload, sessionId })
|
|
242
|
+
sendOk(socket, message.id)
|
|
243
|
+
logDebug('daemon.request.createTab.success', {
|
|
244
|
+
sessionId,
|
|
245
|
+
tabId: message.payload.tabId,
|
|
246
|
+
})
|
|
247
|
+
break
|
|
248
|
+
}
|
|
249
|
+
case 'write': {
|
|
250
|
+
const sessionId = requireSession(socket, attachedSessions)
|
|
251
|
+
requireNegotiatedVersion(socket, negotiatedVersions)
|
|
252
|
+
await manager.write(sessionId, message.payload.tabId, message.payload.data)
|
|
253
|
+
sendOk(socket, message.id)
|
|
254
|
+
break
|
|
255
|
+
}
|
|
256
|
+
case 'resizeClient': {
|
|
257
|
+
const sessionId = requireSession(socket, attachedSessions)
|
|
258
|
+
requireNegotiatedVersion(socket, negotiatedVersions)
|
|
259
|
+
await manager.resize(sessionId, message.payload.cols, message.payload.rows)
|
|
260
|
+
sendOk(socket, message.id)
|
|
261
|
+
break
|
|
166
262
|
}
|
|
167
|
-
|
|
168
|
-
|
|
263
|
+
case 'resizeTab': {
|
|
264
|
+
const sessionId = requireSession(socket, attachedSessions)
|
|
265
|
+
requireNegotiatedVersion(socket, negotiatedVersions)
|
|
266
|
+
await manager.resizeTab(
|
|
267
|
+
sessionId,
|
|
268
|
+
message.payload.tabId,
|
|
269
|
+
message.payload.cols,
|
|
270
|
+
message.payload.rows
|
|
271
|
+
)
|
|
272
|
+
sendOk(socket, message.id)
|
|
273
|
+
break
|
|
274
|
+
}
|
|
275
|
+
case 'scroll': {
|
|
276
|
+
const sessionId = requireSession(socket, attachedSessions)
|
|
277
|
+
requireNegotiatedVersion(socket, negotiatedVersions)
|
|
278
|
+
await manager.scroll(sessionId, message.payload.tabId, message.payload.deltaLines)
|
|
279
|
+
sendOk(socket, message.id)
|
|
280
|
+
break
|
|
281
|
+
}
|
|
282
|
+
case 'scrollToBottom': {
|
|
283
|
+
const sessionId = requireSession(socket, attachedSessions)
|
|
284
|
+
requireNegotiatedVersion(socket, negotiatedVersions)
|
|
285
|
+
await manager.scrollToBottom(sessionId, message.payload.tabId)
|
|
286
|
+
sendOk(socket, message.id)
|
|
287
|
+
break
|
|
288
|
+
}
|
|
289
|
+
case 'setActiveTab': {
|
|
290
|
+
const sessionId = requireSession(socket, attachedSessions)
|
|
291
|
+
requireNegotiatedVersion(socket, negotiatedVersions)
|
|
292
|
+
await manager.setActiveTab(sessionId, message.payload.tabId)
|
|
293
|
+
sendOk(socket, message.id)
|
|
294
|
+
break
|
|
295
|
+
}
|
|
296
|
+
case 'closeTab': {
|
|
297
|
+
const sessionId = requireSession(socket, attachedSessions)
|
|
298
|
+
requireNegotiatedVersion(socket, negotiatedVersions)
|
|
299
|
+
await manager.closeTab(sessionId, message.payload.tabId)
|
|
300
|
+
sendOk(socket, message.id)
|
|
301
|
+
break
|
|
302
|
+
}
|
|
303
|
+
case 'disposeAll': {
|
|
304
|
+
const sessionId = attachedSessions.get(socket)
|
|
305
|
+
requireNegotiatedVersion(socket, negotiatedVersions)
|
|
306
|
+
if (sessionId) {
|
|
307
|
+
await manager.disposeSession(sessionId)
|
|
308
|
+
}
|
|
309
|
+
sendOk(socket, message.id)
|
|
310
|
+
break
|
|
311
|
+
}
|
|
312
|
+
case 'ping':
|
|
313
|
+
sendOk(socket, message.id)
|
|
314
|
+
break
|
|
169
315
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
316
|
+
} catch (error) {
|
|
317
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
318
|
+
logDebug('daemon.request.error', {
|
|
319
|
+
error: errorMessage,
|
|
320
|
+
requestId: message.id,
|
|
321
|
+
type: message.type,
|
|
322
|
+
})
|
|
323
|
+
send(socket, { id: message.id, payload: { message: errorMessage }, type: 'error' })
|
|
173
324
|
}
|
|
174
|
-
} catch (error) {
|
|
175
|
-
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
176
|
-
logDebug('daemon.request.error', { error: errorMessage, requestId: message.id })
|
|
177
|
-
send(socket, { id: message.id, payload: { message: errorMessage }, type: 'error' })
|
|
178
325
|
}
|
|
326
|
+
} catch (error) {
|
|
327
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
328
|
+
logDebug('daemon.decoder.error', { error: message })
|
|
329
|
+
decoder.reset()
|
|
330
|
+
send(socket, { id: crypto.randomUUID(), payload: { message }, type: 'error' })
|
|
179
331
|
}
|
|
180
|
-
}
|
|
181
|
-
const message = error instanceof Error ? error.message : String(error)
|
|
182
|
-
logDebug('daemon.request.error', { error: message })
|
|
183
|
-
decoder.reset()
|
|
184
|
-
send(socket, { id: crypto.randomUUID(), payload: { message }, type: 'error' })
|
|
185
|
-
}
|
|
332
|
+
})()
|
|
186
333
|
})
|
|
187
334
|
|
|
188
335
|
socket.on('close', () => {
|
|
189
|
-
logDebug('daemon.client.close')
|
|
336
|
+
logDebug('daemon.client.close', { sessionId: attachedSessions.get(socket) ?? null })
|
|
190
337
|
sockets.delete(socket)
|
|
191
338
|
attachedSessions.delete(socket)
|
|
339
|
+
negotiatedVersions.delete(socket)
|
|
192
340
|
})
|
|
193
341
|
socket.on('error', () => {
|
|
194
|
-
logDebug('daemon.client.error')
|
|
342
|
+
logDebug('daemon.client.error', { sessionId: attachedSessions.get(socket) ?? null })
|
|
195
343
|
sockets.delete(socket)
|
|
196
344
|
attachedSessions.delete(socket)
|
|
345
|
+
negotiatedVersions.delete(socket)
|
|
197
346
|
})
|
|
198
347
|
})
|
|
199
348
|
|
|
@@ -201,12 +350,11 @@ export async function runDaemon(): Promise<void> {
|
|
|
201
350
|
server.once('error', reject)
|
|
202
351
|
server.listen(socketPath, () => resolve())
|
|
203
352
|
})
|
|
204
|
-
|
|
205
|
-
logDebug('daemon.listening', { socketPath })
|
|
353
|
+
tightenSocketPermissions(socketPath)
|
|
206
354
|
|
|
207
355
|
const gracefulShutdown = (signal: string) => {
|
|
208
356
|
logDebug(`daemon.${signal}`)
|
|
209
|
-
|
|
357
|
+
manager.destroy()
|
|
210
358
|
server.close()
|
|
211
359
|
process.exit(0)
|
|
212
360
|
}
|
|
@@ -216,18 +364,14 @@ export async function runDaemon(): Promise<void> {
|
|
|
216
364
|
|
|
217
365
|
process.on('uncaughtException', (error) => {
|
|
218
366
|
logDebug('daemon.uncaughtException', { error: error.message, stack: error.stack })
|
|
219
|
-
|
|
220
|
-
server.close()
|
|
221
|
-
process.exit(1)
|
|
367
|
+
gracefulShutdown('uncaughtException')
|
|
222
368
|
})
|
|
223
369
|
|
|
224
370
|
process.on('unhandledRejection', (reason) => {
|
|
225
371
|
const message = reason instanceof Error ? reason.message : String(reason)
|
|
226
372
|
const stack = reason instanceof Error ? reason.stack : undefined
|
|
227
373
|
logDebug('daemon.unhandledRejection', { error: message, stack })
|
|
228
|
-
|
|
229
|
-
server.close()
|
|
230
|
-
process.exit(1)
|
|
374
|
+
gracefulShutdown('unhandledRejection')
|
|
231
375
|
})
|
|
232
376
|
|
|
233
377
|
await new Promise<void>(() => {
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { chmodSync, constants, existsSync, lstatSync, mkdirSync, unlinkSync } from 'node:fs'
|
|
2
2
|
import { dirname, join } from 'node:path'
|
|
3
3
|
|
|
4
|
+
import { getProfileName } from '../profile-paths'
|
|
5
|
+
|
|
6
|
+
export function getRuntimeProfile(): string {
|
|
7
|
+
return getProfileName()
|
|
8
|
+
}
|
|
9
|
+
|
|
4
10
|
function getRuntimeBaseDir(): string {
|
|
5
11
|
if (process.env.XDG_RUNTIME_DIR) {
|
|
6
|
-
return join(process.env.XDG_RUNTIME_DIR,
|
|
12
|
+
return join(process.env.XDG_RUNTIME_DIR, `aimux-${getRuntimeProfile()}`)
|
|
7
13
|
}
|
|
8
14
|
|
|
9
|
-
return join(process.env.HOME ?? '.', '.local', 'state',
|
|
15
|
+
return join(process.env.HOME ?? '.', '.local', 'state', `aimux-${getRuntimeProfile()}`)
|
|
10
16
|
}
|
|
11
17
|
|
|
12
18
|
export function ensureRuntimeDir(): string {
|
|
@@ -24,6 +30,14 @@ export function getDaemonSocketPath(): string {
|
|
|
24
30
|
return join(ensureRuntimeDir(), 'daemon.sock')
|
|
25
31
|
}
|
|
26
32
|
|
|
33
|
+
export function getIpcDaemonSocketPath(): string {
|
|
34
|
+
return getDaemonSocketPath()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getTerminalManagerSocketPath(): string {
|
|
38
|
+
return join(ensureRuntimeDir(), 'terminal-manager.sock')
|
|
39
|
+
}
|
|
40
|
+
|
|
27
41
|
export function ensureParentDir(filePath: string): void {
|
|
28
42
|
mkdirSync(dirname(filePath), { recursive: true })
|
|
29
43
|
}
|
|
@@ -36,6 +50,10 @@ export function tightenDaemonSocketPermissions(socketPath: string): void {
|
|
|
36
50
|
}
|
|
37
51
|
}
|
|
38
52
|
|
|
53
|
+
export function tightenSocketPermissions(socketPath: string): void {
|
|
54
|
+
tightenDaemonSocketPermissions(socketPath)
|
|
55
|
+
}
|
|
56
|
+
|
|
39
57
|
export function getDaemonSocketSecurityIssue(socketPath: string): string | null {
|
|
40
58
|
if (!existsSync(socketPath)) {
|
|
41
59
|
return 'socket missing'
|
|
@@ -61,9 +79,23 @@ export function getDaemonSocketSecurityIssue(socketPath: string): string | null
|
|
|
61
79
|
}
|
|
62
80
|
}
|
|
63
81
|
|
|
82
|
+
export function getSocketSecurityIssue(socketPath: string): string | null {
|
|
83
|
+
return getDaemonSocketSecurityIssue(socketPath)
|
|
84
|
+
}
|
|
85
|
+
|
|
64
86
|
export function removeDaemonSocketIfExists(): void {
|
|
65
87
|
const socketPath = getDaemonSocketPath()
|
|
66
88
|
if (existsSync(socketPath)) {
|
|
67
89
|
unlinkSync(socketPath)
|
|
68
90
|
}
|
|
69
91
|
}
|
|
92
|
+
|
|
93
|
+
export function removeSocketIfExists(socketPath: string): void {
|
|
94
|
+
if (existsSync(socketPath)) {
|
|
95
|
+
unlinkSync(socketPath)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function removeTerminalManagerSocketIfExists(): void {
|
|
100
|
+
removeSocketIfExists(getTerminalManagerSocketPath())
|
|
101
|
+
}
|
package/src/index.tsx
CHANGED
|
@@ -4,13 +4,16 @@ import { createRoot } from '@opentui/react'
|
|
|
4
4
|
|
|
5
5
|
import { App } from './app'
|
|
6
6
|
import { runDaemon } from './daemon/daemon'
|
|
7
|
+
import { getRuntimeProfile } from './daemon/runtime-paths'
|
|
7
8
|
import { logDebug } from './debug/input-log'
|
|
8
9
|
import { runDoctor } from './doctor'
|
|
9
10
|
import { runRestartDaemon } from './restart-daemon'
|
|
10
11
|
import { createSessionBackend } from './session-backend/bootstrap'
|
|
12
|
+
import { runTerminalManager } from './terminal-manager/terminal-manager'
|
|
11
13
|
import { runUpdate } from './update'
|
|
12
14
|
|
|
13
15
|
const command = process.argv[2]
|
|
16
|
+
const runtimeProfile = getRuntimeProfile()
|
|
14
17
|
|
|
15
18
|
if (command === '--version' || command === '-v' || command === 'version') {
|
|
16
19
|
const { version } = await import('../package.json')
|
|
@@ -31,13 +34,18 @@ if (command === 'update') {
|
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
if (command === 'daemon') {
|
|
34
|
-
logDebug('index.daemonMode')
|
|
37
|
+
logDebug('index.daemonMode', { runtimeProfile })
|
|
35
38
|
await runDaemon()
|
|
36
39
|
}
|
|
37
40
|
|
|
41
|
+
if (command === 'terminal-manager') {
|
|
42
|
+
logDebug('index.terminalManagerMode', { runtimeProfile })
|
|
43
|
+
await runTerminalManager()
|
|
44
|
+
}
|
|
45
|
+
|
|
38
46
|
if (command === '--help' || command === '-h') {
|
|
39
47
|
process.stdout.write(
|
|
40
|
-
'aimux -- terminal multiplexer for AI CLIs\n\nUsage:\n aimux Start aimux\n aimux update Update to latest version\n aimux doctor Diagnose setup issues\n aimux restart-daemon Restart
|
|
48
|
+
'aimux -- terminal multiplexer for AI CLIs\n\nUsage:\n aimux Start aimux\n aimux update Update to latest version\n aimux doctor Diagnose setup issues\n aimux restart-daemon Restart IPC daemon\n\n'
|
|
41
49
|
)
|
|
42
50
|
process.exit(0)
|
|
43
51
|
}
|
|
@@ -51,6 +59,6 @@ const renderer = await createCliRenderer({
|
|
|
51
59
|
})
|
|
52
60
|
|
|
53
61
|
const backend = await createSessionBackend()
|
|
54
|
-
logDebug('index.backendReady', { backend: backend.constructor.name })
|
|
62
|
+
logDebug('index.backendReady', { backend: backend.constructor.name, runtimeProfile })
|
|
55
63
|
|
|
56
64
|
createRoot(renderer).render(<App backend={backend} />)
|