@walkhi/code-relax 0.1.0-beta.14 → 0.1.0-beta.16

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
@@ -20,12 +20,11 @@ npm i -g @walkhi/code-relax@beta --allow-scripts=@walkhi/code-relax --foreground
20
20
  relax start # 启动/复用 Bridge、受管 Codex 内核和已配置中继
21
21
  relax pair # 启动/复用服务并打开配对二维码
22
22
  relax doctor # 集中查看服务、登录自启和最近恢复结果
23
- relax stop
24
23
  ```
25
24
 
26
25
  Codex 内核由官方 `codex app-server --listen ws://127.0.0.1:45839` 提供执行服务,Desktop 和 Bridge 均可连接;它只监听回环地址。Bridge 独立报告内核健康、Desktop 接入和手机可写状态;Desktop 退出后,手机仍可操作内核中的任务。同一 thread 已被另一个实例持有时,手机只读且可重新检测。
27
26
 
28
- 手机工作站详情可确认让 Desktop 接入 Codex 内核,独立进程逐步执行并保留 Bridge/中继连接,见[启用流程](../docs/shared-recovery.md)。本机恢复命令为 `relax shared-recover --confirm`,查看恢复结果用 `relax doctor`。Bridge 状态匹配且健康时直接复用;未知进程占用 45831/45839 时拒绝接管。`stop` 只停止 Bridge,不停止 Desktop 或受管 app-server。
27
+ 手机工作站详情可确认让 Desktop 接入 Codex 内核,独立进程逐步执行并保留 Bridge/中继连接,见[启用流程](../docs/shared-recovery.md)。本机恢复命令为 `relax desktop-recover --confirm`,查看恢复结果用 `relax doctor`。Bridge 状态匹配且健康时直接复用;未知进程占用 45831/45839 时拒绝接管。
29
28
 
30
29
  受管 app-server 通过隐藏控制台启动,使后续控制台子进程继承隐藏窗口。手机的项目、任务列表和新建任务均使用受管内核,不依赖 Desktop 工具管道;项目和任务 ID 保持原生值。
31
30
 
@@ -52,7 +51,7 @@ Bridge 的局域网端口仅提供鉴权 WebSocket,普通 HTTP 请求返回 40
52
51
  npm i -g @walkhi/code-relax@beta --allow-scripts=@walkhi/code-relax --foreground-scripts
53
52
  ```
54
53
 
55
- `setup` 先暂存并校验新版本,再短暂停止 Bridge/中继完成切换;原服务此前正在运行时会自动恢复,新版本启动失败则回滚并启动旧版本。无需提前执行 `stop`。Node 路径变化或原 Node 被删除后需重新 setup。升级可能因 Node 可执行文件路径变化而再次触发 Windows 防火墙授权。
54
+ `setup` 先暂存并校验新版本,再短暂停止 Bridge/中继完成切换;原服务此前正在运行时会自动恢复,新版本启动失败则回滚并启动旧版本。无需提前停止服务。Node 路径变化或原 Node 被删除后需重新 setup。升级可能因 Node 可执行文件路径变化而再次触发 Windows 防火墙授权。
56
55
 
57
56
  卸载:
58
57
 
@@ -25,15 +25,12 @@ const help = `Code Relax(Windows)
25
25
  常用指令:
26
26
  start 启动或复用 Bridge 和 Codex 内核,并恢复已配置的私有中继
27
27
  pair 启动或复用工作站服务,并打开一次性配对二维码
28
- stop 停止服务
29
28
  维护指令:
30
29
  setup 部署并启动固定目录服务、受管 app-server 和登录自启动;安全配置 Desktop 后续接入
31
30
  doctor 检查服务、登录自启、最近恢复结果、网络和 Skill
32
- shared-recover --confirm 重新打开 Desktop 并接入 Codex 内核;不强制重启已有内核
31
+ desktop-recover --confirm 重新打开 Desktop 并接入 Codex 内核;不强制重启已有内核
33
32
  uninstall 停止并移除固定目录服务和本包 Skill,清理自有 Desktop 环境变量,保留其他配置与附件
