@walkhi/code-relax 0.1.0-beta.50 → 0.1.0-beta.59
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 +105 -105
- package/dist/bin/cleanup-accounts.mjs +63 -63
- package/dist/bin/codex-remote.mjs +403 -403
- package/dist/bin/publish-android-release.mjs +63 -63
- package/dist/bin/release-upload-server.mjs +190 -190
- package/dist/bin/set-relay-admin-password.mjs +11 -0
- package/dist/build-platform.json +1 -1
- package/dist/get-shared-service-url.ps1 +14 -14
- package/dist/shared/code-browser.cjs +159 -159
- package/dist/shared/file-changes.cjs +31 -31
- package/dist/shared/file-citations.cjs +117 -117
- package/dist/shared/file-mentions.cjs +92 -92
- package/dist/shared/message-delivery.cjs +115 -115
- package/dist/shared/p2p-file-channel.cjs +87 -87
- package/dist/shared/release-version.cjs +31 -0
- package/dist/shared/server-requests.cjs +311 -311
- package/dist/shared/timeline-sync.cjs +173 -173
- package/dist/skills/code-relax/SKILL.md +22 -22
- package/dist/src/bounded-process-log.mjs +63 -63
- package/dist/src/celsius-client.mjs +107 -107
- package/dist/src/dots-approval-timeline.mjs +99 -99
- package/dist/src/dots-avatar.mjs +42 -42
- package/dist/src/dots-chat-projection.mjs +50 -50
- package/dist/src/dots-chat-server.mjs +483 -483
- package/dist/src/dots-message-images.mjs +319 -319
- package/dist/src/dots-profile.mjs +77 -77
- package/dist/src/dots-room-activity.mjs +172 -172
- package/dist/src/dots-room-client.mjs +137 -135
- package/dist/src/dots-room-projection.mjs +94 -94
- package/dist/src/dots-schedules.mjs +66 -66
- package/dist/src/dots-summary-tasks.mjs +32 -32
- package/dist/src/dots-task-catalog.mjs +430 -430
- package/dist/src/file-transfer-range.mjs +22 -22
- package/dist/src/file-upload-session.mjs +146 -130
- package/dist/src/hosted-history-app-server.mjs +103 -103
- package/dist/src/hosted-history-cache.mjs +113 -113
- package/dist/src/lan-socket-server.mjs +338 -266
- package/dist/src/managed-app-server.mjs +373 -373
- package/dist/src/managed-endpoint.mjs +25 -25
- package/dist/src/message-upload-files.mjs +109 -109
- package/dist/src/onboarding.mjs +12 -12
- package/dist/src/platform/README.md +1 -1
- package/dist/src/platform/codex-protocol.mjs +46 -46
- package/dist/src/platform/desktop-project-store.mjs +219 -208
- package/dist/src/platform/linux/desktop-host.mjs +87 -87
- package/dist/src/platform/linux/process.mjs +53 -53
- package/dist/src/platform/linux/system.mjs +50 -50
- package/dist/src/platform/linux/user-environment.mjs +9 -9
- package/dist/src/platform/macos/codex-runtime.mjs +125 -125
- package/dist/src/platform/macos/desktop-host.mjs +86 -86
- package/dist/src/platform/macos/user-environment.mjs +9 -9
- package/dist/src/platform/native-package.mjs +58 -58
- package/dist/src/platform/windows/activate-desktop.ps1 +49 -49
- package/dist/src/platform/windows/codex-runtime.mjs +215 -215
- package/dist/src/platform/windows/credential-store.mjs +27 -27
- package/dist/src/platform/windows/credential-store.ps1 +49 -49
- package/dist/src/platform/windows/desktop-host.mjs +65 -65
- package/dist/src/platform/windows/desktop-info.ps1 +38 -38
- package/dist/src/platform/windows/desktop-project-registration.mjs +4 -4
- package/dist/src/platform/windows/desktop-shortcuts.ps1 +14 -14
- package/dist/src/platform/windows/dots-profile.mjs +49 -49
- package/dist/src/platform/windows/launch-worker.mjs +41 -41
- package/dist/src/platform/windows/project-locations.mjs +16 -16
- package/dist/src/platform/windows/service-host.mjs +35 -35
- package/dist/src/platform/windows/user-environment.mjs +52 -52
- package/dist/src/project-management.mjs +58 -58
- package/dist/src/runtime-discovery.mjs +47 -47
- package/dist/src/self-relay/account-cleanup.mjs +30 -30
- package/dist/src/self-relay/admin-auth.mjs +59 -0
- package/dist/src/self-relay/admin-login-page.mjs +7 -0
- package/dist/src/self-relay/admin-page.mjs +87 -80
- package/dist/src/self-relay/admin-server.mjs +49 -18
- package/dist/src/self-relay/admin-style.mjs +58 -14
- package/dist/src/self-relay/admin-workstations-page.mjs +95 -0
- package/dist/src/self-relay/admin-workstations.mjs +143 -0
- package/dist/src/self-relay/connection-reporting.mjs +38 -0
- package/dist/src/self-relay/connector.mjs +444 -439
- package/dist/src/self-relay/demo.mjs +31 -31
- package/dist/src/self-relay/diagnostics.mjs +54 -54
- package/dist/src/self-relay/email-accounts.mjs +183 -183
- package/dist/src/self-relay/file-wire.mjs +13 -13
- package/dist/src/self-relay/lifecycle.mjs +11 -0
- package/dist/src/self-relay/p2p-probe.mjs +168 -168
- package/dist/src/self-relay/phone-connections.mjs +55 -0
- package/dist/src/self-relay/privacy-page.mjs +26 -26
- package/dist/src/self-relay/public-releases.mjs +67 -67
- package/dist/src/self-relay/rtc-file-bridge.mjs +46 -41
- package/dist/src/self-relay/server.mjs +63 -3
- package/dist/src/self-relay/usage-meter.mjs +141 -141
- package/dist/src/server.mjs +23 -9
- package/dist/src/service-doctor.mjs +148 -148
- package/dist/src/service-install.mjs +97 -97
- package/dist/src/service-lifecycle.mjs +139 -139
- package/dist/src/service-update.mjs +6 -3
- package/dist/src/shared-app-server.mjs +683 -666
- package/dist/src/shared-catalog.mjs +132 -132
- package/dist/src/shared-thread-stream.mjs +165 -165
- package/dist/src/usage-reset.mjs +17 -17
- package/dist/src/workstation-downloads.mjs +120 -120
- package/package.json +4 -4
- package/tools/postinstall.mjs +186 -186
|
@@ -1,139 +1,139 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { startBackground, processAlive } from './background-process.mjs';
|
|
4
|
-
import { randomBytes, randomUUID } from 'node:crypto';
|
|
5
|
-
import { setTimeout as delay } from 'node:timers/promises';
|
|
6
|
-
import { fileURLToPath } from 'node:url';
|
|
7
|
-
import { controlRequest } from './service-control.mjs';
|
|
8
|
-
import { serviceHealth as health } from './service-health.mjs';
|
|
9
|
-
|
|
10
|
-
export function readRecord(file) {
|
|
11
|
-
try { return JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, '')); }
|
|
12
|
-
catch (error) { if (error.code === 'ENOENT') return null; throw error; }
|
|
13
|
-
}
|
|
14
|
-
export function writeRecord(file, value) {
|
|
15
|
-
const temp = `${file}.${randomUUID()}.tmp`;
|
|
16
|
-
try {
|
|
17
|
-
fs.writeFileSync(temp, JSON.stringify(value, null, 2), { mode: 0o600 });
|
|
18
|
-
fs.renameSync(temp, file);
|
|
19
|
-
} finally { fs.rmSync(temp, { force: true }); }
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function lifecycle({ stateRoot, host }) {
|
|
23
|
-
fs.mkdirSync(stateRoot, { recursive: true });
|
|
24
|
-
const sessionPath = path.join(stateRoot, 'bridge-session.json');
|
|
25
|
-
const configPath = path.join(stateRoot, 'bridge-config.json');
|
|
26
|
-
const scriptPath = fileURLToPath(new URL('./server.mjs', import.meta.url));
|
|
27
|
-
|
|
28
|
-
async function alive(session) {
|
|
29
|
-
if (!session?.controlPort || !session.instanceId || !session.controlSecret) {
|
|
30
|
-
if (await health(session)) throw new Error('当前为旧版桌面服务,请先使用旧版停止入口结束服务,再启动新版。');
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
try { await controlRequest(session); return true; }
|
|
34
|
-
catch (error) { if (error.code === 'ECONNREFUSED') return false; throw error; }
|
|
35
|
-
}
|
|
36
|
-
async function stop() {
|
|
37
|
-
const session = readRecord(sessionPath);
|
|
38
|
-
if (!await alive(session)) return { stopped: true, message: '桌面服务已停止。' };
|
|
39
|
-
await controlRequest(session, 'POST');
|
|
40
|
-
const deadline = Date.now() + 10000;
|
|
41
|
-
while (true) {
|
|
42
|
-
try { if (!await alive(session)) break; }
|
|
43
|
-
catch (error) {
|
|
44
|
-
// A connection accepted just before the listener closes may be reset.
|
|
45
|
-
// Verify refusal on the next connection; never resend the shutdown request.
|
|
46
|
-
if (error.code !== 'ECONNRESET') throw error;
|
|
47
|
-
}
|
|
48
|
-
if (Date.now() > deadline) throw new Error('桌面服务未在 10 秒内退出;未强杀进程。');
|
|
49
|
-
await delay(100);
|
|
50
|
-
}
|
|
51
|
-
return { stopped: true, message: '桌面服务已正常关闭。' };
|
|
52
|
-
}
|
|
53
|
-
async function status() {
|
|
54
|
-
const session = readRecord(sessionPath);
|
|
55
|
-
const running = await alive(session);
|
|
56
|
-
const result = running ? await health(session) : null;
|
|
57
|
-
return { running: running && result?.status === 'ok', processRunning: running, pid: session?.pid,
|
|
58
|
-
port: session?.port, mode: result?.transportMode, version: result?.version, execution: result?.execution,
|
|
59
|
-
managedAppServer: result?.managedAppServer, desktop: result?.desktop,
|
|
60
|
-
keepAwake: result?.keepAwake,
|
|
61
|
-
desktopRestartRecommended: result?.desktopRestartRecommended === true,
|
|
62
|
-
resident: result?.resident === true };
|
|
63
|
-
}
|
|
64
|
-
async function pair() {
|
|
65
|
-
const session = readRecord(sessionPath);
|
|
66
|
-
const connection = await alive(session) ? await health(session) : null;
|
|
67
|
-
if (connection?.status !== 'ok' || !session.lan) {
|
|
68
|
-
throw new Error('没有健康的局域网服务,请先执行 relax start。');
|
|
69
|
-
}
|
|
70
|
-
const address = await host.lanAddress();
|
|
71
|
-
const url = new URL(session.accessUrl);
|
|
72
|
-
url.hostname = address || '127.0.0.1';
|
|
73
|
-
session.accessUrl = url.href;
|
|
74
|
-
writeRecord(sessionPath, session);
|
|
75
|
-
return { pid: session.pid, port: session.port, version: connection.version, transportMode: connection.transportMode,
|
|
76
|
-
execution: connection.execution, managedAppServer: connection.managedAppServer, desktop: connection.desktop,
|
|
77
|
-
desktopRestartRecommended: connection.desktopRestartRecommended === true, accessUrl: session.accessUrl,
|
|
78
|
-
qrPath: path.join(stateRoot, 'connection-qr.png') };
|
|
79
|
-
}
|
|
80
|
-
async function restartManagedAppServer(confirmed = false) {
|
|
81
|
-
const session = readRecord(sessionPath);
|
|
82
|
-
if (!await alive(session)) throw new Error('桌面服务尚未运行,无法迁移 Codex 内核。');
|
|
83
|
-
const action = confirmed ? 'restart-managed-app-server-confirmed' : 'restart-managed-app-server';
|
|
84
|
-
return controlRequest(session, 'POST', action);
|
|
85
|
-
}
|
|
86
|
-
async function startBridge() {
|
|
87
|
-
let session = readRecord(sessionPath);
|
|
88
|
-
if (await alive(session)) {
|
|
89
|
-
const result = await health(session);
|
|
90
|
-
if (session.scriptPath === scriptPath && session.lan && result?.status === 'ok' && result.resident) {
|
|
91
|
-
await controlRequest(session, 'POST', 'refresh-execution');
|
|
92
|
-
return pair();
|
|
93
|
-
}
|
|
94
|
-
await stop();
|
|
95
|
-
}
|
|
96
|
-
let config = readRecord(configPath);
|
|
97
|
-
if (!config && session?.lan) config = { port: session.port, token: new URL(session.accessUrl).searchParams.get('token') };
|
|
98
|
-
config ||= { port: 45831, token: randomBytes(32).toString('base64url') };
|
|
99
|
-
if (!Number.isInteger(config.port) || config.port < 1 || config.port > 65535 || !/^[A-Za-z0-9_-]{20,}$/.test(config.token)) {
|
|
100
|
-
throw new Error('bridge-config.json 中端口或 Token 无效;不会自动重置凭据。');
|
|
101
|
-
}
|
|
102
|
-
const address = await host.lanAddress() || '127.0.0.1';
|
|
103
|
-
const { executable, environment } = await host.bridgeEnvironment();
|
|
104
|
-
writeRecord(configPath, config);
|
|
105
|
-
const instanceId = randomUUID();
|
|
106
|
-
const controlSecret = randomBytes(32).toString('base64url');
|
|
107
|
-
const readyPath = path.join(stateRoot, `bridge-ready-${instanceId}.json`);
|
|
108
|
-
const pid = await startBackground(executable, [scriptPath, '--lan', '--port', String(config.port), '--token', config.token],
|
|
109
|
-
{ ...environment, CODEX_REMOTE_INSTANCE: instanceId, CODEX_REMOTE_CONTROL_SECRET: controlSecret,
|
|
110
|
-
CODEX_REMOTE_READY_PATH: readyPath }, [path.join(stateRoot, 'bridge.stdout.log'), path.join(stateRoot, 'bridge.stderr.log')]);
|
|
111
|
-
try {
|
|
112
|
-
const deadline = Date.now() + 10000;
|
|
113
|
-
let ready;
|
|
114
|
-
while (!(ready = readRecord(readyPath))) {
|
|
115
|
-
if (!processAlive(pid)) throw new Error('桌面服务启动失败,请查看 bridge.stderr.log。');
|
|
116
|
-
if (Date.now() > deadline) throw new Error('桌面服务未在 10 秒内就绪。');
|
|
117
|
-
await delay(100);
|
|
118
|
-
}
|
|
119
|
-
if (ready.instanceId !== instanceId || ready.pid !== pid) throw new Error('桌面服务启动实例不匹配。');
|
|
120
|
-
session = { ...ready, controlSecret, executablePath: executable, scriptPath, port: config.port,
|
|
121
|
-
lan: true, appServer: false, accessUrl: `http://${address}:${config.port}/?token=${config.token}`,
|
|
122
|
-
startedAt: new Date().toISOString() };
|
|
123
|
-
await controlRequest(session);
|
|
124
|
-
writeRecord(sessionPath, session);
|
|
125
|
-
return await pair();
|
|
126
|
-
} catch (error) {
|
|
127
|
-
if (processAlive(pid)) process.kill(pid);
|
|
128
|
-
throw error;
|
|
129
|
-
} finally { fs.rmSync(readyPath, { force: true }); }
|
|
130
|
-
}
|
|
131
|
-
async function start() {
|
|
132
|
-
const result = await startBridge();
|
|
133
|
-
if (fs.existsSync(path.join(stateRoot, 'self-relay-config.json'))) {
|
|
134
|
-
await controlRequest(readRecord(sessionPath), 'POST', 'relay-start');
|
|
135
|
-
}
|
|
136
|
-
return result;
|
|
137
|
-
}
|
|
138
|
-
return { start, pair, status, stop, restartManagedAppServer };
|
|
139
|
-
}
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { startBackground, processAlive } from './background-process.mjs';
|
|
4
|
+
import { randomBytes, randomUUID } from 'node:crypto';
|
|
5
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { controlRequest } from './service-control.mjs';
|
|
8
|
+
import { serviceHealth as health } from './service-health.mjs';
|
|
9
|
+
|
|
10
|
+
export function readRecord(file) {
|
|
11
|
+
try { return JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, '')); }
|
|
12
|
+
catch (error) { if (error.code === 'ENOENT') return null; throw error; }
|
|
13
|
+
}
|
|
14
|
+
export function writeRecord(file, value) {
|
|
15
|
+
const temp = `${file}.${randomUUID()}.tmp`;
|
|
16
|
+
try {
|
|
17
|
+
fs.writeFileSync(temp, JSON.stringify(value, null, 2), { mode: 0o600 });
|
|
18
|
+
fs.renameSync(temp, file);
|
|
19
|
+
} finally { fs.rmSync(temp, { force: true }); }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function lifecycle({ stateRoot, host }) {
|
|
23
|
+
fs.mkdirSync(stateRoot, { recursive: true });
|
|
24
|
+
const sessionPath = path.join(stateRoot, 'bridge-session.json');
|
|
25
|
+
const configPath = path.join(stateRoot, 'bridge-config.json');
|
|
26
|
+
const scriptPath = fileURLToPath(new URL('./server.mjs', import.meta.url));
|
|
27
|
+
|
|
28
|
+
async function alive(session) {
|
|
29
|
+
if (!session?.controlPort || !session.instanceId || !session.controlSecret) {
|
|
30
|
+
if (await health(session)) throw new Error('当前为旧版桌面服务,请先使用旧版停止入口结束服务,再启动新版。');
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
try { await controlRequest(session); return true; }
|
|
34
|
+
catch (error) { if (error.code === 'ECONNREFUSED') return false; throw error; }
|
|
35
|
+
}
|
|
36
|
+
async function stop() {
|
|
37
|
+
const session = readRecord(sessionPath);
|
|
38
|
+
if (!await alive(session)) return { stopped: true, message: '桌面服务已停止。' };
|
|
39
|
+
await controlRequest(session, 'POST');
|
|
40
|
+
const deadline = Date.now() + 10000;
|
|
41
|
+
while (true) {
|
|
42
|
+
try { if (!await alive(session)) break; }
|
|
43
|
+
catch (error) {
|
|
44
|
+
// A connection accepted just before the listener closes may be reset.
|
|
45
|
+
// Verify refusal on the next connection; never resend the shutdown request.
|
|
46
|
+
if (error.code !== 'ECONNRESET') throw error;
|
|
47
|
+
}
|
|
48
|
+
if (Date.now() > deadline) throw new Error('桌面服务未在 10 秒内退出;未强杀进程。');
|
|
49
|
+
await delay(100);
|
|
50
|
+
}
|
|
51
|
+
return { stopped: true, message: '桌面服务已正常关闭。' };
|
|
52
|
+
}
|
|
53
|
+
async function status() {
|
|
54
|
+
const session = readRecord(sessionPath);
|
|
55
|
+
const running = await alive(session);
|
|
56
|
+
const result = running ? await health(session) : null;
|
|
57
|
+
return { running: running && result?.status === 'ok', processRunning: running, pid: session?.pid,
|
|
58
|
+
port: session?.port, mode: result?.transportMode, version: result?.version, execution: result?.execution,
|
|
59
|
+
managedAppServer: result?.managedAppServer, desktop: result?.desktop,
|
|
60
|
+
keepAwake: result?.keepAwake,
|
|
61
|
+
desktopRestartRecommended: result?.desktopRestartRecommended === true,
|
|
62
|
+
resident: result?.resident === true };
|
|
63
|
+
}
|
|
64
|
+
async function pair() {
|
|
65
|
+
const session = readRecord(sessionPath);
|
|
66
|
+
const connection = await alive(session) ? await health(session) : null;
|
|
67
|
+
if (connection?.status !== 'ok' || !session.lan) {
|
|
68
|
+
throw new Error('没有健康的局域网服务,请先执行 relax start。');
|
|
69
|
+
}
|
|
70
|
+
const address = await host.lanAddress();
|
|
71
|
+
const url = new URL(session.accessUrl);
|
|
72
|
+
url.hostname = address || '127.0.0.1';
|
|
73
|
+
session.accessUrl = url.href;
|
|
74
|
+
writeRecord(sessionPath, session);
|
|
75
|
+
return { pid: session.pid, port: session.port, version: connection.version, transportMode: connection.transportMode,
|
|
76
|
+
execution: connection.execution, managedAppServer: connection.managedAppServer, desktop: connection.desktop,
|
|
77
|
+
desktopRestartRecommended: connection.desktopRestartRecommended === true, accessUrl: session.accessUrl,
|
|
78
|
+
qrPath: path.join(stateRoot, 'connection-qr.png') };
|
|
79
|
+
}
|
|
80
|
+
async function restartManagedAppServer(confirmed = false) {
|
|
81
|
+
const session = readRecord(sessionPath);
|
|
82
|
+
if (!await alive(session)) throw new Error('桌面服务尚未运行,无法迁移 Codex 内核。');
|
|
83
|
+
const action = confirmed ? 'restart-managed-app-server-confirmed' : 'restart-managed-app-server';
|
|
84
|
+
return controlRequest(session, 'POST', action);
|
|
85
|
+
}
|
|
86
|
+
async function startBridge() {
|
|
87
|
+
let session = readRecord(sessionPath);
|
|
88
|
+
if (await alive(session)) {
|
|
89
|
+
const result = await health(session);
|
|
90
|
+
if (session.scriptPath === scriptPath && session.lan && result?.status === 'ok' && result.resident) {
|
|
91
|
+
await controlRequest(session, 'POST', 'refresh-execution');
|
|
92
|
+
return pair();
|
|
93
|
+
}
|
|
94
|
+
await stop();
|
|
95
|
+
}
|
|
96
|
+
let config = readRecord(configPath);
|
|
97
|
+
if (!config && session?.lan) config = { port: session.port, token: new URL(session.accessUrl).searchParams.get('token') };
|
|
98
|
+
config ||= { port: 45831, token: randomBytes(32).toString('base64url') };
|
|
99
|
+
if (!Number.isInteger(config.port) || config.port < 1 || config.port > 65535 || !/^[A-Za-z0-9_-]{20,}$/.test(config.token)) {
|
|
100
|
+
throw new Error('bridge-config.json 中端口或 Token 无效;不会自动重置凭据。');
|
|
101
|
+
}
|
|
102
|
+
const address = await host.lanAddress() || '127.0.0.1';
|
|
103
|
+
const { executable, environment } = await host.bridgeEnvironment();
|
|
104
|
+
writeRecord(configPath, config);
|
|
105
|
+
const instanceId = randomUUID();
|
|
106
|
+
const controlSecret = randomBytes(32).toString('base64url');
|
|
107
|
+
const readyPath = path.join(stateRoot, `bridge-ready-${instanceId}.json`);
|
|
108
|
+
const pid = await startBackground(executable, [scriptPath, '--lan', '--port', String(config.port), '--token', config.token],
|
|
109
|
+
{ ...environment, CODEX_REMOTE_INSTANCE: instanceId, CODEX_REMOTE_CONTROL_SECRET: controlSecret,
|
|
110
|
+
CODEX_REMOTE_READY_PATH: readyPath }, [path.join(stateRoot, 'bridge.stdout.log'), path.join(stateRoot, 'bridge.stderr.log')]);
|
|
111
|
+
try {
|
|
112
|
+
const deadline = Date.now() + 10000;
|
|
113
|
+
let ready;
|
|
114
|
+
while (!(ready = readRecord(readyPath))) {
|
|
115
|
+
if (!processAlive(pid)) throw new Error('桌面服务启动失败,请查看 bridge.stderr.log。');
|
|
116
|
+
if (Date.now() > deadline) throw new Error('桌面服务未在 10 秒内就绪。');
|
|
117
|
+
await delay(100);
|
|
118
|
+
}
|
|
119
|
+
if (ready.instanceId !== instanceId || ready.pid !== pid) throw new Error('桌面服务启动实例不匹配。');
|
|
120
|
+
session = { ...ready, controlSecret, executablePath: executable, scriptPath, port: config.port,
|
|
121
|
+
lan: true, appServer: false, accessUrl: `http://${address}:${config.port}/?token=${config.token}`,
|
|
122
|
+
startedAt: new Date().toISOString() };
|
|
123
|
+
await controlRequest(session);
|
|
124
|
+
writeRecord(sessionPath, session);
|
|
125
|
+
return await pair();
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (processAlive(pid)) process.kill(pid);
|
|
128
|
+
throw error;
|
|
129
|
+
} finally { fs.rmSync(readyPath, { force: true }); }
|
|
130
|
+
}
|
|
131
|
+
async function start() {
|
|
132
|
+
const result = await startBridge();
|
|
133
|
+
if (fs.existsSync(path.join(stateRoot, 'self-relay-config.json'))) {
|
|
134
|
+
await controlRequest(readRecord(sessionPath), 'POST', 'relay-start');
|
|
135
|
+
}
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
return { start, pair, status, stop, restartManagedAppServer };
|
|
139
|
+
}
|
|
@@ -5,6 +5,7 @@ import { startBackground } from './background-process.mjs';
|
|
|
5
5
|
import { readRecord, writeRecord } from './service-lifecycle.mjs';
|
|
6
6
|
import { runtimePaths } from './platform/runtime-paths.mjs';
|
|
7
7
|
import { desktopHost } from './platform/desktop-host.mjs';
|
|
8
|
+
import releaseVersion from '../shared/release-version.cjs';
|
|
8
9
|
|
|
9
10
|
const versionPattern = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
10
11
|
const statusPath = root => path.join(root, 'service-update.json');
|
|
@@ -27,8 +28,10 @@ export async function checkServiceUpdate(root) {
|
|
|
27
28
|
const result = await fetch('https://registry.npmjs.org/@walkhi%2fcode-relax/latest', { signal: AbortSignal.timeout(12000) });
|
|
28
29
|
if (!result.ok) throw new Error(`查询桌面服务版本失败:HTTP ${result.status}`);
|
|
29
30
|
const latest = await result.json();
|
|
30
|
-
if (!versionPattern.test(latest.version || '')) throw new Error('npm 返回的版本号无效');
|
|
31
|
-
|
|
31
|
+
if (!versionPattern.test(latest.version || '') || releaseVersion.compare(latest.version, latest.version) !== 0) throw new Error('npm 返回的版本号无效');
|
|
32
|
+
const status = serviceUpdateStatus(root);
|
|
33
|
+
return { ...status, latestVersion: latest.version,
|
|
34
|
+
updateAvailable: releaseVersion.compare(latest.version, status.currentVersion) > 0 };
|
|
32
35
|
}
|
|
33
36
|
let scheduling = false;
|
|
34
37
|
export async function requestServiceUpdate(root, body) {
|
|
@@ -50,7 +53,7 @@ export async function requestServiceUpdate(root, body) {
|
|
|
50
53
|
const latest = await checkServiceUpdate(root);
|
|
51
54
|
if (!latest.supported) throw new Error('当前安装缺少 npm 路径,请先安装支持手机更新的过渡版。');
|
|
52
55
|
if (latest.latestVersion !== body.targetVersion) throw new Error('最新版本已变化,请重新检查后确认。');
|
|
53
|
-
if (latest.
|
|
56
|
+
if (!latest.updateAvailable) throw new Error('没有高于当前安装版本的桌面服务更新。');
|
|
54
57
|
const installed = installation();
|
|
55
58
|
const directory = path.join(root, 'service-updates', body.operationId);
|
|
56
59
|
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|