@walkhi/code-relax 0.1.0-beta.8 → 0.1.0-beta.9
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 +10 -11
- package/dist/bin/codex-remote.mjs +11 -12
- package/dist/bin/self-relay-demo.mjs +1 -1
- package/dist/shared/app-server-events.cjs +5 -3
- package/dist/src/desktop-launcher.mjs +7 -10
- package/dist/src/message-images.mjs +0 -18
- package/dist/src/onboarding.mjs +3 -4
- package/dist/src/platform/README.md +1 -1
- package/dist/src/platform/windows/desktop-shortcut-run.ps1 +1 -1
- package/dist/src/platform/windows/desktop-shortcuts.ps1 +1 -1
- package/dist/src/platform/windows/service-host.mjs +4 -7
- package/dist/src/self-relay/admin-page.mjs +54 -0
- package/dist/src/self-relay/admin-state.mjs +25 -12
- package/dist/src/self-relay/demo.mjs +2 -3
- package/dist/src/self-relay/lifecycle.mjs +6 -6
- package/dist/src/self-relay/server.mjs +7 -11
- package/dist/src/server.mjs +147 -1030
- package/dist/src/service-doctor.mjs +3 -3
- package/dist/src/service-lifecycle.mjs +2 -7
- package/dist/src/shared-app-server.mjs +13 -11
- package/dist/src/shared-recovery.mjs +5 -5
- package/dist/src/shared-thread-stream.mjs +1 -1
- package/dist/web/capabilities.js +1 -8
- package/dist/web/chat-transport.js +6 -12
- package/dist/web/chat.css +13 -14
- package/dist/web/chat.js +23 -41
- package/dist/web/community.css +15 -10
- package/dist/web/community.html +4 -5
- package/dist/web/composer-controller.js +0 -4
- package/dist/web/index.html +7 -39
- package/dist/web/resources.json +1 -1
- package/dist/web/task-list-view.js +1 -8
- package/dist/web/timeline-reducer.js +0 -4
- package/package.json +16 -18
- package/dist/src/app-server-client.mjs +0 -468
- package/dist/src/app-server-tasks.mjs +0 -358
- package/dist/src/platform/windows/desktop-monitor.mjs +0 -34
- package/dist/src/platform/windows/desktop-tools.mjs +0 -48
- package/dist/src/thread-catalog.mjs +0 -47
- package/dist/tools/find-desktop-pipe.mjs +0 -10
- package/dist/web/community-view.js +0 -20
|
@@ -1,468 +0,0 @@
|
|
|
1
|
-
import { execFileSync, spawn } from 'node:child_process';
|
|
2
|
-
import { EventEmitter } from 'node:events';
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import readline from 'node:readline';
|
|
6
|
-
|
|
7
|
-
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
8
|
-
const DEFAULT_CLOSE_TIMEOUT_MS = 3_000;
|
|
9
|
-
const DEFAULT_STDERR_LIMIT = 16_000;
|
|
10
|
-
const DEFAULT_NOTIFICATION_LIMIT = 1_000;
|
|
11
|
-
export const APP_SERVER_SCHEMA_VERSION = '0.154.0';
|
|
12
|
-
|
|
13
|
-
export const APP_SERVER_REQUIRED_METHODS = Object.freeze([
|
|
14
|
-
'initialize',
|
|
15
|
-
'model/list',
|
|
16
|
-
'thread/start',
|
|
17
|
-
'thread/list',
|
|
18
|
-
'thread/read',
|
|
19
|
-
'thread/name/set',
|
|
20
|
-
'thread/delete',
|
|
21
|
-
'thread/turns/list',
|
|
22
|
-
'thread/items/list',
|
|
23
|
-
'turn/start',
|
|
24
|
-
'turn/steer',
|
|
25
|
-
'turn/interrupt',
|
|
26
|
-
'account/rateLimits/read',
|
|
27
|
-
]);
|
|
28
|
-
|
|
29
|
-
export function locateCodexCliJs(environment = process.env) {
|
|
30
|
-
if (environment.CODEX_CLI_JS) {
|
|
31
|
-
const explicit = path.resolve(environment.CODEX_CLI_JS);
|
|
32
|
-
if (!fs.existsSync(explicit)) throw new Error(`CODEX_CLI_JS 指向的文件不存在:${explicit}`);
|
|
33
|
-
return explicit;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const candidates = [];
|
|
37
|
-
if (environment.APPDATA) {
|
|
38
|
-
candidates.push(path.join(environment.APPDATA, 'npm', 'node_modules', '@openai', 'codex', 'bin', 'codex.js'));
|
|
39
|
-
}
|
|
40
|
-
if (process.platform === 'win32') {
|
|
41
|
-
try {
|
|
42
|
-
const launchers = execFileSync('where.exe', ['codex.cmd'], { encoding: 'utf8' })
|
|
43
|
-
.split(/\r?\n/)
|
|
44
|
-
.map(value => value.trim())
|
|
45
|
-
.filter(Boolean);
|
|
46
|
-
for (const launcher of launchers) {
|
|
47
|
-
candidates.push(path.join(path.dirname(launcher), 'node_modules', '@openai', 'codex', 'bin', 'codex.js'));
|
|
48
|
-
}
|
|
49
|
-
} catch {
|
|
50
|
-
// The explicit error below includes the supported override.
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const installed = [...new Set(candidates.map(value => path.resolve(value)))]
|
|
55
|
-
.filter(value => fs.existsSync(value))
|
|
56
|
-
.map(value => ({ path: value, version: parseCodexCliVersion(readCodexCliVersion(value)) }));
|
|
57
|
-
installed.sort((left, right) => compareVersionTuple(right.version, left.version));
|
|
58
|
-
if (!installed.length) {
|
|
59
|
-
throw new Error('找不到 Codex CLI。可通过 CODEX_CLI_JS 指定 @openai/codex/bin/codex.js。');
|
|
60
|
-
}
|
|
61
|
-
return installed[0].path;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function readCodexCliVersion(cliJs, nodePath = process.execPath) {
|
|
65
|
-
if (!cliJs) return '';
|
|
66
|
-
try {
|
|
67
|
-
return execFileSync(nodePath, [cliJs, '--version'], {
|
|
68
|
-
encoding: 'utf8',
|
|
69
|
-
windowsHide: true,
|
|
70
|
-
timeout: 10_000,
|
|
71
|
-
}).trim();
|
|
72
|
-
} catch {
|
|
73
|
-
return '';
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function parseCodexCliVersion(value) {
|
|
78
|
-
const match = String(value || '').match(/(\d+)\.(\d+)\.(\d+)/);
|
|
79
|
-
return match ? match.slice(1).map(Number) : [0, 0, 0];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function compareVersionTuple(left, right) {
|
|
83
|
-
for (let index = 0; index < 3; index += 1) {
|
|
84
|
-
if (left[index] !== right[index]) return left[index] - right[index];
|
|
85
|
-
}
|
|
86
|
-
return 0;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export class AppServerRpcError extends Error {
|
|
90
|
-
constructor(method, rpcError) {
|
|
91
|
-
super(`${method} 失败:${rpcError?.message || JSON.stringify(rpcError)}`);
|
|
92
|
-
this.name = 'AppServerRpcError';
|
|
93
|
-
this.method = method;
|
|
94
|
-
this.code = rpcError?.code;
|
|
95
|
-
this.data = rpcError?.data;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export class AppServerClient extends EventEmitter {
|
|
100
|
-
constructor({
|
|
101
|
-
cliJs,
|
|
102
|
-
cwd = process.cwd(),
|
|
103
|
-
environment = process.env,
|
|
104
|
-
nodePath = process.execPath,
|
|
105
|
-
command,
|
|
106
|
-
args,
|
|
107
|
-
requestTimeoutMs = DEFAULT_TIMEOUT_MS,
|
|
108
|
-
closeTimeoutMs = DEFAULT_CLOSE_TIMEOUT_MS,
|
|
109
|
-
stderrLimit = DEFAULT_STDERR_LIMIT,
|
|
110
|
-
notificationLimit = DEFAULT_NOTIFICATION_LIMIT,
|
|
111
|
-
initializeParams,
|
|
112
|
-
} = {}) {
|
|
113
|
-
super();
|
|
114
|
-
this.cliJs = cliJs || null;
|
|
115
|
-
this.cwd = path.resolve(cwd);
|
|
116
|
-
this.environment = environment;
|
|
117
|
-
this.nodePath = nodePath;
|
|
118
|
-
this.command = command || nodePath;
|
|
119
|
-
this.args = args || null;
|
|
120
|
-
this.requestTimeoutMs = requestTimeoutMs;
|
|
121
|
-
this.closeTimeoutMs = closeTimeoutMs;
|
|
122
|
-
this.stderrLimit = stderrLimit;
|
|
123
|
-
this.notificationLimit = notificationLimit;
|
|
124
|
-
this.initializeParams = initializeParams || {
|
|
125
|
-
clientInfo: {
|
|
126
|
-
name: 'codex_remote_bridge',
|
|
127
|
-
title: 'Code Relax Bridge',
|
|
128
|
-
version: '0.1.0',
|
|
129
|
-
},
|
|
130
|
-
capabilities: { experimentalApi: true },
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
this.child = null;
|
|
134
|
-
this.lines = null;
|
|
135
|
-
this.state = 'idle';
|
|
136
|
-
this.nextId = 1;
|
|
137
|
-
this.pending = new Map();
|
|
138
|
-
this.serverRequests = new Map();
|
|
139
|
-
this.notifications = [];
|
|
140
|
-
this.notificationSequence = 0;
|
|
141
|
-
this.notificationWaiters = new Set();
|
|
142
|
-
this.stderr = '';
|
|
143
|
-
this.initializeResult = null;
|
|
144
|
-
this.cliVersion = '';
|
|
145
|
-
this.startedAt = null;
|
|
146
|
-
this.lastExit = null;
|
|
147
|
-
this.startPromise = null;
|
|
148
|
-
this.closePromise = null;
|
|
149
|
-
this.closing = false;
|
|
150
|
-
this.generation = 0;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
snapshot() {
|
|
154
|
-
return {
|
|
155
|
-
state: this.state,
|
|
156
|
-
pid: this.child?.pid || null,
|
|
157
|
-
cliJs: this.cliJs,
|
|
158
|
-
cliVersion: this.cliVersion,
|
|
159
|
-
startedAt: this.startedAt,
|
|
160
|
-
lastExit: this.lastExit,
|
|
161
|
-
pendingRequestCount: this.pending.size,
|
|
162
|
-
pendingServerRequestCount: this.serverRequests.size,
|
|
163
|
-
schemaVersion: APP_SERVER_SCHEMA_VERSION,
|
|
164
|
-
requiredMethods: APP_SERVER_REQUIRED_METHODS,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
async start() {
|
|
169
|
-
if (this.state === 'ready') return this.snapshot();
|
|
170
|
-
if (this.startPromise) return this.startPromise;
|
|
171
|
-
if (this.state === 'closing') throw new Error('app-server 正在关闭。');
|
|
172
|
-
|
|
173
|
-
this.startPromise = this.startSession();
|
|
174
|
-
try {
|
|
175
|
-
return await this.startPromise;
|
|
176
|
-
} finally {
|
|
177
|
-
this.startPromise = null;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
async startSession() {
|
|
182
|
-
this.closing = false;
|
|
183
|
-
this.stderr = '';
|
|
184
|
-
this.initializeResult = null;
|
|
185
|
-
this.lastExit = null;
|
|
186
|
-
this.generation += 1;
|
|
187
|
-
this.setState('starting');
|
|
188
|
-
try {
|
|
189
|
-
if (!this.args) this.cliJs ||= locateCodexCliJs(this.environment);
|
|
190
|
-
this.cliVersion = readCodexCliVersion(this.cliJs, this.nodePath);
|
|
191
|
-
if (!this.args && !this.cliVersion.includes(APP_SERVER_SCHEMA_VERSION)) {
|
|
192
|
-
throw new Error(`当前迁移层只验证了 codex-cli ${APP_SERVER_SCHEMA_VERSION},实际为 ${this.cliVersion || '未知版本'}。`);
|
|
193
|
-
}
|
|
194
|
-
} catch (error) {
|
|
195
|
-
this.setState('failed');
|
|
196
|
-
throw error;
|
|
197
|
-
}
|
|
198
|
-
const args = this.args || [this.cliJs, 'app-server', '--listen', 'stdio://'];
|
|
199
|
-
|
|
200
|
-
const child = spawn(this.command, args, {
|
|
201
|
-
cwd: this.cwd,
|
|
202
|
-
env: this.environment,
|
|
203
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
204
|
-
windowsHide: true,
|
|
205
|
-
});
|
|
206
|
-
this.child = child;
|
|
207
|
-
const generation = this.generation;
|
|
208
|
-
child.stderr.on('data', chunk => {
|
|
209
|
-
this.stderr = `${this.stderr}${chunk}`.slice(-this.stderrLimit);
|
|
210
|
-
});
|
|
211
|
-
child.once('error', error => this.handleProcessFailure(error, generation));
|
|
212
|
-
child.once('exit', (code, signal) => this.handleProcessExit(code, signal, generation));
|
|
213
|
-
this.lines = readline.createInterface({ input: child.stdout, crlfDelay: Infinity });
|
|
214
|
-
this.lines.on('line', line => this.handleLine(line, generation));
|
|
215
|
-
|
|
216
|
-
try {
|
|
217
|
-
this.initializeResult = await this.sendRequest('initialize', this.initializeParams, this.requestTimeoutMs);
|
|
218
|
-
this.send({ method: 'initialized', params: {} });
|
|
219
|
-
this.startedAt = new Date().toISOString();
|
|
220
|
-
this.setState('ready');
|
|
221
|
-
return this.snapshot();
|
|
222
|
-
} catch (error) {
|
|
223
|
-
if (generation === this.generation && !this.closing) {
|
|
224
|
-
this.setState('failed');
|
|
225
|
-
if (child.exitCode === null) child.kill();
|
|
226
|
-
}
|
|
227
|
-
throw error;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
async request(method, params = {}, timeoutMs = this.requestTimeoutMs) {
|
|
232
|
-
if (this.state !== 'ready') await this.start();
|
|
233
|
-
return this.sendRequest(method, params, timeoutMs);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
notify(method, params = {}) {
|
|
237
|
-
if (this.state !== 'ready' && method !== 'initialized') {
|
|
238
|
-
throw new Error('app-server 尚未就绪。');
|
|
239
|
-
}
|
|
240
|
-
this.send({ method, params });
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
waitForNotification(method, predicate = () => true, {
|
|
244
|
-
afterSequence = 0,
|
|
245
|
-
timeoutMs = this.requestTimeoutMs,
|
|
246
|
-
} = {}) {
|
|
247
|
-
const existing = this.notifications.find(record => record.sequence > afterSequence
|
|
248
|
-
&& record.method === method && predicate(record.params));
|
|
249
|
-
if (existing) return Promise.resolve(existing.params);
|
|
250
|
-
if (!['starting', 'ready'].includes(this.state)) {
|
|
251
|
-
return Promise.reject(new Error(`app-server 当前状态为 ${this.state},无法等待通知。`));
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return new Promise((resolve, reject) => {
|
|
255
|
-
const waiter = { method, predicate, afterSequence, resolve, reject, timer: null };
|
|
256
|
-
waiter.timer = setTimeout(() => {
|
|
257
|
-
this.notificationWaiters.delete(waiter);
|
|
258
|
-
reject(new Error(`等待 ${method} 超时(${timeoutMs} ms)。${this.stderrSuffix()}`));
|
|
259
|
-
}, timeoutMs);
|
|
260
|
-
waiter.timer.unref?.();
|
|
261
|
-
this.notificationWaiters.add(waiter);
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
listServerRequests() {
|
|
266
|
-
return [...this.serverRequests.values()].map(request => ({
|
|
267
|
-
id: request.id,
|
|
268
|
-
method: request.method,
|
|
269
|
-
params: request.params,
|
|
270
|
-
receivedAt: request.receivedAt,
|
|
271
|
-
generation: request.generation,
|
|
272
|
-
}));
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
respondServerRequest(id, result) {
|
|
276
|
-
const request = this.takeServerRequest(id);
|
|
277
|
-
this.send({ id: request.id, result });
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
rejectServerRequest(id, code, message, data) {
|
|
281
|
-
const request = this.takeServerRequest(id);
|
|
282
|
-
const error = { code, message };
|
|
283
|
-
if (data !== undefined) error.data = data;
|
|
284
|
-
this.send({ id: request.id, error });
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
takeServerRequest(id) {
|
|
288
|
-
const request = this.serverRequests.get(id);
|
|
289
|
-
if (!request || request.generation !== this.generation) {
|
|
290
|
-
throw new Error('app-server 反向请求不存在或已经失效。');
|
|
291
|
-
}
|
|
292
|
-
this.serverRequests.delete(id);
|
|
293
|
-
return request;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
async close() {
|
|
297
|
-
if (this.closePromise) return this.closePromise;
|
|
298
|
-
if (!this.child || ['idle', 'closed'].includes(this.state)) {
|
|
299
|
-
this.setState('closed');
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
this.closePromise = this.closeSession();
|
|
304
|
-
try {
|
|
305
|
-
await this.closePromise;
|
|
306
|
-
} finally {
|
|
307
|
-
this.closePromise = null;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
async closeSession() {
|
|
312
|
-
this.closing = true;
|
|
313
|
-
this.setState('closing');
|
|
314
|
-
const child = this.child;
|
|
315
|
-
this.rejectAll(new Error('app-server 连接正在关闭。'));
|
|
316
|
-
this.invalidateServerRequests('client-closed');
|
|
317
|
-
child.stdin.end();
|
|
318
|
-
if (child.exitCode === null) {
|
|
319
|
-
await Promise.race([
|
|
320
|
-
new Promise(resolve => child.once('exit', resolve)),
|
|
321
|
-
new Promise(resolve => {
|
|
322
|
-
const timer = setTimeout(resolve, this.closeTimeoutMs);
|
|
323
|
-
timer.unref?.();
|
|
324
|
-
}),
|
|
325
|
-
]);
|
|
326
|
-
}
|
|
327
|
-
if (child.exitCode === null) child.kill();
|
|
328
|
-
if (this.child === child) this.child = null;
|
|
329
|
-
this.lines?.close();
|
|
330
|
-
this.lines = null;
|
|
331
|
-
this.setState('closed');
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
sendRequest(method, params, timeoutMs) {
|
|
335
|
-
const id = this.nextId++;
|
|
336
|
-
return new Promise((resolve, reject) => {
|
|
337
|
-
const timer = setTimeout(() => {
|
|
338
|
-
this.pending.delete(id);
|
|
339
|
-
reject(new Error(`${method} 超时(${timeoutMs} ms)。${this.stderrSuffix()}`));
|
|
340
|
-
}, timeoutMs);
|
|
341
|
-
timer.unref?.();
|
|
342
|
-
this.pending.set(id, { method, resolve, reject, timer, generation: this.generation });
|
|
343
|
-
try {
|
|
344
|
-
this.send({ method, id, params });
|
|
345
|
-
} catch (error) {
|
|
346
|
-
clearTimeout(timer);
|
|
347
|
-
this.pending.delete(id);
|
|
348
|
-
reject(error);
|
|
349
|
-
}
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
send(message) {
|
|
354
|
-
if (!this.child || this.child.stdin.destroyed || !this.child.stdin.writable) {
|
|
355
|
-
throw new Error('app-server 输入通道不可用。');
|
|
356
|
-
}
|
|
357
|
-
this.child.stdin.write(`${JSON.stringify(message)}\n`);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
handleLine(line, generation) {
|
|
361
|
-
if (generation !== this.generation) return;
|
|
362
|
-
let message;
|
|
363
|
-
try {
|
|
364
|
-
message = JSON.parse(line);
|
|
365
|
-
} catch (error) {
|
|
366
|
-
this.emit('protocol-error', new Error('app-server 返回了无法解析的 JSON。', { cause: error }));
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
if (Object.hasOwn(message, 'id') && !message.method) {
|
|
371
|
-
const pending = this.pending.get(message.id);
|
|
372
|
-
if (!pending || pending.generation !== generation) return;
|
|
373
|
-
clearTimeout(pending.timer);
|
|
374
|
-
this.pending.delete(message.id);
|
|
375
|
-
if (message.error) pending.reject(new AppServerRpcError(pending.method, message.error));
|
|
376
|
-
else pending.resolve(message.result);
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
if (message.method && Object.hasOwn(message, 'id')) {
|
|
381
|
-
const request = {
|
|
382
|
-
id: message.id,
|
|
383
|
-
method: message.method,
|
|
384
|
-
params: message.params || {},
|
|
385
|
-
receivedAt: new Date().toISOString(),
|
|
386
|
-
generation,
|
|
387
|
-
};
|
|
388
|
-
this.serverRequests.set(message.id, request);
|
|
389
|
-
this.emit('server-request', request);
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
if (!message.method) return;
|
|
394
|
-
const record = {
|
|
395
|
-
sequence: ++this.notificationSequence,
|
|
396
|
-
method: message.method,
|
|
397
|
-
params: message.params || {},
|
|
398
|
-
};
|
|
399
|
-
this.notifications.push(record);
|
|
400
|
-
if (this.notifications.length > this.notificationLimit) this.notifications.shift();
|
|
401
|
-
this.emit('notification', record);
|
|
402
|
-
this.emit(`notification:${message.method}`, record.params);
|
|
403
|
-
for (const waiter of [...this.notificationWaiters]) {
|
|
404
|
-
if (record.sequence <= waiter.afterSequence || waiter.method !== record.method) continue;
|
|
405
|
-
let matches = false;
|
|
406
|
-
try { matches = waiter.predicate(record.params); } catch (error) {
|
|
407
|
-
clearTimeout(waiter.timer);
|
|
408
|
-
this.notificationWaiters.delete(waiter);
|
|
409
|
-
waiter.reject(error);
|
|
410
|
-
continue;
|
|
411
|
-
}
|
|
412
|
-
if (!matches) continue;
|
|
413
|
-
clearTimeout(waiter.timer);
|
|
414
|
-
this.notificationWaiters.delete(waiter);
|
|
415
|
-
waiter.resolve(record.params);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
handleProcessFailure(error, generation) {
|
|
420
|
-
if (generation !== this.generation) return;
|
|
421
|
-
this.rejectAll(error);
|
|
422
|
-
if (!this.closing) this.setState('failed');
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
handleProcessExit(code, signal, generation) {
|
|
426
|
-
if (generation !== this.generation) return;
|
|
427
|
-
const wasClosing = this.closing;
|
|
428
|
-
this.lastExit = { code, signal: signal || null, at: new Date().toISOString() };
|
|
429
|
-
this.child = null;
|
|
430
|
-
this.lines?.close();
|
|
431
|
-
this.lines = null;
|
|
432
|
-
const error = new Error(`app-server 已退出:code=${code}, signal=${signal || 'none'}。${this.stderrSuffix()}`);
|
|
433
|
-
this.rejectAll(error);
|
|
434
|
-
this.invalidateServerRequests('process-exited');
|
|
435
|
-
this.setState(wasClosing ? 'closed' : 'failed');
|
|
436
|
-
this.emit('exit', this.lastExit);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
rejectAll(error) {
|
|
440
|
-
for (const pending of this.pending.values()) {
|
|
441
|
-
clearTimeout(pending.timer);
|
|
442
|
-
pending.reject(error);
|
|
443
|
-
}
|
|
444
|
-
this.pending.clear();
|
|
445
|
-
for (const waiter of this.notificationWaiters) {
|
|
446
|
-
clearTimeout(waiter.timer);
|
|
447
|
-
waiter.reject(error);
|
|
448
|
-
}
|
|
449
|
-
this.notificationWaiters.clear();
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
invalidateServerRequests(reason) {
|
|
453
|
-
if (!this.serverRequests.size) return;
|
|
454
|
-
const requests = this.listServerRequests();
|
|
455
|
-
this.serverRequests.clear();
|
|
456
|
-
this.emit('server-requests-invalidated', { reason, requests });
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
stderrSuffix() {
|
|
460
|
-
return this.stderr ? `\n${this.stderr}` : '';
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
setState(state) {
|
|
464
|
-
if (this.state === state) return;
|
|
465
|
-
this.state = state;
|
|
466
|
-
this.emit('state', this.snapshot());
|
|
467
|
-
}
|
|
468
|
-
}
|