34
- 其他指令:
35
- autostart-enable 启用当前用户登录启动
36
- 默认监听 0.0.0.0,移动端与电脑需在同一受信任局域网。
33
+ 默认监听 0.0.0.0,移动端与电脑需在同一受信任局域网。
37
34
  --json 输出机器可读结果;统一二维码不含 LAN 地址、Bridge Token 或中继地址。`;
38
35
 
39
36
  async function main() {
@@ -42,8 +39,8 @@ async function main() {
42
39
  console.log(help);
43
40
  return;
44
41
  }
45
- if (!['autostart-enable', 'shared-recover', 'setup', 'doctor', 'uninstall', 'start', 'pair', 'stop'].includes(command)
46
- || flags.some(flag => flag !== '--json' && !(command === 'shared-recover' && flag === '--confirm'))) {
42
+ if (!['desktop-recover', 'setup', 'doctor', 'uninstall', 'start', 'pair'].includes(command)
43
+ || flags.some(flag => flag !== '--json' && !(command === 'desktop-recover' && flag === '--confirm'))) {
47
44
  throw new Error('无效命令或参数。使用 --help 查看用法。');
48
45
  }
49
46
  if (process.platform !== 'win32') throw new Error('当前被控端仅支持 Windows。');
@@ -51,7 +48,7 @@ async function main() {
51
48
  const paths = runtimePaths();
52
49
  const { stateRoot, serviceRoot } = paths;
53
50
  const installedEntry = path.join(serviceRoot, 'dist/bin/codex-remote.mjs');
54
- if (['autostart-enable', 'shared-recover', 'start', 'pair', 'stop'].includes(command) && path.resolve(fileURLToPath(import.meta.url)).toLowerCase() !== installedEntry.toLowerCase()) {
51
+ if (['desktop-recover', 'start', 'pair'].includes(command) && path.resolve(fileURLToPath(import.meta.url)).toLowerCase() !== installedEntry.toLowerCase()) {
55
52
  if (!fs.existsSync(installedEntry)) throw new Error('尚未部署服务,请先执行 relax setup。');
56
53
  const executable = readRecord(path.join(serviceRoot, 'installation.json'))?.nodeExecutable;
57
54
  if (!executable || !fs.existsSync(executable)) throw new Error('安装时选用的 Node 不存在,请重新执行 relax setup。');
@@ -61,31 +58,25 @@ async function main() {
61
58
  return;
62
59
  }
63
60
  const service = lifecycle({ stateRoot, host });
64
- if (command.startsWith('autostart-')) {
61
+ const ensureAutostart = async () => {
65
62
  const installation = readRecord(path.join(serviceRoot, 'installation.json'));
66
63
  if (!installation?.nodeExecutable) throw new Error('请先执行 setup。');
67
- const action = command.slice('autostart-'.length);
68
- let environment;
69
- if (action === 'enable') {
70
- const running = await service.status();
71
- if (!running.running) await service.start();
72
- const deadline = Date.now() + 35000;
73
- while (true) {
74
- const ready = await service.status();
75
- if (ready.running && ready.managedAppServer?.ready) break;
76
- if (Date.now() >= deadline) throw new Error('Codex 内核未就绪,未启用 Desktop 自动接入。');
77
- await delay(250);
78
- }
64
+ const current = await residentStartup({ serviceRoot, nodeExecutable: installation.nodeExecutable }, 'status');
65
+ if (current.enabled) return { ...current, repaired: false };
66
+ const running = await service.status();
67
+ if (!running.running) await service.start();
68
+ const deadline = Date.now() + 35000;
69
+ while (true) {
70
+ const ready = await service.status();
71
+ if (ready.running && ready.managedAppServer?.ready) break;
72
+ if (Date.now() >= deadline) throw new Error('Codex 内核未就绪,未启用 Desktop 自动接入。');
73
+ await delay(250);
79
74
  }
80
- const result = await residentStartup({ serviceRoot, nodeExecutable: installation.nodeExecutable }, action);
81
- if (action === 'enable' && (await desktopHost.inspect()).desktop) environment = await configureManagedEnvironment(stateRoot);
82
- const output = { ...result, ...(environment ? { environment } : {}) };
83
- const environmentText = environment?.preserved ? ';用户自定义的 Desktop 地址已保留,请自行核对' :
84
- environment?.removed ? ';已清除 Code Relax 设置的 Desktop 地址' :
85
- environment?.configured ? ';已配置 Desktop 接入地址' : '';
86
- console.log(json ? JSON.stringify(output) : `登录启动:${result.enabled ? '已启用' : '已关闭'}${environmentText}`); return;
87
- }
88
- if (command === 'shared-recover') {
75
+ const result = await residentStartup({ serviceRoot, nodeExecutable: installation.nodeExecutable }, 'enable');
76
+ if ((await desktopHost.inspect()).desktop) await configureManagedEnvironment(stateRoot);
77
+ return { ...result, repaired: true };
78
+ };
79
+ if (command === 'desktop-recover') {
89
80
  if (!flags.includes('--confirm')) throw new Error(recoveryWarning + ' 使用 --confirm 确认。');
90
81
  const result = await requestRecovery(stateRoot, { confirmed: true });
91
82
  console.log(json ? JSON.stringify(result) : `${result.message || result.status}${result.error ? '\n' + result.error : ''}\n${result.operationId || ''}`.trim());
@@ -98,6 +89,7 @@ async function main() {
98
89
  bridge = await service.status();
99
90
  }
100
91
  if (!bridge.running) throw new Error('Code Relax 工作站服务未能启动。');
92
+ await ensureAutostart();
101
93
  const result = await selfRelay('relay-pair', stateRoot);
102
94
  if (!json && result.qrPath) {
103
95
  const viewer = spawn('explorer.exe', [result.qrPath], { detached: true, stdio: 'ignore', windowsHide: true });
@@ -107,7 +99,7 @@ async function main() {
107
99
  return;
108
100
  }
109
101
  if (command === 'doctor') {
110
- const result = await doctor({ paths, host, service });
102
+ const result = await doctor({ paths, host, service, ensureAutostart });
111
103
  console.log(json ? JSON.stringify(result) : result.checks.map(item => `[${item.status}] ${item.message}`).join('\n'));
112
104
  if (!result.ready) process.exitCode = 1;
113
105
  return;
@@ -256,13 +248,9 @@ async function main() {
256
248
  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}`);
