@walkhi/code-relax 0.1.0-beta.4 → 0.1.0-beta.6
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 +1 -1
- package/dist/bin/codex-remote.mjs +22 -4
- package/dist/src/managed-app-server.mjs +9 -3
- package/dist/src/platform/windows/launch-worker.mjs +2 -2
- package/dist/src/self-relay/lifecycle.mjs +33 -34
- package/dist/src/server.mjs +8 -2
- package/dist/src/service-doctor.mjs +2 -0
- package/dist/src/service-lifecycle.mjs +9 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -80,4 +80,4 @@ npm uninstall -g @walkhi/code-relax
|
|
|
80
80
|
|
|
81
81
|
## 发布边界
|
|
82
82
|
|
|
83
|
-
registry 当前公开包为 `@walkhi/code-relax@0.1.0-beta.
|
|
83
|
+
registry 当前公开包为 `@walkhi/code-relax@0.1.0-beta.6`,主 CLI 为 `relax`,并保留 `code-relax` 兼容入口。该版为受管 app-server 记录启动代次,升级发现旧启动链时自动重建进程,使 beta5 的隐藏控制台修复真正生效;`doctor` 会区分安装版本与运行代次。通信协议仍为 7。详细规则见 [npm 分发](../docs/npm-distribution.md)。
|
|
@@ -23,7 +23,7 @@ const help = `Code Relax(Windows)
|
|
|
23
23
|
setup 部署并启动固定目录服务、受管 app-server 和登录自启动;安全配置 Desktop 后续接入
|
|
24
24
|
start 启动或复用 Bridge,并恢复已配置的私有中继
|
|
25
25
|
shared-start 启动或复用完整控制,再启动 Bridge 和已配置的私有中继
|
|
26
|
-
shared-recover --confirm
|
|
26
|
+
shared-recover --confirm 重新打开 Desktop 并接入完整控制;不强制重启已有 app-server
|
|
27
27
|
shared-recovery-status 查看最近一次恢复的实际阶段和结果
|
|
28
28
|
autostart-enable / autostart-disable / autostart-status 管理当前用户登录时启动 Bridge
|
|
29
29
|
desktop-start 恢复基础控制,再启动 Bridge(切换前须自行退出 Desktop)
|
|
@@ -154,8 +154,8 @@ async function main() {
|
|
|
154
154
|
result.environment = environment;
|
|
155
155
|
result.skill = skill.managed ? removeSkill() : { removed: false, message: '未移除自定义或未安装的 Skill。' };
|
|
156
156
|
result.message = environment?.preserved
|
|
157
|
-
? '固定目录服务已卸载;检测到用户自定义的 CODEX_APP_SERVER_WS_URL
|
|
158
|
-
: '固定目录服务已卸载;Code Relax 设置的 app-server
|
|
157
|
+
? '固定目录服务已卸载;检测到用户自定义的 CODEX_APP_SERVER_WS_URL,已保留。配置、附件和可能仍在执行任务的 app-server 也已保留;重启 Windows可结束该进程。npm 命令入口可再用 npm uninstall -g @walkhi/code-relax 移除。'
|
|
158
|
+
: '固定目录服务已卸载;Code Relax 设置的 app-server 环境变量已清理,其他配置、附件和可能仍在执行任务的 app-server 已保留;重启 Windows可结束该进程。npm 命令入口可再用 npm uninstall -g @walkhi/code-relax 移除。';
|
|
159
159
|
console.log(json ? JSON.stringify(result) : `${result.message}\n${result.skill.message}`);
|
|
160
160
|
return;
|
|
161
161
|
}
|
|
@@ -205,18 +205,36 @@ async function main() {
|
|
|
205
205
|
if (!bridge.running || bridge.managedAppServer?.state !== 'ready') {
|
|
206
206
|
throw new Error(bridge.managedAppServer?.error || 'Bridge 已安装,但受管 app-server 未就绪;未写入 Desktop 环境变量。');
|
|
207
207
|
}
|
|
208
|
+
let managedAppServerMigrated = false;
|
|
209
|
+
if (bridge.managedAppServer.launcherRestartRequired) {
|
|
210
|
+
await service.restartManagedAppServer();
|
|
211
|
+
const migrationDeadline = Date.now() + 40_000;
|
|
212
|
+
do {
|
|
213
|
+
await delay(250);
|
|
214
|
+
bridge = await service.status();
|
|
215
|
+
if (bridge.managedAppServer?.restart?.status === 'failed') {
|
|
216
|
+
throw new Error(bridge.managedAppServer.restart.error || '受管 app-server 启动链迁移失败。');
|
|
217
|
+
}
|
|
218
|
+
} while (Date.now() < migrationDeadline && (!bridge.running || bridge.managedAppServer?.state !== 'ready' || bridge.managedAppServer.launcherRestartRequired));
|
|
219
|
+
if (!bridge.running || bridge.managedAppServer?.state !== 'ready' || bridge.managedAppServer.launcherRestartRequired) {
|
|
220
|
+
throw new Error('受管 app-server 未在 40 秒内完成启动链迁移。');
|
|
221
|
+
}
|
|
222
|
+
managedAppServerMigrated = true;
|
|
223
|
+
}
|
|
208
224
|
const autostart = await residentStartup(installed, 'enable');
|
|
209
225
|
const environment = await configureManagedEnvironment(physicalStateRoot);
|
|
210
226
|
installed.autostart = autostart;
|
|
211
227
|
installed.restarted = resumeAfterUpdate;
|
|
212
228
|
installed.environment = environment;
|
|
213
229
|
installed.managedAppServer = bridge.managedAppServer;
|
|
230
|
+
installed.managedAppServerMigrated = managedAppServerMigrated;
|
|
214
231
|
installed.desktop = bridge.desktop;
|
|
215
232
|
installed.desktopRestartRecommended = bridge.desktop?.state === 'ordinary';
|
|
233
|
+
const migrationText = managedAppServerMigrated ? '已自动重建旧启动链的 app-server;安装前尚未完成的任务可能已中断。\n' : '';
|
|
216
234
|
const restartText = installed.desktopRestartRecommended
|
|
217
235
|
? 'Codex 当前仍在使用原连接。可以在手机端选择“立即重启(推荐)”,或稍后自行完全退出再启动。'
|
|
218
236
|
: '下次正常启动 Codex 时会连接受管 app-server。';
|
|
219
|
-
console.log(json ? JSON.stringify({ ...installed, nextSteps: desktopSetupSteps, displayText: `${desktopSetupSteps.join('\n')}\n${restartText}` }) : `服务已安装:${installed.serviceRoot}\n使用已安装的 Node:${installed.nodeExecutable}\n受管 app-server 已就绪;首次联网可能需要 Windows 防火墙授权。\n${desktopSetupSteps.join('\n')}\n${restartText}`);
|
|
237
|
+
console.log(json ? JSON.stringify({ ...installed, nextSteps: desktopSetupSteps, displayText: `${migrationText}${desktopSetupSteps.join('\n')}\n${restartText}` }) : `服务已安装:${installed.serviceRoot}\n使用已安装的 Node:${installed.nodeExecutable}\n受管 app-server 已就绪;首次联网可能需要 Windows 防火墙授权。\n${migrationText}${desktopSetupSteps.join('\n')}\n${restartText}`);
|
|
220
238
|
return;
|
|
221
239
|
}
|
|
222
240
|
if (command === 'stop') {
|
|
@@ -8,6 +8,7 @@ import { SharedAppServer } from './shared-app-server.mjs';
|
|
|
8
8
|
|
|
9
9
|
export const managedAppServerUrl = 'ws://127.0.0.1:45839';
|
|
10
10
|
export const managedAppServerReadyUrl = 'http://127.0.0.1:45839/readyz';
|
|
11
|
+
export const managedAppServerLauncherRevision = 2;
|
|
11
12
|
const retryDelays = [1_000, 2_000, 5_000, 10_000, 20_000];
|
|
12
13
|
|
|
13
14
|
export function managedPortAvailable() {
|
|
@@ -80,21 +81,26 @@ export class ManagedAppServer extends EventEmitter {
|
|
|
80
81
|
[path.join(this.stateRoot, 'managed-app-server.stdout.log'), path.join(this.stateRoot, 'managed-app-server.stderr.log')]);
|
|
81
82
|
const started = await this.host.process(pid);
|
|
82
83
|
if (!sameManagedProcess(started, started)) throw new Error('Codex 内核启动后进程身份无法确认。');
|
|
83
|
-
|
|
84
|
+
actual = started;
|
|
85
|
+
record = { ...started, launcherRevision: managedAppServerLauncherRevision };
|
|
84
86
|
writeRecord(this.recordPath, record);
|
|
85
87
|
} else if (!readRecord(this.recordPath)) {
|
|
86
|
-
|
|
88
|
+
record = { ...actual, launcherRevision: Number.isSafeInteger(record?.launcherRevision) ? record.launcherRevision : 0 };
|
|
89
|
+
writeRecord(this.recordPath, record);
|
|
87
90
|
}
|
|
88
91
|
await this.waitUntilReady(actual);
|
|
89
92
|
await this.client.connect();
|
|
90
93
|
if (!this.client.ready) throw new Error('Codex 内核协议初始化未完成。');
|
|
91
94
|
const preferredExecutable = path.resolve(info.cli);
|
|
92
95
|
const updatePending = path.resolve(actual.executable).toLowerCase() !== preferredExecutable.toLowerCase();
|
|
96
|
+
const launcherRevision = Number.isSafeInteger(record?.launcherRevision) ? record.launcherRevision : 0;
|
|
93
97
|
this.retryAttempt = 0;
|
|
94
98
|
this.retryAfter = 0;
|
|
95
99
|
this.publish({ state: 'ready', ready: true, error: null, pid: actual.pid,
|
|
96
100
|
created: actual.created, executable: actual.executable, preferredExecutable,
|
|
97
|
-
updatePending,
|
|
101
|
+
updatePending, launcherRevision,
|
|
102
|
+
launcherRestartRequired: launcherRevision !== managedAppServerLauncherRevision,
|
|
103
|
+
url: managedAppServerUrl });
|
|
98
104
|
return true;
|
|
99
105
|
} catch (error) {
|
|
100
106
|
this.failed(error instanceof Error ? error.message : String(error));
|
|
@@ -17,7 +17,6 @@ try {
|
|
|
17
17
|
const descriptors = (spec.logs || []).map(file => fs.openSync(file, 'a'));
|
|
18
18
|
try {
|
|
19
19
|
const child = spawn(spec.executable, spec.args || [], {
|
|
20
|
-
detached: true,
|
|
21
20
|
windowsHide: true,
|
|
22
21
|
env: spec.environment,
|
|
23
22
|
stdio: ['ignore', descriptors[0] ?? 'ignore', descriptors[1] ?? 'ignore'],
|
|
@@ -26,7 +25,8 @@ try {
|
|
|
26
25
|
child.once('spawn', resolve);
|
|
27
26
|
child.once('error', reject);
|
|
28
27
|
});
|
|
29
|
-
|
|
28
|
+
// Keep this WMI-owned broker alive with the target so Codex tools inherit
|
|
29
|
+
// its hidden console instead of creating a visible console per command.
|
|
30
30
|
result.pid = child.pid;
|
|
31
31
|
diagnostic(`target started (PID ${child.pid})`);
|
|
32
32
|
} finally {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { execFile } from 'node:child_process';
|
|
4
|
-
import { setTimeout as delay } from 'node:timers/promises';
|
|
5
|
-
import { startBackground, processAlive } from '../background-process.mjs';
|
|
6
|
-
import { promisify } from 'node:util';
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { execFile } from 'node:child_process';
|
|
4
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
5
|
+
import { startBackground, processAlive } from '../background-process.mjs';
|
|
6
|
+
import { promisify } from 'node:util';
|
|
7
7
|
import { randomBytes, randomUUID } from 'node:crypto';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import { hostname } from 'node:os';
|
|
@@ -16,8 +16,8 @@ import { connectBridgeRelay } from './connector.mjs';
|
|
|
16
16
|
import { validProbeIceServers } from './p2p-probe.mjs';
|
|
17
17
|
import { relayAddress, validSecret } from './wire.mjs';
|
|
18
18
|
import { saveLanAuthorizations } from './lan-authorizations.mjs';
|
|
19
|
-
import { lanNetworks } from '../platform/windows/network-info.mjs';
|
|
20
|
-
|
|
19
|
+
import { lanNetworks } from '../platform/windows/network-info.mjs';
|
|
20
|
+
|
|
21
21
|
const exec = promisify(execFile);
|
|
22
22
|
const script = fileURLToPath(import.meta.url);
|
|
23
23
|
const recordPath = root => path.join(root, 'self-relay-session.json');
|
|
@@ -32,34 +32,33 @@ async function loadConfig(stateRoot) {
|
|
|
32
32
|
if (config.p2pWindowsRouteHelper !== undefined && typeof config.p2pWindowsRouteHelper !== 'boolean') throw new Error('p2pWindowsRouteHelper 必须是布尔值');
|
|
33
33
|
if (config.p2pWindowsRouteHelper && !/^[A-Za-z0-9_-]{32,128}$/.test(config.p2pWindowsRouteHelperToken || '')) throw new Error('p2pWindowsRouteHelperToken 无效');
|
|
34
34
|
const url = process.env.RELAY_URL || config.url;
|
|
35
|
-
if (!url) throw new Error('请先配置 self-relay-config.json 的 url 和 hostKeyFile(或 sshHost、sshKeyPath),详见自建中继文档。');
|
|
35
|
+
if (!url) throw new Error('请先配置 self-relay-config.json 的 url 和 hostKeyFile(或 sshHost、sshKeyPath),详见自建中继文档。');
|
|
36
36
|
relayAddress(url);
|
|
37
37
|
const resolved = relayAddress(url);
|
|
38
38
|
const pairingUrl = config.pairingUrl || `${resolved.startsWith('wss:') ? 'https:' : 'http:'}//${new URL(resolved).host}/pair`;
|
|
39
39
|
const pairing = new URL(pairingUrl);
|
|
40
40
|
if (!['https:', 'http:'].includes(pairing.protocol) || pairing.pathname !== '/pair' || pairing.search || pairing.hash || pairing.username || pairing.password ||
|
|
41
41
|
(pairing.protocol === 'http:' && pairing.hostname !== '127.0.0.1')) throw new Error('self-relay-config.json 的 pairingUrl 必须是 HTTPS /pair 地址。');
|
|
42
|
-
return { ...config, url: resolved, pairingUrl: pairing.href };
|
|
43
|
-
}
|
|
42
|
+
return { ...config, url: resolved, pairingUrl: pairing.href };
|
|
43
|
+
}
|
|
44
44
|
async function registrationKey(config) {
|
|
45
45
|
if (config.url === officialConfig.url) return undefined;
|
|
46
46
|
const hostKeyFile = process.env.RELAY_HOST_KEY_FILE || config.hostKeyFile;
|
|
47
|
-
let hostKey;
|
|
48
|
-
if (hostKeyFile) hostKey = fs.readFileSync(hostKeyFile, 'utf8').trim();
|
|
49
|
-
else {
|
|
50
|
-
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(config.sshHost || '') ||
|
|
51
|
-
!/^\/[a-zA-Z0-9/_.-]+$/.test(config.sshKeyPath || '')) throw new Error('中继注册凭据配置不完整或无效。');
|
|
52
|
-
try {
|
|
53
|
-
const result = await exec('ssh', ['-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', config.sshHost,
|
|
54
|
-
`sudo -n cat ${config.sshKeyPath}`], { encoding: 'utf8', timeout: 15000, windowsHide: true });
|
|
55
|
-
hostKey = result.stdout.trim();
|
|
56
|
-
} catch { throw new Error('无法通过已配置 SSH 读取中继注册凭据。'); }
|
|
57
|
-
}
|
|
58
|
-
if (!validSecret(hostKey)) throw new Error('中继注册凭据格式无效。');
|
|
59
|
-
return hostKey;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export async function selfRelay(command, stateRoot, requiredMode = '') {
|
|
47
|
+
let hostKey;
|
|
48
|
+
if (hostKeyFile) hostKey = fs.readFileSync(hostKeyFile, 'utf8').trim();
|
|
49
|
+
else {
|
|
50
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(config.sshHost || '') ||
|
|
51
|
+
!/^\/[a-zA-Z0-9/_.-]+$/.test(config.sshKeyPath || '')) throw new Error('中继注册凭据配置不完整或无效。');
|
|
52
|
+
try {
|
|
53
|
+
const result = await exec('ssh', ['-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', config.sshHost,
|
|
54
|
+
`sudo -n cat ${config.sshKeyPath}`], { encoding: 'utf8', timeout: 15000, windowsHide: true });
|
|
55
|
+
hostKey = result.stdout.trim();
|
|
56
|
+
} catch { throw new Error('无法通过已配置 SSH 读取中继注册凭据。'); }
|
|
57
|
+
}
|
|
58
|
+
if (!validSecret(hostKey)) throw new Error('中继注册凭据格式无效。');
|
|
59
|
+
return hostKey;
|
|
60
|
+
}
|
|
61
|
+
export async function selfRelay(command, stateRoot, requiredMode = '') {
|
|
63
62
|
const previous = readRecord(recordPath(stateRoot));
|
|
64
63
|
let current;
|
|
65
64
|
if (previous) {
|
|
@@ -139,12 +138,12 @@ async function worker(stateRoot, instanceId, refresh = false, requiredMode = '')
|
|
|
139
138
|
fs.rmSync(legacyPath);
|
|
140
139
|
}
|
|
141
140
|
let identity = identities.servers[config.url];
|
|
142
|
-
if (!identity) {
|
|
143
|
-
identity = { id: randomUUID(), secret: randomBytes(32).toString('base64url'), registered: false };
|
|
144
|
-
identities.servers[config.url] = identity; await credentialStore('save', identities);
|
|
145
|
-
}
|
|
146
|
-
const hostKey = identity.registered ? undefined : await registrationKey(config);
|
|
147
|
-
let bridgeError = '';
|
|
141
|
+
if (!identity) {
|
|
142
|
+
identity = { id: randomUUID(), secret: randomBytes(32).toString('base64url'), registered: false };
|
|
143
|
+
identities.servers[config.url] = identity; await credentialStore('save', identities);
|
|
144
|
+
}
|
|
145
|
+
const hostKey = identity.registered ? undefined : await registrationKey(config);
|
|
146
|
+
let bridgeError = '';
|
|
148
147
|
async function refreshHealth() {
|
|
149
148
|
try { health = await serviceHealth(session()) || { transportMode: health.transportMode, status: 'offline', desktopConnected: false }; bridgeError = ''; }
|
|
150
149
|
catch (error) { bridgeError = error.message; health = { transportMode: health.transportMode, status: 'offline', desktopConnected: false }; }
|
|
@@ -204,7 +203,7 @@ async function worker(stateRoot, instanceId, refresh = false, requiredMode = '')
|
|
|
204
203
|
await refreshHealth();
|
|
205
204
|
const lanCandidates = await currentLanCandidates();
|
|
206
205
|
connector?.events.removeAllListeners(); connector?.close();
|
|
207
|
-
const current = connector = connectBridgeRelay({ relayUrl: config.url, hostKey: identity.registered ? undefined : hostKey, identity,
|
|
206
|
+
const current = connector = connectBridgeRelay({ relayUrl: config.url, hostKey: identity.registered ? undefined : hostKey, identity,
|
|
208
207
|
bridgeUrl: `ws://127.0.0.1:${bridge?.port || 45831}/api/socket`, bridgeToken: bridge?.accessUrl ? new URL(bridge.accessUrl).searchParams.get('token') : 'unavailable',
|
|
209
208
|
bridgeConnection: () => { const latest = session(); return { url: `ws://127.0.0.1:${latest.port}/api/socket`, token: new URL(latest.accessUrl).searchParams.get('token') }; },
|
|
210
209
|
hostName: hostname(), lanCandidates, p2pProbe: config.p2pProbe === true, iceServers: config.p2pIceServers || [],
|
package/dist/src/server.mjs
CHANGED
|
@@ -768,8 +768,14 @@ server.on('error', error => {
|
|
|
768
768
|
server.listen(options.port, options.lan ? '0.0.0.0' : '127.0.0.1', async () => {
|
|
769
769
|
try {
|
|
770
770
|
if (process.env.CODEX_REMOTE_INSTANCE && process.env.CODEX_REMOTE_CONTROL_SECRET && process.env.CODEX_REMOTE_READY_PATH) {
|
|
771
|
-
control = await openControl({ instanceId: process.env.CODEX_REMOTE_INSTANCE,
|
|
772
|
-
secret: process.env.CODEX_REMOTE_CONTROL_SECRET, shutdown: shutDown, actions: {
|
|
771
|
+
control = await openControl({ instanceId: process.env.CODEX_REMOTE_INSTANCE,
|
|
772
|
+
secret: process.env.CODEX_REMOTE_CONTROL_SECRET, shutdown: shutDown, actions: {
|
|
773
|
+
'refresh-execution': refreshExecution,
|
|
774
|
+
'restart-managed-app-server': () => {
|
|
775
|
+
const state = managedAppServer.snapshot();
|
|
776
|
+
return state.launcherRestartRequired ? managedAppServer.restart(crypto.randomUUID()) : state;
|
|
777
|
+
},
|
|
778
|
+
} });
|
|
773
779
|
writeRecord(process.env.CODEX_REMOTE_READY_PATH, { instanceId: process.env.CODEX_REMOTE_INSTANCE,
|
|
774
780
|
pid: process.pid, controlPort: control.port });
|
|
775
781
|
}
|
|
@@ -40,6 +40,8 @@ export async function doctor({ paths, host, service, skillTarget }) {
|
|
|
40
40
|
: state.managedAppServer?.error || '受管 app-server 尚未就绪。');
|
|
41
41
|
if (state.managedAppServer?.updatePending) add('managed-app-server-update', 'warning',
|
|
42
42
|
'Codex Desktop 已提供新版 CLI;当前 app-server 会继续完成任务,并在下次进程自然重启或重新登录 Windows 后采用新版。');
|
|
43
|
+
if (state.managedAppServer?.launcherRestartRequired) add('managed-app-server-launcher', 'warning',
|
|
44
|
+
'安装文件已经更新,但当前 app-server 仍由旧启动链创建;隐藏控制台等运行时修复尚未生效,请执行 relax setup 或重启 Windows。');
|
|
43
45
|
});
|
|
44
46
|
await check('desktop-environment', async () => {
|
|
45
47
|
const environment = await readManagedEnvironment();
|
|
@@ -61,7 +61,7 @@ export function lifecycle({ stateRoot, host }) {
|
|
|
61
61
|
desktopRestartRecommended: result?.desktopRestartRecommended === true,
|
|
62
62
|
resident: result?.resident === true };
|
|
63
63
|
}
|
|
64
|
-
async function pair() {
|
|
64
|
+
async function pair() {
|
|
65
65
|
const session = readRecord(sessionPath);
|
|
66
66
|
const connection = await alive(session) ? await health(session) : null;
|
|
67
67
|
if (connection?.status !== 'ok' || !session.lan) {
|
|
@@ -76,7 +76,12 @@ export function lifecycle({ stateRoot, host }) {
|
|
|
76
76
|
execution: connection.execution, managedAppServer: connection.managedAppServer, desktop: connection.desktop,
|
|
77
77
|
desktopRestartRecommended: connection.desktopRestartRecommended === true, accessUrl: session.accessUrl,
|
|
78
78
|
qrPath: path.join(stateRoot, 'connection-qr.png') };
|
|
79
|
-
}
|
|
79
|
+
}
|
|
80
|
+
async function restartManagedAppServer() {
|
|
81
|
+
const session = readRecord(sessionPath);
|
|
82
|
+
if (!await alive(session)) throw new Error('Bridge 尚未运行,无法迁移受管 app-server。');
|
|
83
|
+
return controlRequest(session, 'POST', 'restart-managed-app-server');
|
|
84
|
+
}
|
|
80
85
|
async function start() {
|
|
81
86
|
let session = readRecord(sessionPath);
|
|
82
87
|
if (await alive(session)) {
|
|
@@ -126,5 +131,5 @@ export function lifecycle({ stateRoot, host }) {
|
|
|
126
131
|
throw error;
|
|
127
132
|
} finally { fs.rmSync(readyPath, { force: true }); }
|
|
128
133
|
}
|
|
129
|
-
return { start, pair, status, stop };
|
|
130
|
-
}
|
|
134
|
+
return { start, pair, status, stop, restartManagedAppServer };
|
|
135
|
+
}
|