@caoruhua/open-claude-remote 0.1.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/LICENSE +21 -0
- package/README.md +449 -0
- package/dist/api/auth-routes.d.ts +4 -0
- package/dist/api/auth-routes.d.ts.map +1 -0
- package/dist/api/auth-routes.js +7 -0
- package/dist/api/auth-routes.js.map +1 -0
- package/dist/api/config-routes.d.ts +4 -0
- package/dist/api/config-routes.d.ts.map +1 -0
- package/dist/api/config-routes.js +180 -0
- package/dist/api/config-routes.js.map +1 -0
- package/dist/api/health-routes.d.ts +3 -0
- package/dist/api/health-routes.d.ts.map +1 -0
- package/dist/api/health-routes.js +9 -0
- package/dist/api/health-routes.js.map +1 -0
- package/dist/api/hook-routes.d.ts +4 -0
- package/dist/api/hook-routes.d.ts.map +1 -0
- package/dist/api/hook-routes.js +32 -0
- package/dist/api/hook-routes.js.map +1 -0
- package/dist/api/instance-routes.d.ts +20 -0
- package/dist/api/instance-routes.d.ts.map +1 -0
- package/dist/api/instance-routes.js +128 -0
- package/dist/api/instance-routes.js.map +1 -0
- package/dist/api/push-routes.d.ts +5 -0
- package/dist/api/push-routes.d.ts.map +1 -0
- package/dist/api/push-routes.js +45 -0
- package/dist/api/push-routes.js.map +1 -0
- package/dist/api/router.d.ts +19 -0
- package/dist/api/router.d.ts.map +1 -0
- package/dist/api/router.js +37 -0
- package/dist/api/router.js.map +1 -0
- package/dist/api/status-routes.d.ts +5 -0
- package/dist/api/status-routes.d.ts.map +1 -0
- package/dist/api/status-routes.js +17 -0
- package/dist/api/status-routes.js.map +1 -0
- package/dist/attach.d.ts +9 -0
- package/dist/attach.d.ts.map +1 -0
- package/dist/attach.js +155 -0
- package/dist/attach.js.map +1 -0
- package/dist/auth/auth-middleware.d.ts +52 -0
- package/dist/auth/auth-middleware.d.ts.map +1 -0
- package/dist/auth/auth-middleware.js +124 -0
- package/dist/auth/auth-middleware.js.map +1 -0
- package/dist/auth/rate-limiter.d.ts +37 -0
- package/dist/auth/rate-limiter.d.ts.map +1 -0
- package/dist/auth/rate-limiter.js +81 -0
- package/dist/auth/rate-limiter.js.map +1 -0
- package/dist/auth/token-generator.d.ts +9 -0
- package/dist/auth/token-generator.d.ts.map +1 -0
- package/dist/auth/token-generator.js +15 -0
- package/dist/auth/token-generator.js.map +1 -0
- package/dist/cli-utils.d.ts +19 -0
- package/dist/cli-utils.d.ts.map +1 -0
- package/dist/cli-utils.js +132 -0
- package/dist/cli-utils.js.map +1 -0
- package/dist/cli.d.ts +21 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +58 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +146 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +329 -0
- package/dist/config.js.map +1 -0
- package/dist/hooks/hook-receiver.d.ts +39 -0
- package/dist/hooks/hook-receiver.d.ts.map +1 -0
- package/dist/hooks/hook-receiver.js +46 -0
- package/dist/hooks/hook-receiver.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +353 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/logger.d.ts +8 -0
- package/dist/logger/logger.d.ts.map +1 -0
- package/dist/logger/logger.js +90 -0
- package/dist/logger/logger.js.map +1 -0
- package/dist/notification/dingtalk-service.d.ts +24 -0
- package/dist/notification/dingtalk-service.d.ts.map +1 -0
- package/dist/notification/dingtalk-service.js +94 -0
- package/dist/notification/dingtalk-service.js.map +1 -0
- package/dist/pty/output-buffer.d.ts +25 -0
- package/dist/pty/output-buffer.d.ts.map +1 -0
- package/dist/pty/output-buffer.js +58 -0
- package/dist/pty/output-buffer.js.map +1 -0
- package/dist/pty/pty-manager.d.ts +45 -0
- package/dist/pty/pty-manager.d.ts.map +1 -0
- package/dist/pty/pty-manager.js +108 -0
- package/dist/pty/pty-manager.js.map +1 -0
- package/dist/pty/types.d.ts +11 -0
- package/dist/pty/types.d.ts.map +1 -0
- package/dist/pty/types.js +2 -0
- package/dist/pty/types.js.map +1 -0
- package/dist/pty/virtual-pty.d.ts +37 -0
- package/dist/pty/virtual-pty.d.ts.map +1 -0
- package/dist/pty/virtual-pty.js +161 -0
- package/dist/pty/virtual-pty.js.map +1 -0
- package/dist/push/push-service.d.ts +87 -0
- package/dist/push/push-service.d.ts.map +1 -0
- package/dist/push/push-service.js +301 -0
- package/dist/push/push-service.js.map +1 -0
- package/dist/registry/instance-registry.d.ts +32 -0
- package/dist/registry/instance-registry.d.ts.map +1 -0
- package/dist/registry/instance-registry.js +115 -0
- package/dist/registry/instance-registry.js.map +1 -0
- package/dist/registry/instance-spawner.d.ts +33 -0
- package/dist/registry/instance-spawner.d.ts.map +1 -0
- package/dist/registry/instance-spawner.js +91 -0
- package/dist/registry/instance-spawner.js.map +1 -0
- package/dist/registry/port-finder.d.ts +8 -0
- package/dist/registry/port-finder.d.ts.map +1 -0
- package/dist/registry/port-finder.js +35 -0
- package/dist/registry/port-finder.js.map +1 -0
- package/dist/registry/shared-token.d.ts +11 -0
- package/dist/registry/shared-token.d.ts.map +1 -0
- package/dist/registry/shared-token.js +82 -0
- package/dist/registry/shared-token.js.map +1 -0
- package/dist/registry/stop-instances.d.ts +27 -0
- package/dist/registry/stop-instances.d.ts.map +1 -0
- package/dist/registry/stop-instances.js +212 -0
- package/dist/registry/stop-instances.js.map +1 -0
- package/dist/session/session-controller.d.ts +58 -0
- package/dist/session/session-controller.d.ts.map +1 -0
- package/dist/session/session-controller.js +273 -0
- package/dist/session/session-controller.js.map +1 -0
- package/dist/terminal/terminal-relay.d.ts +29 -0
- package/dist/terminal/terminal-relay.d.ts.map +1 -0
- package/dist/terminal/terminal-relay.js +106 -0
- package/dist/terminal/terminal-relay.js.map +1 -0
- package/dist/utils/ansi-filter.d.ts +41 -0
- package/dist/utils/ansi-filter.d.ts.map +1 -0
- package/dist/utils/ansi-filter.js +147 -0
- package/dist/utils/ansi-filter.js.map +1 -0
- package/dist/utils/file-lock.d.ts +23 -0
- package/dist/utils/file-lock.d.ts.map +1 -0
- package/dist/utils/file-lock.js +125 -0
- package/dist/utils/file-lock.js.map +1 -0
- package/dist/utils/ip-monitor.d.ts +39 -0
- package/dist/utils/ip-monitor.d.ts.map +1 -0
- package/dist/utils/ip-monitor.js +114 -0
- package/dist/utils/ip-monitor.js.map +1 -0
- package/dist/utils/network.d.ts +19 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +54 -0
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/pid-file.d.ts +10 -0
- package/dist/utils/pid-file.d.ts.map +1 -0
- package/dist/utils/pid-file.js +30 -0
- package/dist/utils/pid-file.js.map +1 -0
- package/dist/utils/qrcode-banner.d.ts +6 -0
- package/dist/utils/qrcode-banner.d.ts.map +1 -0
- package/dist/utils/qrcode-banner.js +16 -0
- package/dist/utils/qrcode-banner.js.map +1 -0
- package/dist/ws/ws-handler.d.ts +10 -0
- package/dist/ws/ws-handler.d.ts.map +1 -0
- package/dist/ws/ws-handler.js +36 -0
- package/dist/ws/ws-handler.js.map +1 -0
- package/dist/ws/ws-server.d.ts +78 -0
- package/dist/ws/ws-server.d.ts.map +1 -0
- package/dist/ws/ws-server.js +223 -0
- package/dist/ws/ws-server.js.map +1 -0
- package/frontend-dist/assets/index-BKudo1Dw.css +32 -0
- package/frontend-dist/assets/index-BqqB1hYe.js +141 -0
- package/frontend-dist/index.html +15 -0
- package/frontend-dist/sw.js +46 -0
- package/package.json +79 -0
- package/shared-dist/constants.d.ts +10 -0
- package/shared-dist/constants.d.ts.map +1 -0
- package/shared-dist/constants.js +10 -0
- package/shared-dist/constants.js.map +1 -0
- package/shared-dist/defaults.d.ts +30 -0
- package/shared-dist/defaults.d.ts.map +1 -0
- package/shared-dist/defaults.js +31 -0
- package/shared-dist/defaults.js.map +1 -0
- package/shared-dist/index.d.ts +5 -0
- package/shared-dist/index.d.ts.map +1 -0
- package/shared-dist/index.js +5 -0
- package/shared-dist/index.js.map +1 -0
- package/shared-dist/instance.d.ts +25 -0
- package/shared-dist/instance.d.ts.map +1 -0
- package/shared-dist/instance.js +7 -0
- package/shared-dist/instance.js.map +1 -0
- package/shared-dist/question-utils.d.ts +15 -0
- package/shared-dist/question-utils.d.ts.map +1 -0
- package/shared-dist/question-utils.js +24 -0
- package/shared-dist/question-utils.js.map +1 -0
- package/shared-dist/ws-protocol.d.ts +60 -0
- package/shared-dist/ws-protocol.d.ts.map +1 -0
- package/shared-dist/ws-protocol.js +5 -0
- package/shared-dist/ws-protocol.js.map +1 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { OutputBuffer } from '../pty/output-buffer.js';
|
|
2
|
+
import { handleWsMessage } from '../ws/ws-handler.js';
|
|
3
|
+
import { logger } from '../logger/logger.js';
|
|
4
|
+
const WS_FLUSH_INTERVAL_MS = 16;
|
|
5
|
+
const WS_MAX_CHUNK_BYTES = 32 * 1024;
|
|
6
|
+
const WS_HIGH_WATERMARK_BYTES = 256 * 1024;
|
|
7
|
+
/**
|
|
8
|
+
* Core coordinator: wires PTY ↔ WebSocket ↔ Terminal ↔ Hooks.
|
|
9
|
+
*/
|
|
10
|
+
export class SessionController {
|
|
11
|
+
ptyManager;
|
|
12
|
+
wsServer;
|
|
13
|
+
hookReceiver;
|
|
14
|
+
terminalRelay;
|
|
15
|
+
_status = 'idle';
|
|
16
|
+
buffer;
|
|
17
|
+
pushService = null;
|
|
18
|
+
dingtalkService = null;
|
|
19
|
+
wsPendingChunks = [];
|
|
20
|
+
wsPendingBytes = 0;
|
|
21
|
+
wsFlushTimer = null;
|
|
22
|
+
ptyInputBytesTotal = 0;
|
|
23
|
+
wsFlushCount = 0;
|
|
24
|
+
wsFlushBytesTotal = 0;
|
|
25
|
+
wsMaxPendingBytes = 0;
|
|
26
|
+
wsBackpressureEvents = 0;
|
|
27
|
+
constructor(ptyManager, wsServer, hookReceiver, maxBufferLines, terminalRelay) {
|
|
28
|
+
this.ptyManager = ptyManager;
|
|
29
|
+
this.wsServer = wsServer;
|
|
30
|
+
this.hookReceiver = hookReceiver;
|
|
31
|
+
this.terminalRelay = terminalRelay;
|
|
32
|
+
this.buffer = new OutputBuffer(maxBufferLines);
|
|
33
|
+
this.setupPtyHandlers();
|
|
34
|
+
this.setupWsHandlers();
|
|
35
|
+
this.setupHookHandlers();
|
|
36
|
+
}
|
|
37
|
+
get status() {
|
|
38
|
+
return this._status;
|
|
39
|
+
}
|
|
40
|
+
get connectedClients() {
|
|
41
|
+
return this.wsServer.clientCount;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Inject PushService for hook-triggered notifications.
|
|
45
|
+
*/
|
|
46
|
+
setPushService(pushService) {
|
|
47
|
+
this.pushService = pushService;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Inject DingtalkService for hook-triggered notifications.
|
|
51
|
+
*/
|
|
52
|
+
setDingtalkService(dingtalkService) {
|
|
53
|
+
this.dingtalkService = dingtalkService;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Wire PTY output → buffer + WS broadcast + process.stdout
|
|
57
|
+
*/
|
|
58
|
+
setupPtyHandlers() {
|
|
59
|
+
this.ptyManager.on('data', (data) => {
|
|
60
|
+
// Write to PC terminal
|
|
61
|
+
process.stdout.write(data);
|
|
62
|
+
this.ptyInputBytesTotal += Buffer.byteLength(data, 'utf8');
|
|
63
|
+
// Buffer raw data for reconnection history
|
|
64
|
+
this.buffer.append(data);
|
|
65
|
+
// Broadcast raw PTY output to web clients (batched)
|
|
66
|
+
// xterm.js handles all ANSI sequences natively including alternate screen
|
|
67
|
+
this.enqueueWsOutput(data);
|
|
68
|
+
});
|
|
69
|
+
this.ptyManager.on('exit', (exitCode) => {
|
|
70
|
+
this.flushPendingWsOutput();
|
|
71
|
+
this._status = 'idle';
|
|
72
|
+
this.wsServer.broadcast({
|
|
73
|
+
type: 'session_ended',
|
|
74
|
+
exitCode,
|
|
75
|
+
reason: exitCode === 0 ? 'Process exited normally' : `Process exited with code ${exitCode}`,
|
|
76
|
+
});
|
|
77
|
+
logger.info({
|
|
78
|
+
exitCode,
|
|
79
|
+
ptyInputBytesTotal: this.ptyInputBytesTotal,
|
|
80
|
+
wsFlushCount: this.wsFlushCount,
|
|
81
|
+
wsFlushBytesTotal: this.wsFlushBytesTotal,
|
|
82
|
+
wsMaxPendingBytes: this.wsMaxPendingBytes,
|
|
83
|
+
wsBackpressureEvents: this.wsBackpressureEvents,
|
|
84
|
+
}, 'Claude Code session ended');
|
|
85
|
+
});
|
|
86
|
+
this.ptyManager.on('error', (err) => {
|
|
87
|
+
logger.error({ err }, 'PTY error');
|
|
88
|
+
this.wsServer.broadcast({
|
|
89
|
+
type: 'error',
|
|
90
|
+
code: 'pty_error',
|
|
91
|
+
message: err.message,
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
// Broadcast PTY resize to web clients
|
|
95
|
+
this.ptyManager.on('resize', (cols, rows) => {
|
|
96
|
+
logger.debug({ cols, rows }, 'PTY resize event, broadcasting to web clients');
|
|
97
|
+
this.wsServer.broadcast({
|
|
98
|
+
type: 'terminal_resize',
|
|
99
|
+
cols,
|
|
100
|
+
rows,
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
enqueueWsOutput(data) {
|
|
105
|
+
this.wsPendingChunks.push(data);
|
|
106
|
+
this.wsPendingBytes += Buffer.byteLength(data, 'utf8');
|
|
107
|
+
this.wsMaxPendingBytes = Math.max(this.wsMaxPendingBytes, this.wsPendingBytes);
|
|
108
|
+
if (this.wsPendingBytes >= WS_HIGH_WATERMARK_BYTES) {
|
|
109
|
+
this.wsBackpressureEvents += 1;
|
|
110
|
+
this.flushPendingWsOutput();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (this.wsPendingBytes >= WS_MAX_CHUNK_BYTES) {
|
|
114
|
+
this.flushPendingWsOutput();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (!this.wsFlushTimer) {
|
|
118
|
+
this.wsFlushTimer = setTimeout(() => {
|
|
119
|
+
this.wsFlushTimer = null;
|
|
120
|
+
this.flushPendingWsOutput();
|
|
121
|
+
}, WS_FLUSH_INTERVAL_MS);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
flushPendingWsOutput() {
|
|
125
|
+
if (this.wsFlushTimer) {
|
|
126
|
+
clearTimeout(this.wsFlushTimer);
|
|
127
|
+
this.wsFlushTimer = null;
|
|
128
|
+
}
|
|
129
|
+
if (this.wsPendingChunks.length === 0) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const output = this.wsPendingChunks.join('');
|
|
133
|
+
const flushBytes = this.wsPendingBytes;
|
|
134
|
+
this.wsPendingChunks = [];
|
|
135
|
+
this.wsPendingBytes = 0;
|
|
136
|
+
this.wsFlushCount += 1;
|
|
137
|
+
this.wsFlushBytesTotal += flushBytes;
|
|
138
|
+
this.wsServer.broadcast({
|
|
139
|
+
type: 'terminal_output',
|
|
140
|
+
data: output,
|
|
141
|
+
seq: this.buffer.sequenceNumber,
|
|
142
|
+
});
|
|
143
|
+
logger.debug({
|
|
144
|
+
flushBytes,
|
|
145
|
+
wsFlushCount: this.wsFlushCount,
|
|
146
|
+
wsPendingBytes: this.wsPendingBytes,
|
|
147
|
+
wsMaxPendingBytes: this.wsMaxPendingBytes,
|
|
148
|
+
wsBackpressureEvents: this.wsBackpressureEvents,
|
|
149
|
+
}, 'Flushed batched terminal output');
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Wire WS messages → PTY input
|
|
153
|
+
*/
|
|
154
|
+
setupWsHandlers() {
|
|
155
|
+
this.wsServer.onMessage((ws, data, clientType) => {
|
|
156
|
+
handleWsMessage(ws, data, {
|
|
157
|
+
onUserInput: (input) => {
|
|
158
|
+
logger.debug({ length: input.length }, 'User input from client');
|
|
159
|
+
this.ptyManager.write(input);
|
|
160
|
+
},
|
|
161
|
+
onResize: (cols, rows) => {
|
|
162
|
+
// webapp 在线时忽略 attach 的 resize(webapp 是主控端)
|
|
163
|
+
if (clientType === 'attach') {
|
|
164
|
+
const counts = this.wsServer.getClientCounts();
|
|
165
|
+
if (counts.webapp > 0) {
|
|
166
|
+
logger.debug({ cols, rows }, 'Ignoring attach resize, webapp is master');
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
logger.info({ cols, rows, clientType, currentPtyCols: this.ptyManager.cols, currentPtyRows: this.ptyManager.rows }, 'Resize request from client, applying to PTY');
|
|
171
|
+
this.ptyManager.resize(cols, rows);
|
|
172
|
+
logger.info({ ptyCols: this.ptyManager.cols, ptyRows: this.ptyManager.rows }, 'PTY resize after client request');
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
// 处理客户端连接:区分主控端(webapp)和从控端(attach)
|
|
177
|
+
this.wsServer.onConnect((ws, clientType) => {
|
|
178
|
+
if (clientType === 'attach') {
|
|
179
|
+
// attach 客户端连接:作为从控端
|
|
180
|
+
// 暂停 PC 端 resize,attach 将跟随 webapp 的尺寸
|
|
181
|
+
if (this.terminalRelay) {
|
|
182
|
+
this.terminalRelay.pauseResize();
|
|
183
|
+
}
|
|
184
|
+
logger.info('attach client connected (slave mode)');
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
// WebApp 客户端连接:作为主控端
|
|
188
|
+
// 暂停 PC 端 resize,让 webapp 接管尺寸控制
|
|
189
|
+
const counts = this.wsServer.getClientCounts();
|
|
190
|
+
if (this.terminalRelay && counts.attach === 0 && counts.webapp === 1) {
|
|
191
|
+
this.terminalRelay.pauseResize();
|
|
192
|
+
logger.info('First WebApp client connected, PC resize paused');
|
|
193
|
+
}
|
|
194
|
+
logger.info('WebApp client connected (master mode)');
|
|
195
|
+
}
|
|
196
|
+
// 发送历史数据
|
|
197
|
+
this.wsServer.sendTo(ws, {
|
|
198
|
+
type: 'history_sync',
|
|
199
|
+
data: this.buffer.getFullContent(),
|
|
200
|
+
seq: this.buffer.sequenceNumber,
|
|
201
|
+
status: this._status,
|
|
202
|
+
cols: this.ptyManager.cols,
|
|
203
|
+
rows: this.ptyManager.rows,
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
// 处理客户端断开:根据剩余客户端类型决定是否恢复 PC 端 resize
|
|
207
|
+
this.wsServer.onDisconnect((clientCounts) => {
|
|
208
|
+
const { attach, webapp } = clientCounts;
|
|
209
|
+
if (attach === 0 && webapp === 0) {
|
|
210
|
+
// 所有客户端断开:恢复 PC 端 resize
|
|
211
|
+
if (this.terminalRelay) {
|
|
212
|
+
this.terminalRelay.resumeResize();
|
|
213
|
+
logger.info('All clients disconnected, PC resize resumed');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
else if (attach === 0 && webapp > 0) {
|
|
217
|
+
// attach 断开,但 WebApp 还在:
|
|
218
|
+
// 不恢复 PC 端 resize(WebApp 继续控制尺寸)
|
|
219
|
+
logger.info({ webappCount: webapp }, 'attach disconnected, WebApp still connected');
|
|
220
|
+
}
|
|
221
|
+
else if (attach > 0 && webapp === 0) {
|
|
222
|
+
// WebApp 全部断开,attach 仍在:
|
|
223
|
+
// 广播当前 PTY 尺寸,触发 attach 客户端的 scheduleResizeSync 恢复自身尺寸
|
|
224
|
+
this.wsServer.broadcast({
|
|
225
|
+
type: 'terminal_resize',
|
|
226
|
+
cols: this.ptyManager.cols,
|
|
227
|
+
rows: this.ptyManager.rows,
|
|
228
|
+
});
|
|
229
|
+
logger.info('All WebApps disconnected, broadcast PTY size to trigger attach re-sync');
|
|
230
|
+
}
|
|
231
|
+
// attach 还在且 webapp 也在:保持当前状态
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Wire Hook notifications → status update + Push notification
|
|
236
|
+
*/
|
|
237
|
+
setupHookHandlers() {
|
|
238
|
+
this.hookReceiver.on('notification', (notification) => {
|
|
239
|
+
this._status = 'waiting_input';
|
|
240
|
+
this.wsServer.broadcast({
|
|
241
|
+
type: 'status_update',
|
|
242
|
+
status: 'waiting_input',
|
|
243
|
+
detail: `Waiting for input: ${notification.tool}`,
|
|
244
|
+
});
|
|
245
|
+
// Send push notification if service is available
|
|
246
|
+
if (this.pushService) {
|
|
247
|
+
this.pushService.notifyAll({
|
|
248
|
+
title: 'Claude Code 需要输入',
|
|
249
|
+
body: notification.message,
|
|
250
|
+
tag: 'claude-input',
|
|
251
|
+
renotify: true,
|
|
252
|
+
}).catch((err) => {
|
|
253
|
+
logger.error({ err }, 'Failed to send push notification');
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
// Send dingtalk notification if service is available
|
|
257
|
+
if (this.dingtalkService) {
|
|
258
|
+
this.dingtalkService.sendNotification('Claude Code 需要输入', notification.tool, notification.message).catch((err) => {
|
|
259
|
+
logger.error({ err }, 'Failed to send dingtalk notification');
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
logger.info({ tool: notification.tool }, 'Hook notification processed, status set to waiting_input');
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Update session status.
|
|
267
|
+
*/
|
|
268
|
+
setStatus(status) {
|
|
269
|
+
this._status = status;
|
|
270
|
+
this.wsServer.broadcast({ type: 'status_update', status });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
//# sourceMappingURL=session-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-controller.js","sourceRoot":"","sources":["../../src/session/session-controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { IPtyManager } from '../pty/types.js';
|
|
2
|
+
export declare class TerminalRelay {
|
|
3
|
+
private ptyManager;
|
|
4
|
+
private stdinHandler;
|
|
5
|
+
private resizeHandler;
|
|
6
|
+
private wasRaw;
|
|
7
|
+
private lastCtrlCTime;
|
|
8
|
+
private resizePaused;
|
|
9
|
+
constructor(ptyManager: IPtyManager);
|
|
10
|
+
/**
|
|
11
|
+
* Start relaying stdin → PTY and listen for terminal resize.
|
|
12
|
+
*/
|
|
13
|
+
start(): void;
|
|
14
|
+
/**
|
|
15
|
+
* Pause forwarding PC terminal resize events to PTY.
|
|
16
|
+
* Used when a mobile client connects and becomes the resize source.
|
|
17
|
+
*/
|
|
18
|
+
pauseResize(): void;
|
|
19
|
+
/**
|
|
20
|
+
* Resume forwarding PC terminal resize events to PTY.
|
|
21
|
+
* Immediately syncs current PC terminal size to PTY.
|
|
22
|
+
*/
|
|
23
|
+
resumeResize(): void;
|
|
24
|
+
/**
|
|
25
|
+
* Stop relaying and restore terminal state.
|
|
26
|
+
*/
|
|
27
|
+
stop(): void;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=terminal-relay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal-relay.d.ts","sourceRoot":"","sources":["../../src/terminal/terminal-relay.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAcnD,qBAAa,aAAa;IAOZ,OAAO,CAAC,UAAU;IAN9B,OAAO,CAAC,YAAY,CAAyC;IAC7D,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,YAAY,CAAkB;gBAElB,UAAU,EAAE,WAAW;IAE3C;;OAEG;IACH,KAAK,IAAI,IAAI;IA8Cb;;;OAGG;IACH,WAAW,IAAI,IAAI;IAKnB;;;OAGG;IACH,YAAY,IAAI,IAAI;IAUpB;;OAEG;IACH,IAAI,IAAI,IAAI;CAiBb"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { logger } from '../logger/logger.js';
|
|
2
|
+
/**
|
|
3
|
+
* Relays PC terminal stdin/stdout to/from the PTY process.
|
|
4
|
+
* Sets stdin to raw mode so all keystrokes pass through directly.
|
|
5
|
+
*/
|
|
6
|
+
const CTRL_C = '\x03';
|
|
7
|
+
// Kitty keyboard protocol CSI u variants for Ctrl+C:
|
|
8
|
+
// basic: \x1b[99;5u | with event type: \x1b[99;5:1u | with text: \x1b[99;5;99u
|
|
9
|
+
// Match press/repeat (event type 1 or 2) but not release (3)
|
|
10
|
+
const KITTY_CTRL_C_RE = /\x1b\[99;5(?::(?:[12]))?(?:;\d+)*u/;
|
|
11
|
+
const DOUBLE_CTRL_C_WINDOW_MS = 500;
|
|
12
|
+
export class TerminalRelay {
|
|
13
|
+
ptyManager;
|
|
14
|
+
stdinHandler = null;
|
|
15
|
+
resizeHandler = null;
|
|
16
|
+
wasRaw = false;
|
|
17
|
+
lastCtrlCTime = 0;
|
|
18
|
+
resizePaused = false;
|
|
19
|
+
constructor(ptyManager) {
|
|
20
|
+
this.ptyManager = ptyManager;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Start relaying stdin → PTY and listen for terminal resize.
|
|
24
|
+
*/
|
|
25
|
+
start() {
|
|
26
|
+
if (!process.stdin.isTTY) {
|
|
27
|
+
logger.warn('stdin is not a TTY, terminal relay will not set raw mode');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// Save and set raw mode
|
|
31
|
+
this.wasRaw = process.stdin.isRaw ?? false;
|
|
32
|
+
process.stdin.setRawMode(true);
|
|
33
|
+
process.stdin.resume();
|
|
34
|
+
// stdin → PTY
|
|
35
|
+
this.stdinHandler = (data) => {
|
|
36
|
+
const str = data.toString();
|
|
37
|
+
this.ptyManager.write(str);
|
|
38
|
+
// Double Ctrl+C within window → send SIGINT to proxy process itself
|
|
39
|
+
// Support both classic ETX (\x03) and kitty keyboard protocol CSI u encoding
|
|
40
|
+
if (str.startsWith('\x1b')) {
|
|
41
|
+
logger.debug({ hex: data.toString('hex'), len: data.length }, 'stdin escape sequence received');
|
|
42
|
+
}
|
|
43
|
+
if (str === CTRL_C || KITTY_CTRL_C_RE.test(str)) {
|
|
44
|
+
const now = Date.now();
|
|
45
|
+
if (now - this.lastCtrlCTime <= DOUBLE_CTRL_C_WINDOW_MS) {
|
|
46
|
+
logger.info('Double Ctrl+C detected, sending SIGINT to proxy process');
|
|
47
|
+
process.kill(process.pid, 'SIGINT');
|
|
48
|
+
}
|
|
49
|
+
this.lastCtrlCTime = now;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
process.stdin.on('data', this.stdinHandler);
|
|
53
|
+
// Terminal resize → PTY resize
|
|
54
|
+
this.resizeHandler = () => {
|
|
55
|
+
if (this.resizePaused)
|
|
56
|
+
return;
|
|
57
|
+
const cols = process.stdout.columns;
|
|
58
|
+
const rows = process.stdout.rows;
|
|
59
|
+
if (cols && rows) {
|
|
60
|
+
this.ptyManager.resize(cols, rows);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
process.stdout.on('resize', this.resizeHandler);
|
|
64
|
+
logger.info('Terminal relay started (raw mode)');
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Pause forwarding PC terminal resize events to PTY.
|
|
68
|
+
* Used when a mobile client connects and becomes the resize source.
|
|
69
|
+
*/
|
|
70
|
+
pauseResize() {
|
|
71
|
+
this.resizePaused = true;
|
|
72
|
+
logger.info('Terminal relay: PC resize paused (mobile client connected)');
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Resume forwarding PC terminal resize events to PTY.
|
|
76
|
+
* Immediately syncs current PC terminal size to PTY.
|
|
77
|
+
*/
|
|
78
|
+
resumeResize() {
|
|
79
|
+
this.resizePaused = false;
|
|
80
|
+
const cols = process.stdout.columns;
|
|
81
|
+
const rows = process.stdout.rows;
|
|
82
|
+
if (cols && rows) {
|
|
83
|
+
this.ptyManager.resize(cols, rows);
|
|
84
|
+
}
|
|
85
|
+
logger.info({ cols, rows }, 'Terminal relay: PC resize resumed, synced PTY size');
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Stop relaying and restore terminal state.
|
|
89
|
+
*/
|
|
90
|
+
stop() {
|
|
91
|
+
if (this.stdinHandler) {
|
|
92
|
+
process.stdin.removeListener('data', this.stdinHandler);
|
|
93
|
+
this.stdinHandler = null;
|
|
94
|
+
}
|
|
95
|
+
if (this.resizeHandler) {
|
|
96
|
+
process.stdout.removeListener('resize', this.resizeHandler);
|
|
97
|
+
this.resizeHandler = null;
|
|
98
|
+
}
|
|
99
|
+
if (process.stdin.isTTY) {
|
|
100
|
+
process.stdin.setRawMode(this.wasRaw);
|
|
101
|
+
process.stdin.pause();
|
|
102
|
+
}
|
|
103
|
+
logger.info('Terminal relay stopped');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=terminal-relay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal-relay.js","sourceRoot":"","sources":["../../src/terminal/terminal-relay.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alternate Screen Buffer 过滤器
|
|
3
|
+
*
|
|
4
|
+
* 检测 PTY 输出中的 alternate screen buffer 切换序列(\x1b[?1049h/l),
|
|
5
|
+
* 在交互式 UI 模式时过滤输出,避免 web 端显示错位。
|
|
6
|
+
*
|
|
7
|
+
* 典型场景:Claude Code 斜杠命令选择时,下拉菜单使用 alternate screen,
|
|
8
|
+
* 其 ANSI 定位序列基于 PC 终端尺寸,在 web 端会导致内容错位重叠。
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Alternate Screen Buffer 状态过滤
|
|
12
|
+
*
|
|
13
|
+
* 维护 alternate screen 状态,处理流式数据中可能被切分的 ANSI 序列。
|
|
14
|
+
*/
|
|
15
|
+
export declare class AlternateScreenFilter {
|
|
16
|
+
private _inAlternateScreen;
|
|
17
|
+
private pending;
|
|
18
|
+
/** 当前是否处于 alternate screen 模式 */
|
|
19
|
+
get inAlternateScreen(): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* 处理 PTY 输出数据
|
|
22
|
+
*
|
|
23
|
+
* @param data - PTY 输出的原始 ANSI 字符串
|
|
24
|
+
* @returns 过滤后的数据(alternate screen 期间返回空字符串)
|
|
25
|
+
*/
|
|
26
|
+
process(data: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* 处理数据并返回过滤后的内容
|
|
29
|
+
*/
|
|
30
|
+
private processData;
|
|
31
|
+
/**
|
|
32
|
+
* 保存可能不完整的序列
|
|
33
|
+
* @returns 是否保存了未完成的序列
|
|
34
|
+
*/
|
|
35
|
+
private savePendingIncomplete;
|
|
36
|
+
/**
|
|
37
|
+
* 重置状态
|
|
38
|
+
*/
|
|
39
|
+
reset(): void;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=ansi-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ansi-filter.d.ts","sourceRoot":"","sources":["../../src/utils/ansi-filter.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAWH;;;;GAIG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,kBAAkB,CAAkB;IAC5C,OAAO,CAAC,OAAO,CAAc;IAE7B,iCAAiC;IACjC,IAAI,iBAAiB,IAAI,OAAO,CAE/B;IAED;;;;;OAKG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAS7B;;OAEG;IACH,OAAO,CAAC,WAAW;IAqFnB;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAuB7B;;OAEG;IACH,KAAK,IAAI,IAAI;CAId"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alternate Screen Buffer 过滤器
|
|
3
|
+
*
|
|
4
|
+
* 检测 PTY 输出中的 alternate screen buffer 切换序列(\x1b[?1049h/l),
|
|
5
|
+
* 在交互式 UI 模式时过滤输出,避免 web 端显示错位。
|
|
6
|
+
*
|
|
7
|
+
* 典型场景:Claude Code 斜杠命令选择时,下拉菜单使用 alternate screen,
|
|
8
|
+
* 其 ANSI 定位序列基于 PC 终端尺寸,在 web 端会导致内容错位重叠。
|
|
9
|
+
*/
|
|
10
|
+
/** 进入 alternate screen 的 ANSI 序列 */
|
|
11
|
+
const ALT_SCREEN_ENTER_REGEX = /\x1b\[\?(1049|47)h/;
|
|
12
|
+
/** 退出 alternate screen 的 ANSI 序列 */
|
|
13
|
+
const ALT_SCREEN_EXIT_REGEX = /\x1b\[\?(1049|47)l/;
|
|
14
|
+
/** ANSII ESC 字符 */
|
|
15
|
+
const ESC = '\x1b';
|
|
16
|
+
/**
|
|
17
|
+
* Alternate Screen Buffer 状态过滤
|
|
18
|
+
*
|
|
19
|
+
* 维护 alternate screen 状态,处理流式数据中可能被切分的 ANSI 序列。
|
|
20
|
+
*/
|
|
21
|
+
export class AlternateScreenFilter {
|
|
22
|
+
_inAlternateScreen = false;
|
|
23
|
+
pending = '';
|
|
24
|
+
/** 当前是否处于 alternate screen 模式 */
|
|
25
|
+
get inAlternateScreen() {
|
|
26
|
+
return this._inAlternateScreen;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 处理 PTY 输出数据
|
|
30
|
+
*
|
|
31
|
+
* @param data - PTY 输出的原始 ANSI 字符串
|
|
32
|
+
* @returns 过滤后的数据(alternate screen 期间返回空字符串)
|
|
33
|
+
*/
|
|
34
|
+
process(data) {
|
|
35
|
+
// 合并上次可能未完成的序列
|
|
36
|
+
const combined = this.pending + data;
|
|
37
|
+
this.pending = '';
|
|
38
|
+
// 处理完整数据
|
|
39
|
+
return this.processData(combined);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 处理数据并返回过滤后的内容
|
|
43
|
+
*/
|
|
44
|
+
processData(data) {
|
|
45
|
+
// 查找所有进入和退出序列的位置
|
|
46
|
+
const segments = [];
|
|
47
|
+
// 找出所有进入序列
|
|
48
|
+
let match;
|
|
49
|
+
const enterRegex = new RegExp(ALT_SCREEN_ENTER_REGEX.source, 'g');
|
|
50
|
+
while ((match = enterRegex.exec(data)) !== null) {
|
|
51
|
+
segments.push({ type: 'enter', index: match.index, length: match[0].length });
|
|
52
|
+
}
|
|
53
|
+
// 找出所有退出序列
|
|
54
|
+
const exitRegex = new RegExp(ALT_SCREEN_EXIT_REGEX.source, 'g');
|
|
55
|
+
while ((match = exitRegex.exec(data)) !== null) {
|
|
56
|
+
segments.push({ type: 'exit', index: match.index, length: match[0].length });
|
|
57
|
+
}
|
|
58
|
+
// 按位置排序
|
|
59
|
+
segments.sort((a, b) => a.index - b.index);
|
|
60
|
+
// 如果没有找到任何序列
|
|
61
|
+
if (segments.length === 0) {
|
|
62
|
+
// 检查是否有未完成的序列需要保存
|
|
63
|
+
this.savePendingIncomplete(data);
|
|
64
|
+
if (this._inAlternateScreen) {
|
|
65
|
+
return '';
|
|
66
|
+
}
|
|
67
|
+
return data;
|
|
68
|
+
}
|
|
69
|
+
// 构建结果
|
|
70
|
+
let result = '';
|
|
71
|
+
let currentPos = 0;
|
|
72
|
+
let inAltScreen = this._inAlternateScreen;
|
|
73
|
+
for (const seg of segments) {
|
|
74
|
+
// 处理序列之前的内容(如果有)
|
|
75
|
+
if (seg.index > currentPos) {
|
|
76
|
+
const content = data.slice(currentPos, seg.index);
|
|
77
|
+
if (!inAltScreen) {
|
|
78
|
+
result += content;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// 更新状态
|
|
82
|
+
if (seg.type === 'enter') {
|
|
83
|
+
inAltScreen = true;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
inAltScreen = false;
|
|
87
|
+
}
|
|
88
|
+
currentPos = seg.index + seg.length;
|
|
89
|
+
}
|
|
90
|
+
// 处理最后一个序列之后的内容
|
|
91
|
+
if (currentPos < data.length) {
|
|
92
|
+
const remaining = data.slice(currentPos);
|
|
93
|
+
// 检查是否有未完成的序列
|
|
94
|
+
const hasIncomplete = this.savePendingIncomplete(remaining);
|
|
95
|
+
if (inAltScreen && !hasIncomplete) {
|
|
96
|
+
// 在 alternate screen 中,且剩余部分是完整内容
|
|
97
|
+
// 不返回任何内容
|
|
98
|
+
}
|
|
99
|
+
else if (!inAltScreen) {
|
|
100
|
+
// 不在 alternate screen 中
|
|
101
|
+
if (hasIncomplete) {
|
|
102
|
+
// 有未完成的序列,只返回不完整的序列之前的内容
|
|
103
|
+
const escIndex = remaining.lastIndexOf(ESC);
|
|
104
|
+
if (escIndex > 0) {
|
|
105
|
+
result += remaining.slice(0, escIndex);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
result += remaining;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// 更新最终状态
|
|
114
|
+
this._inAlternateScreen = inAltScreen;
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 保存可能不完整的序列
|
|
119
|
+
* @returns 是否保存了未完成的序列
|
|
120
|
+
*/
|
|
121
|
+
savePendingIncomplete(data) {
|
|
122
|
+
// 查找最后一个 ESC 字符的位置
|
|
123
|
+
const lastEscIndex = data.lastIndexOf(ESC);
|
|
124
|
+
if (lastEscIndex !== -1) {
|
|
125
|
+
const afterEsc = data.slice(lastEscIndex);
|
|
126
|
+
// 如果 ESC 后面内容太短(完整的序列至少 7 字符),可能是不完整的序列
|
|
127
|
+
if (afterEsc.length < 7) {
|
|
128
|
+
// 检查是否已经是完整序列
|
|
129
|
+
const isCompleteEnter = ALT_SCREEN_ENTER_REGEX.test(afterEsc);
|
|
130
|
+
const isCompleteExit = ALT_SCREEN_EXIT_REGEX.test(afterEsc);
|
|
131
|
+
if (!isCompleteEnter && !isCompleteExit) {
|
|
132
|
+
this.pending = afterEsc;
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* 重置状态
|
|
141
|
+
*/
|
|
142
|
+
reset() {
|
|
143
|
+
this._inAlternateScreen = false;
|
|
144
|
+
this.pending = '';
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=ansi-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ansi-filter.js","sourceRoot":"","sources":["../../src/utils/ansi-filter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface FileLockOptions {
|
|
2
|
+
/** 最大重试次数 (默认 50) */
|
|
3
|
+
retries?: number;
|
|
4
|
+
/** 重试间隔 ms (默认 50) */
|
|
5
|
+
retryIntervalMs?: number;
|
|
6
|
+
/** 超过此 ms 视为僵尸锁,强制删除 (默认 10000) */
|
|
7
|
+
staleMs?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* 同步文件锁。在 fn 执行期间持有 lockPath 目录锁。
|
|
11
|
+
* fn 无论正常返回还是抛异常,锁都会被释放。
|
|
12
|
+
*
|
|
13
|
+
* 注意:锁竞争时使用 Atomics.wait 阻塞事件循环。
|
|
14
|
+
* 仅适合启动/关闭阶段使用,请求路径上应使用 withFileLockAsync。
|
|
15
|
+
*/
|
|
16
|
+
export declare function withFileLock<T>(lockPath: string, fn: () => T, options?: FileLockOptions): T;
|
|
17
|
+
/**
|
|
18
|
+
* 异步文件锁。在 fn 执行期间持有 lockPath 目录锁。
|
|
19
|
+
* fn 无论 resolve 还是 reject,锁都会被释放。
|
|
20
|
+
* 使用非阻塞 setTimeout 重试,不阻塞事件循环。
|
|
21
|
+
*/
|
|
22
|
+
export declare function withFileLockAsync<T>(lockPath: string, fn: () => Promise<T>, options?: FileLockOptions): Promise<T>;
|
|
23
|
+
//# sourceMappingURL=file-lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-lock.d.ts","sourceRoot":"","sources":["../../src/utils/file-lock.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,eAAe;IAC9B,qBAAqB;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sBAAsB;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA8DD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,CAAC,CAqB3F;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,CAAC,CAAC,CAqBZ"}
|