257
249
  return;
258
250
  }
259
- if (command === 'stop') {
260
- const result = await service.stop();
261
- console.log(json ? JSON.stringify(result) : result.message);
262
- return;
263
- }
264
251
  if (!json && command === 'start') console.log('正在检查电脑连接并准备 Bridge…');
265
252
  const result = await service[command]();
253
+ await ensureAutostart();
266
254
  const { accessUrl, qrPath: _legacyQrPath, ...metadata } = result;
267
255
  const notice = '本机服务检查通过,移动端连通性尚未验证。若电脑弹出 Node 网络访问提示,请先处理 Windows 防火墙授权,再用移动端连接。';
268
256
  let relay;
@@ -5,9 +5,9 @@ description: 启动、检查或停止 Code Relax 工作站服务,生成并展
5
5
 
6
6
  # Code Relax
7
7
 
8
- - `relax start --json` 启动 Bridge 和受管 Codex 内核,无需打开 Desktop;支持纯 CLI 安装。Desktop 正常打开后接入已配置内核;需要重新接入时使用 App 的“重启 Codex Desktop”或经确认的 `relax shared-recover --confirm`。已配置 Code Relax 中继时,启动命令同时恢复中继连接器和已有设备授权。
8
+ - `relax start --json` 启动 Bridge 和受管 Codex 内核,无需打开 Desktop;支持纯 CLI 安装。Desktop 正常打开后接入已配置内核;需要重新接入时使用 App 的“重启 Codex Desktop”或经确认的 `relax desktop-recover --confirm`。已配置 Code Relax 中继时,启动命令同时恢复中继连接器和已有设备授权。
9
9
  - npm 全局安装会自动执行服务部署和 Skill 安装;修复安装时运行 `relax setup --json`。
