@walkhi/code-relax 0.1.0-beta.5 → 0.1.0-beta.7
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
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.7`,主 CLI 为 `relax`,并保留 `code-relax` 兼容入口。该版沿用 beta6 的启动代次自动迁移,并在 npm 安装控制台明确报告 app-server 是否重启、迁移前后 PID、Bridge 恢复结果和安装日志路径;`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,45 @@ 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
|
+
const managedAppServerMigration = {
|
|
210
|
+
required: bridge.managedAppServer.launcherRestartRequired === true,
|
|
211
|
+
restarted: false,
|
|
212
|
+
previousPid: bridge.managedAppServer.pid || null,
|
|
213
|
+
};
|
|
214
|
+
if (managedAppServerMigration.required) {
|
|
215
|
+
await service.restartManagedAppServer();
|
|
216
|
+
const migrationDeadline = Date.now() + 40_000;
|
|
217
|
+
do {
|
|
218
|
+
await delay(250);
|
|
219
|
+
bridge = await service.status();
|
|
220
|
+
if (bridge.managedAppServer?.restart?.status === 'failed') {
|
|
221
|
+
throw new Error(bridge.managedAppServer.restart.error || '受管 app-server 启动链迁移失败。');
|
|
222
|
+
}
|
|
223
|
+
} while (Date.now() < migrationDeadline && (!bridge.running || bridge.managedAppServer?.state !== 'ready' || bridge.managedAppServer.launcherRestartRequired));
|
|
224
|
+
if (!bridge.running || bridge.managedAppServer?.state !== 'ready' || bridge.managedAppServer.launcherRestartRequired) {
|
|
225
|
+
throw new Error('受管 app-server 未在 40 秒内完成启动链迁移。');
|
|
226
|
+
}
|
|
227
|
+
managedAppServerMigrated = true;
|
|
228
|
+
managedAppServerMigration.restarted = true;
|
|
229
|
+
}
|
|
208
230
|
const autostart = await residentStartup(installed, 'enable');
|
|
209
231
|
const environment = await configureManagedEnvironment(physicalStateRoot);
|
|
210
232
|
installed.autostart = autostart;
|
|
211
233
|
installed.restarted = resumeAfterUpdate;
|
|
212
234
|
installed.environment = environment;
|
|
213
235
|
installed.managedAppServer = bridge.managedAppServer;
|
|
236
|
+
installed.managedAppServerMigrated = managedAppServerMigrated;
|
|
237
|
+
installed.managedAppServerMigration = { ...managedAppServerMigration,
|
|
238
|
+
currentPid: bridge.managedAppServer.pid || null,
|
|
239
|
+
launcherRevision: bridge.managedAppServer.launcherRevision ?? null };
|
|
214
240
|
installed.desktop = bridge.desktop;
|
|
215
241
|
installed.desktopRestartRecommended = bridge.desktop?.state === 'ordinary';
|
|
242
|
+
const migrationText = managedAppServerMigrated ? '已自动重建旧启动链的 app-server;安装前尚未完成的任务可能已中断。\n' : '';
|
|
216
243
|
const restartText = installed.desktopRestartRecommended
|
|
217
244
|
? 'Codex 当前仍在使用原连接。可以在手机端选择“立即重启(推荐)”,或稍后自行完全退出再启动。'
|
|
218
245
|
: '下次正常启动 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}`);
|
|
246
|
+
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
247
|
return;
|
|
221
248
|
}
|
|
222
249
|
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));
|
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
|
+
}
|
package/package.json
CHANGED
package/tools/postinstall.mjs
CHANGED
|
@@ -33,12 +33,12 @@ function systemSummary() {
|
|
|
33
33
|
+ `osArch=${os.arch()} processArch=${process.arch} node=${process.version} npmUserAgent=${npm}`;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function runStage(entry, stage, args) {
|
|
36
|
+
function runStage(entry, stage, args, formatOutput = value => value) {
|
|
37
37
|
appendLog(`[${stage}] start`);
|
|
38
38
|
try {
|
|
39
39
|
const output = execFileSync(process.execPath, [entry, ...args], { encoding: 'utf8', windowsHide: true,
|
|
40
40
|
stdio: ['inherit', 'pipe', 'pipe'] });
|
|
41
|
-
if (output) { process.stdout.write(output); appendLog(`[${stage}] stdout\n${output.trimEnd()}`); }
|
|
41
|
+
if (output) { process.stdout.write(formatOutput(output)); appendLog(`[${stage}] stdout\n${output.trimEnd()}`); }
|
|
42
42
|
appendLog(`[${stage}] success`);
|
|
43
43
|
} catch (error) {
|
|
44
44
|
if (error?.stdout) { process.stdout.write(error.stdout); appendLog(`[${stage}] stdout\n${String(error.stdout).trimEnd()}`); }
|
|
@@ -48,6 +48,30 @@ function runStage(entry, stage, args) {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
export function formatSetupOutput(output, previous = {}) {
|
|
52
|
+
try {
|
|
53
|
+
const result = JSON.parse(output);
|
|
54
|
+
const migration = result.managedAppServerMigration || {};
|
|
55
|
+
const pid = migration.currentPid || result.managedAppServer?.pid || '未知';
|
|
56
|
+
const appServer = migration.restarted
|
|
57
|
+
? `已重启(旧启动链迁移,PID ${migration.previousPid || '未知'} → ${pid})`
|
|
58
|
+
: previous.valid
|
|
59
|
+
? `未重启(PID ${pid},启动代次 ${migration.launcherRevision ?? '未知'} 已是最新)`
|
|
60
|
+
: `已启动(PID ${pid},启动代次 ${migration.launcherRevision ?? '未知'})`;
|
|
61
|
+
return `Code Relax ${result.version || '未知版本'} 安装成功。\n`
|
|
62
|
+
+ `受管 app-server:${appServer}\n`
|
|
63
|
+
+ `Bridge:${result.restarted ? '已恢复并健康' : '已启动并健康'}\n`
|
|
64
|
+
+ `${result.desktopRestartRecommended ? 'Codex Desktop:建议完全退出后重新打开。\n' : ''}`;
|
|
65
|
+
} catch { return output; }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function formatSkillOutput(output) {
|
|
69
|
+
try {
|
|
70
|
+
const result = JSON.parse(output);
|
|
71
|
+
return `Code Relax Skill:${result.message || (result.installed ? '已安装' : '已检查')}\n`;
|
|
72
|
+
} catch { return output; }
|
|
73
|
+
}
|
|
74
|
+
|
|
51
75
|
function installationSnapshot() {
|
|
52
76
|
const recordPath = path.join(serviceRoot, 'installation.json');
|
|
53
77
|
if (!fs.existsSync(serviceRoot)) return { exists: false, valid: false };
|
|
@@ -101,9 +125,10 @@ if (process.env.npm_config_global === 'true') {
|
|
|
101
125
|
let stage = 'setup';
|
|
102
126
|
appendLog(`[postinstall] start package=${process.env.npm_package_version || 'unknown'} ${systemSummary()}`);
|
|
103
127
|
try {
|
|
104
|
-
runStage(entry, stage, ['setup', '--json']);
|
|
128
|
+
runStage(entry, stage, ['setup', '--json'], output => formatSetupOutput(output, previous));
|
|
105
129
|
stage = 'skill-install';
|
|
106
|
-
runStage(entry, stage, ['skill-install', '--json']);
|
|
130
|
+
runStage(entry, stage, ['skill-install', '--json'], formatSkillOutput);
|
|
131
|
+
console.log(`安装日志:${installLog}`);
|
|
107
132
|
appendLog('[postinstall] success');
|
|
108
133
|
} catch (error) {
|
|
109
134
|
const current = installationSnapshot();
|