10
- - `setup` 默认启用当前用户登录启动工作站服务,不自动打开 Codex;用 `doctor` 查看、`autostart-enable` 恢复登录启动。恢复完整控制会保留 Bridge 和中继,但仍须明确确认可能中断全部完整控制任务。
10
+ - `setup` 默认启用当前用户登录启动工作站服务,不自动打开 Codex;`start`、`pair`、`setup` 和 `doctor` 在自启缺失时自动恢复。恢复完整控制会保留 Bridge 和中继,但仍须明确确认可能中断全部完整控制任务。
11
11
  - 用户要求启动 Code Relax 时,执行一次 `relax start --json`。用户要求二维码或当前启动结果包含二维码时,展示 CLI 返回的 `qrPath` 和 `displayText`;没有设备授权时可用 `relax pair --json` 生成新的配对二维码。
12
12
  - 配对二维码是短期 HTTPS 邀请,不属于工作站或某一台移动设备,也不含 LAN IP、端口、Bridge Token 或中继地址。新增设备不撤销已有设备。
13
13
  - 已授权设备可直接使用 Code Relax 中继。App 以工作站名显示已配对电脑,进入后选择局域网、Code Relax 中继或 OpenAI 中继。启动命令会经已认证中继临时刷新当前 LAN 候选;App 只保存设备凭据验证成功的网络记录。
@@ -2,10 +2,9 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { readRecord } from './service-lifecycle.mjs';
4
4
  import { inspectSkill } from './skill-install.mjs';
5
- import { residentStartup } from './platform/windows/desktop-host.mjs';
6
5
  import { recoveryStatus } from './shared-recovery.mjs';
7
6
 
8
- export async function doctor({ paths, host, service, skillTarget }) {
7
+ export async function doctor({ paths, host, service, skillTarget, ensureAutostart }) {
9
8
  const checks = [];
10
9
  let installedVersion;
11
10
  const add = (id, status, message) => checks.push({ id, status, message });
@@ -28,6 +27,10 @@ export async function doctor({ paths, host, service, skillTarget }) {
28
27
  if (!address) throw Error('未找到局域网 IPv4;请连接受信任的 Wi-Fi 或有线网络。');
29
28
  add('network', 'ok', `局域网地址:${address}`);
30
29
  });
30
+ await check('autostart', async () => {
31
+ const state = await ensureAutostart();
32
+ add('autostart', 'ok', `当前用户登录自启:${state.repaired ? '已自动恢复' : '已启用'}。`);
33
+ });
31
34
  await check('service', async () => {
32
35
  const state = await service.status();
33
36
  add('service', state.running ? 'ok' : 'warning', state.running
@@ -43,12 +46,6 @@ export async function doctor({ paths, host, service, skillTarget }) {
43
46
  if (state.managedAppServer?.launcherRestartRequired) add('managed-app-server-launcher', 'warning',
44
47
  '安装文件已经更新,但当前 app-server 仍由旧启动链创建;隐藏控制台等运行时修复尚未生效,请执行 relax setup 或重启 Windows。');
45
48
  });
46
- await check('autostart', async () => {
47
- const record = readRecord(path.join(paths.serviceRoot, 'installation.json'));
48
- if (!record?.nodeExecutable) throw Error('尚未安装服务,无法检查登录自启。');
49
- const state = await residentStartup({ serviceRoot: paths.serviceRoot, nodeExecutable: record.nodeExecutable }, 'status');
50
- add('autostart', state.enabled ? 'ok' : 'warning', `当前用户登录自启:${state.enabled ? '已启用' : '未启用'}。`);
51
- });
52
49
  await check('desktop-recovery', async () => {
53
50
  const state = await recoveryStatus(paths.stateRoot);
54
51
  add('desktop-recovery', state.status === 'failed' ? 'warning' : 'ok',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walkhi/code-relax",
3
- "version": "0.1.0-beta.14",
3
+ "version": "0.1.0-beta.16",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "scripts": {