@walkhi/code-relax 0.1.0-beta.50 → 0.1.0-beta.51

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.
Files changed (89) hide show
  1. package/README.md +105 -105
  2. package/dist/bin/cleanup-accounts.mjs +63 -63
  3. package/dist/bin/codex-remote.mjs +403 -403
  4. package/dist/bin/publish-android-release.mjs +63 -63
  5. package/dist/bin/release-upload-server.mjs +190 -190
  6. package/dist/build-platform.json +1 -1
  7. package/dist/get-shared-service-url.ps1 +14 -14
  8. package/dist/shared/code-browser.cjs +159 -159
  9. package/dist/shared/file-changes.cjs +31 -31
  10. package/dist/shared/file-citations.cjs +117 -117
  11. package/dist/shared/file-mentions.cjs +92 -92
  12. package/dist/shared/message-delivery.cjs +115 -115
  13. package/dist/shared/p2p-file-channel.cjs +87 -87
  14. package/dist/shared/server-requests.cjs +311 -311
  15. package/dist/shared/timeline-sync.cjs +173 -173
  16. package/dist/skills/code-relax/SKILL.md +22 -22
  17. package/dist/src/bounded-process-log.mjs +63 -63
  18. package/dist/src/celsius-client.mjs +107 -107
  19. package/dist/src/dots-approval-timeline.mjs +99 -99
  20. package/dist/src/dots-avatar.mjs +42 -42
  21. package/dist/src/dots-chat-projection.mjs +50 -50
  22. package/dist/src/dots-chat-server.mjs +483 -483
  23. package/dist/src/dots-message-images.mjs +319 -319
  24. package/dist/src/dots-profile.mjs +77 -77
  25. package/dist/src/dots-room-activity.mjs +172 -172
  26. package/dist/src/dots-room-client.mjs +135 -135
  27. package/dist/src/dots-room-projection.mjs +94 -94
  28. package/dist/src/dots-schedules.mjs +66 -66
  29. package/dist/src/dots-summary-tasks.mjs +32 -32
  30. package/dist/src/dots-task-catalog.mjs +430 -430
  31. package/dist/src/file-transfer-range.mjs +22 -22
  32. package/dist/src/file-upload-session.mjs +130 -130
  33. package/dist/src/hosted-history-app-server.mjs +103 -103
  34. package/dist/src/hosted-history-cache.mjs +113 -113
  35. package/dist/src/lan-socket-server.mjs +266 -266
  36. package/dist/src/managed-app-server.mjs +373 -373
  37. package/dist/src/managed-endpoint.mjs +25 -25
  38. package/dist/src/message-upload-files.mjs +109 -109
  39. package/dist/src/onboarding.mjs +12 -12
  40. package/dist/src/platform/README.md +1 -1
  41. package/dist/src/platform/codex-protocol.mjs +46 -46
  42. package/dist/src/platform/desktop-project-store.mjs +208 -208
  43. package/dist/src/platform/linux/desktop-host.mjs +87 -87
  44. package/dist/src/platform/linux/process.mjs +53 -53
  45. package/dist/src/platform/linux/system.mjs +50 -50
  46. package/dist/src/platform/linux/user-environment.mjs +9 -9
  47. package/dist/src/platform/macos/codex-runtime.mjs +125 -125
  48. package/dist/src/platform/macos/desktop-host.mjs +86 -86
  49. package/dist/src/platform/macos/user-environment.mjs +9 -9
  50. package/dist/src/platform/native-package.mjs +58 -58
  51. package/dist/src/platform/windows/activate-desktop.ps1 +49 -49
  52. package/dist/src/platform/windows/codex-runtime.mjs +215 -215
  53. package/dist/src/platform/windows/credential-store.mjs +27 -27
  54. package/dist/src/platform/windows/credential-store.ps1 +49 -49
  55. package/dist/src/platform/windows/desktop-host.mjs +65 -65
  56. package/dist/src/platform/windows/desktop-info.ps1 +38 -38
  57. package/dist/src/platform/windows/desktop-project-registration.mjs +4 -4
  58. package/dist/src/platform/windows/desktop-shortcuts.ps1 +14 -14
  59. package/dist/src/platform/windows/dots-profile.mjs +49 -49
  60. package/dist/src/platform/windows/launch-worker.mjs +41 -41
  61. package/dist/src/platform/windows/project-locations.mjs +16 -16
  62. package/dist/src/platform/windows/service-host.mjs +35 -35
  63. package/dist/src/platform/windows/user-environment.mjs +52 -52
  64. package/dist/src/project-management.mjs +58 -58
  65. package/dist/src/runtime-discovery.mjs +47 -47
  66. package/dist/src/self-relay/account-cleanup.mjs +30 -30
  67. package/dist/src/self-relay/admin-page.mjs +80 -80
  68. package/dist/src/self-relay/admin-style.mjs +14 -14
  69. package/dist/src/self-relay/connector.mjs +439 -439
  70. package/dist/src/self-relay/demo.mjs +31 -31
  71. package/dist/src/self-relay/diagnostics.mjs +54 -54
  72. package/dist/src/self-relay/email-accounts.mjs +183 -183
  73. package/dist/src/self-relay/file-wire.mjs +13 -13
  74. package/dist/src/self-relay/p2p-probe.mjs +168 -168
  75. package/dist/src/self-relay/privacy-page.mjs +26 -26
  76. package/dist/src/self-relay/public-releases.mjs +67 -67
  77. package/dist/src/self-relay/rtc-file-bridge.mjs +41 -41
  78. package/dist/src/self-relay/usage-meter.mjs +141 -141
  79. package/dist/src/server.mjs +5 -4
  80. package/dist/src/service-doctor.mjs +148 -148
  81. package/dist/src/service-install.mjs +97 -97
  82. package/dist/src/service-lifecycle.mjs +139 -139
  83. package/dist/src/shared-app-server.mjs +666 -666
  84. package/dist/src/shared-catalog.mjs +132 -132
  85. package/dist/src/shared-thread-stream.mjs +165 -165
  86. package/dist/src/usage-reset.mjs +17 -17
  87. package/dist/src/workstation-downloads.mjs +120 -120
  88. package/package.json +4 -4
  89. package/tools/postinstall.mjs +186 -186
@@ -1,148 +1,148 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import os from 'node:os';
4
- import { readRecord } from './service-lifecycle.mjs';
5
- import { inspectSkill } from './skill-install.mjs';
6
- import { recoveryStatus } from './shared-recovery.mjs';
7
-
8
- export function packageDeploymentCheck(cliVersion, installedVersion) {
9
- if (!cliVersion) return { id: 'package-version', status: 'warning', message: '无法读取当前 CLI 包版本,未确认本次部署版本。' };
10
- if (cliVersion !== installedVersion) return { id: 'package-version', status: 'error',
11
- message: `当前 CLI 包为 ${cliVersion},固定服务为 ${installedVersion || '未确认'};本次包尚未完整部署。安装脚本可能未执行或执行失败,不能仅凭 npm 警告判断。请查看 install.log;确认允许部署并中断相关任务后,执行 relax setup,再运行 relax doctor。无需放开全局 npm 脚本权限。` };
12
- return { id: 'package-version', status: 'ok', message: `CLI 与固定服务版本一致:${cliVersion}。这不单独证明本次 postinstall 执行过;当前可用性以下方服务和内核检查为准。` };
13
- }
14
-
15
- export async function doctor({ paths, host, service, skillTarget, ensureAutostart, cliVersion, diagnostics = false }) {
16
- const checks = [];
17
- let installedVersion;
18
- let kernelUpdate = null;
19
- let desktopRecovery = null;
20
- let serviceState = null;
21
- const add = (id, status, message) => checks.push({ id, status, message });
22
- const check = async (id, task) => {
23
- try { await task(); } catch (error) { add(id, 'error', error.message); }
24
- };
25
- await check('installation', async () => {
26
- const record = readRecord(path.join(paths.serviceRoot, 'installation.json'));
27
- if (!record || !fs.existsSync(path.join(paths.serviceRoot, 'dist/bin/codex-remote.mjs'))) throw Error('尚未完整部署服务;安装脚本可能未执行或执行失败。请查看 install.log,确认部署后执行 relax setup,再运行 relax doctor;不能仅凭 npm 警告判断原因。');
28
- if (!record.nodeExecutable || !fs.existsSync(record.nodeExecutable)) throw Error('记录的 Node 已失效,请用可用的 Node 重新安装 Code Relax。');
29
- installedVersion = record.version;
30
- add('installation', 'ok', `已安装版本:${record.version};Node:${record.nodeExecutable}`);
31
- });
32
- checks.push(packageDeploymentCheck(cliVersion, installedVersion));
33
- await check('bridge-runtime', async () => {
34
- await host.bridgeEnvironment();
35
- add('bridge-runtime', 'ok', '桌面服务启动环境正常。');
36
- });
37
- await check('network', async () => {
38
- const address = await host.lanAddress();
39
- if (!address) { add('network', 'warning', '未连接局域网;需要局域网连接时,请接入 Wi-Fi 或有线网络。'); return; }
40
- add('network', 'ok', `局域网地址:${address}`);
41
- });
42
- await check('autostart', async () => {
43
- const state = await ensureAutostart();
44
- add('autostart', 'ok', `当前用户登录自启:${state.repaired ? '已自动恢复' : '已启用'}。`);
45
- });
46
- await check('service', async () => {
47
- const state = await service.status();
48
- serviceState = state;
49
- if (state.keepAwake) {
50
- const power = state.keepAwake;
51
- add('keep-awake', power.enabled && !power.active ? 'warning' : 'ok',
52
- power.active ? '保持唤醒已生效,允许屏幕熄灭。' : !power.enabled ? '保持唤醒已由 CLI 关闭。'
53
- : `保持唤醒未生效:${power.error || '未知原因'};可执行 relax keep-awake on 重新申请。`);
54
- }
55
- add('service', state.running ? 'ok' : 'error', state.running
56
- ? `本机服务正常:${state.mode},端口 ${state.port},运行版本:${state.version || '未知'}` : '服务未就绪;安装后执行 relax start。');
57
- if (state.running && installedVersion && state.version !== installedVersion) add('service-version', 'error', state.version
58
- ? `已安装版本 ${installedVersion},但当前运行版本为 ${state.version};请重新执行安装或重启工作站服务。`
59
- : `已安装版本 ${installedVersion},但无法确认运行服务版本;请保留诊断结果核查,不能据此认定旧进程或自动重启。`);
60
- if (state.running) add('execution', state.execution?.ready === true && state.execution?.canWrite === true ? 'ok' : 'error',
61
- state.execution?.ready === true && state.execution?.canWrite === true
62
- ? 'Codex 完整控制通道已就绪;单个对话的写入权限需在打开该对话时确认。' : 'Codex 完整控制尚未确认就绪且可写;请查看内核和连接诊断,不能仅凭服务在线判定健康。');
63
- if (state.running && state.desktop) {
64
- const descriptions = {
65
- managed: '桌面端已接入 Code Relax 内核。',
66
- ordinary: '桌面端正在独立运行,未接入 Code Relax 内核;若同一对话报写入冲突,请核对桌面端占用。',
67
- stopped: '桌面端未启动;手机可独立使用已就绪的内核。',
68
- unsupported: '当前平台未提供桌面端接入。',
69
- };
70
- add('desktop', 'ok', state.desktop.installed === false && state.desktop.state !== 'unsupported'
71
- ? '未安装桌面端;手机可独立使用已就绪的内核。'
72
- : descriptions[state.desktop.state] || '桌面端接入状态暂未确认。');
73
- }
74
- const currentVersion = state.managedAppServer?.version || null;
75
- const selectedVersion = state.managedAppServer?.selectedVersion || null;
76
- if (state.managedAppServer) kernelUpdate = { pending: state.managedAppServer.updatePending === true,
77
- currentVersion, selectedVersion,
78
- command: state.managedAppServer.updatePending ? 'relax kernel-restart --confirm' : null };
79
- if (state.running) add('managed-app-server', state.managedAppServer?.state === 'ready' ? 'ok' : 'error',
80
- state.managedAppServer?.state === 'ready' ? `Codex 内核已就绪:版本 ${currentVersion || '未知'}。`
81
- : `${state.managedAppServer?.error || 'Codex 内核尚未就绪。'} 请执行 relax setup 修复;若提示未安装或未登录 Codex,请先完成安装和登录。`);
82
- if (state.managedAppServer?.updatePending) {
83
- const versionDetail = currentVersion && selectedVersion && currentVersion === selectedVersion
84
- ? `内核版本号未变(${currentVersion}),重启后将使用更新后的安装文件`
85
- : `当前内核 ${currentVersion || '未知'},待应用 ${selectedVersion || '未知'}`;
86
- add('managed-app-server-update', 'warning',
87
- `Codex 更新待应用:${versionDetail};执行 relax kernel-restart --confirm 应用更新。`);
88
- }
89
- if (state.managedAppServer?.launcherRestartRequired) add('managed-app-server-launcher', 'warning',
90
- '工作站更新尚未完全生效,请执行 relax setup。');
91
- });
92
- if (diagnostics) {
93
- try { desktopRecovery = await recoveryStatus(paths.stateRoot); }
94
- catch (error) { desktopRecovery = { status: 'unknown', error: error.message }; }
95
- }
96
- await check('skill', async () => {
97
- const state = inspectSkill(skillTarget);
98
- add('skill', state.managed ? 'ok' : 'warning', state.managed ? '本包管理的 Skill 已安装。'
99
- : state.installed ? '存在同名自定义 Skill,本包不会覆盖。' : 'Skill 未安装;重新执行 relax setup。');
100
- });
101
- return { ready: !checks.some(item => item.status === 'error'), kernelUpdate, checks, ...(diagnostics ? { desktopRecovery, diagnostics: {
102
- collectedAt: new Date().toISOString(), platform: process.platform, arch: process.arch, osRelease: os.release(),
103
- nodeVersion: process.version, nodeExecutable: process.execPath, serviceRoot: paths.serviceRoot,
104
- servicePid: serviceState?.pid || null, port: serviceState?.port || null, serviceVersion: serviceState?.version || null,
105
- kernel: serviceState?.managedAppServer ? {
106
- version: serviceState.managedAppServer.version || null,
107
- executable: serviceState.managedAppServer.executable || null,
108
- selectedVersion: serviceState.managedAppServer.selectedVersion || null,
109
- preferredExecutable: serviceState.managedAppServer.preferredExecutable || null,
110
- selectedSource: serviceState.managedAppServer.source || null,
111
- } : null,
112
- } } : {}) };
113
- }
114
-
115
- export function formatDoctorOutput(result, detail = false) {
116
- const labels = { 'keep-awake': '保持唤醒', installation: '安装文件', 'bridge-runtime': '运行环境', network: '局域网',
117
- autostart: '登录自启动', service: '工作站服务', 'managed-app-server': 'Codex 内核',
118
- execution: '完整控制', desktop: 'Codex 桌面端', 'package-version': '安装包版本', 'service-version': '服务版本', 'managed-app-server-update': 'Codex 更新',
119
- 'managed-app-server-launcher': '工作站更新', skill: 'Codex Skill' };
120
- const byId = id => result.checks.find(item => item.id === id);
121
- const summary = (id, success) => {
122
- const item = byId(id);
123
- return `${labels[id]}:${!item ? '未检查(工作站服务尚未启动)' : item.status === 'ok' ? success : '异常,见下方处理方法'}`;
124
- };
125
- const issues = result.checks.filter(item => item.status !== 'ok');
126
- const lines = [result.ready ? '工作站检查通过。' : '工作站检查发现问题。',
127
- summary('installation', '完整'), summary('package-version', 'CLI 与固定服务一致'), summary('service', '正在运行'), summary('managed-app-server', '已就绪'), summary('execution', '通道已就绪(不代表每个对话均可写)')];
128
- if (issues.length) lines.push('', '需要处理或留意:', ...issues.map(item => `- ${labels[item.id] || item.id}:${item.message}`));
129
- if (detail) {
130
- const info = result.diagnostics || {};
131
- lines.unshift('Code Relax 诊断报告(供客服或技术人员排查)', `采集时间:${info.collectedAt || '未知'}`,
132
- `系统:${info.platform || process.platform} ${info.arch || process.arch} ${info.osRelease || ''}`,
133
- `Node:${info.nodeVersion || process.version};${info.nodeExecutable || process.execPath}`,
134
- `服务目录:${info.serviceRoot || '未知'}`, `服务进程:${info.servicePid || '未运行'};端口:${info.port || '未知'}`, '');
135
- lines.push('', '检查详情:', ...result.checks.filter(item => item.status === 'ok').map(item => `- ${labels[item.id] || item.id}:${item.message}`));
136
- if (info.kernel) lines.push('',
137
- `运行内核:${info.kernel.version || '未知'};${info.kernel.executable || '路径未知'}`,
138
- `候选内核:${info.kernel.selectedVersion || '未知'};来源 ${info.kernel.selectedSource || '未知'};${info.kernel.preferredExecutable || '路径未知'}`);
139
- const recovery = result.desktopRecovery;
140
- if (recovery && !['none', 'unsupported'].includes(recovery.status)) {
141
- const status = { failed: '未完成', completed: '已完成', succeeded: '已完成', running: '进行中', unknown: '无法读取' }[recovery.status] || recovery.status;
142
- lines.push('', `Desktop 接入排障记录(仅用于排查上次接入,不代表当前服务状态):上次 Codex Desktop 接入${status}。`);
143
- if (recovery.error) lines.push(`记录原因:${recovery.error}`);
144
- }
145
- }
146
- lines.push('', result.ready ? '添加手机:运行 relax pair 生成配对二维码。' : '按上述方法处理后,再运行 relax doctor。');
147
- return lines.join('\n');
148
- }
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import { readRecord } from './service-lifecycle.mjs';
5
+ import { inspectSkill } from './skill-install.mjs';
6
+ import { recoveryStatus } from './shared-recovery.mjs';
7
+
8
+ export function packageDeploymentCheck(cliVersion, installedVersion) {
9
+ if (!cliVersion) return { id: 'package-version', status: 'warning', message: '无法读取当前 CLI 包版本,未确认本次部署版本。' };
10
+ if (cliVersion !== installedVersion) return { id: 'package-version', status: 'error',
11
+ message: `当前 CLI 包为 ${cliVersion},固定服务为 ${installedVersion || '未确认'};本次包尚未完整部署。安装脚本可能未执行或执行失败,不能仅凭 npm 警告判断。请查看 install.log;确认允许部署并中断相关任务后,执行 relax setup,再运行 relax doctor。无需放开全局 npm 脚本权限。` };
12
+ return { id: 'package-version', status: 'ok', message: `CLI 与固定服务版本一致:${cliVersion}。这不单独证明本次 postinstall 执行过;当前可用性以下方服务和内核检查为准。` };
13
+ }
14
+
15
+ export async function doctor({ paths, host, service, skillTarget, ensureAutostart, cliVersion, diagnostics = false }) {
16
+ const checks = [];
17
+ let installedVersion;
18
+ let kernelUpdate = null;
19
+ let desktopRecovery = null;
20
+ let serviceState = null;
21
+ const add = (id, status, message) => checks.push({ id, status, message });
22
+ const check = async (id, task) => {
23
+ try { await task(); } catch (error) { add(id, 'error', error.message); }
24
+ };
25
+ await check('installation', async () => {
26
+ const record = readRecord(path.join(paths.serviceRoot, 'installation.json'));
27
+ if (!record || !fs.existsSync(path.join(paths.serviceRoot, 'dist/bin/codex-remote.mjs'))) throw Error('尚未完整部署服务;安装脚本可能未执行或执行失败。请查看 install.log,确认部署后执行 relax setup,再运行 relax doctor;不能仅凭 npm 警告判断原因。');
28
+ if (!record.nodeExecutable || !fs.existsSync(record.nodeExecutable)) throw Error('记录的 Node 已失效,请用可用的 Node 重新安装 Code Relax。');
29
+ installedVersion = record.version;
30
+ add('installation', 'ok', `已安装版本:${record.version};Node:${record.nodeExecutable}`);
31
+ });
32
+ checks.push(packageDeploymentCheck(cliVersion, installedVersion));
33
+ await check('bridge-runtime', async () => {
34
+ await host.bridgeEnvironment();
35
+ add('bridge-runtime', 'ok', '桌面服务启动环境正常。');
36
+ });
37
+ await check('network', async () => {
38
+ const address = await host.lanAddress();
39
+ if (!address) { add('network', 'warning', '未连接局域网;需要局域网连接时,请接入 Wi-Fi 或有线网络。'); return; }
40
+ add('network', 'ok', `局域网地址:${address}`);
41
+ });
42
+ await check('autostart', async () => {
43
+ const state = await ensureAutostart();
44
+ add('autostart', 'ok', `当前用户登录自启:${state.repaired ? '已自动恢复' : '已启用'}。`);
45
+ });
46
+ await check('service', async () => {
47
+ const state = await service.status();
48
+ serviceState = state;
49
+ if (state.keepAwake) {
50
+ const power = state.keepAwake;
51
+ add('keep-awake', power.enabled && !power.active ? 'warning' : 'ok',
52
+ power.active ? '保持唤醒已生效,允许屏幕熄灭。' : !power.enabled ? '保持唤醒已由 CLI 关闭。'
53
+ : `保持唤醒未生效:${power.error || '未知原因'};可执行 relax keep-awake on 重新申请。`);
54
+ }
55
+ add('service', state.running ? 'ok' : 'error', state.running
56
+ ? `本机服务正常:${state.mode},端口 ${state.port},运行版本:${state.version || '未知'}` : '服务未就绪;安装后执行 relax start。');
57
+ if (state.running && installedVersion && state.version !== installedVersion) add('service-version', 'error', state.version
58
+ ? `已安装版本 ${installedVersion},但当前运行版本为 ${state.version};请重新执行安装或重启工作站服务。`
59
+ : `已安装版本 ${installedVersion},但无法确认运行服务版本;请保留诊断结果核查,不能据此认定旧进程或自动重启。`);
60
+ if (state.running) add('execution', state.execution?.ready === true && state.execution?.canWrite === true ? 'ok' : 'error',
61
+ state.execution?.ready === true && state.execution?.canWrite === true
62
+ ? 'Codex 完整控制通道已就绪;单个对话的写入权限需在打开该对话时确认。' : 'Codex 完整控制尚未确认就绪且可写;请查看内核和连接诊断,不能仅凭服务在线判定健康。');
63
+ if (state.running && state.desktop) {
64
+ const descriptions = {
65
+ managed: '桌面端已接入 Code Relax 内核。',
66
+ ordinary: '桌面端正在独立运行,未接入 Code Relax 内核;若同一对话报写入冲突,请核对桌面端占用。',
67
+ stopped: '桌面端未启动;手机可独立使用已就绪的内核。',
68
+ unsupported: '当前平台未提供桌面端接入。',
69
+ };
70
+ add('desktop', 'ok', state.desktop.installed === false && state.desktop.state !== 'unsupported'
71
+ ? '未安装桌面端;手机可独立使用已就绪的内核。'
72
+ : descriptions[state.desktop.state] || '桌面端接入状态暂未确认。');
73
+ }
74
+ const currentVersion = state.managedAppServer?.version || null;
75
+ const selectedVersion = state.managedAppServer?.selectedVersion || null;
76
+ if (state.managedAppServer) kernelUpdate = { pending: state.managedAppServer.updatePending === true,
77
+ currentVersion, selectedVersion,
78
+ command: state.managedAppServer.updatePending ? 'relax kernel-restart --confirm' : null };
79
+ if (state.running) add('managed-app-server', state.managedAppServer?.state === 'ready' ? 'ok' : 'error',
80
+ state.managedAppServer?.state === 'ready' ? `Codex 内核已就绪:版本 ${currentVersion || '未知'}。`
81
+ : `${state.managedAppServer?.error || 'Codex 内核尚未就绪。'} 请执行 relax setup 修复;若提示未安装或未登录 Codex,请先完成安装和登录。`);
82
+ if (state.managedAppServer?.updatePending) {
83
+ const versionDetail = currentVersion && selectedVersion && currentVersion === selectedVersion
84
+ ? `内核版本号未变(${currentVersion}),重启后将使用更新后的安装文件`
85
+ : `当前内核 ${currentVersion || '未知'},待应用 ${selectedVersion || '未知'}`;
86
+ add('managed-app-server-update', 'warning',
87
+ `Codex 更新待应用:${versionDetail};执行 relax kernel-restart --confirm 应用更新。`);
88
+ }
89
+ if (state.managedAppServer?.launcherRestartRequired) add('managed-app-server-launcher', 'warning',
90
+ '工作站更新尚未完全生效,请执行 relax setup。');
91
+ });
92
+ if (diagnostics) {
93
+ try { desktopRecovery = await recoveryStatus(paths.stateRoot); }
94
+ catch (error) { desktopRecovery = { status: 'unknown', error: error.message }; }
95
+ }
96
+ await check('skill', async () => {
97
+ const state = inspectSkill(skillTarget);
98
+ add('skill', state.managed ? 'ok' : 'warning', state.managed ? '本包管理的 Skill 已安装。'
99
+ : state.installed ? '存在同名自定义 Skill,本包不会覆盖。' : 'Skill 未安装;重新执行 relax setup。');
100
+ });
101
+ return { ready: !checks.some(item => item.status === 'error'), kernelUpdate, checks, ...(diagnostics ? { desktopRecovery, diagnostics: {
102
+ collectedAt: new Date().toISOString(), platform: process.platform, arch: process.arch, osRelease: os.release(),
103
+ nodeVersion: process.version, nodeExecutable: process.execPath, serviceRoot: paths.serviceRoot,
104
+ servicePid: serviceState?.pid || null, port: serviceState?.port || null, serviceVersion: serviceState?.version || null,
105
+ kernel: serviceState?.managedAppServer ? {
106
+ version: serviceState.managedAppServer.version || null,
107
+ executable: serviceState.managedAppServer.executable || null,
108
+ selectedVersion: serviceState.managedAppServer.selectedVersion || null,
109
+ preferredExecutable: serviceState.managedAppServer.preferredExecutable || null,
110
+ selectedSource: serviceState.managedAppServer.source || null,
111
+ } : null,
112
+ } } : {}) };
113
+ }
114
+
115
+ export function formatDoctorOutput(result, detail = false) {
116
+ const labels = { 'keep-awake': '保持唤醒', installation: '安装文件', 'bridge-runtime': '运行环境', network: '局域网',
117
+ autostart: '登录自启动', service: '工作站服务', 'managed-app-server': 'Codex 内核',
118
+ execution: '完整控制', desktop: 'Codex 桌面端', 'package-version': '安装包版本', 'service-version': '服务版本', 'managed-app-server-update': 'Codex 更新',
119
+ 'managed-app-server-launcher': '工作站更新', skill: 'Codex Skill' };
120
+ const byId = id => result.checks.find(item => item.id === id);
121
+ const summary = (id, success) => {
122
+ const item = byId(id);
123
+ return `${labels[id]}:${!item ? '未检查(工作站服务尚未启动)' : item.status === 'ok' ? success : '异常,见下方处理方法'}`;
124
+ };
125
+ const issues = result.checks.filter(item => item.status !== 'ok');
126
+ const lines = [result.ready ? '工作站检查通过。' : '工作站检查发现问题。',
127
+ summary('installation', '完整'), summary('package-version', 'CLI 与固定服务一致'), summary('service', '正在运行'), summary('managed-app-server', '已就绪'), summary('execution', '通道已就绪(不代表每个对话均可写)')];
128
+ if (issues.length) lines.push('', '需要处理或留意:', ...issues.map(item => `- ${labels[item.id] || item.id}:${item.message}`));
129
+ if (detail) {
130
+ const info = result.diagnostics || {};
131
+ lines.unshift('Code Relax 诊断报告(供客服或技术人员排查)', `采集时间:${info.collectedAt || '未知'}`,
132
+ `系统:${info.platform || process.platform} ${info.arch || process.arch} ${info.osRelease || ''}`,
133
+ `Node:${info.nodeVersion || process.version};${info.nodeExecutable || process.execPath}`,
134
+ `服务目录:${info.serviceRoot || '未知'}`, `服务进程:${info.servicePid || '未运行'};端口:${info.port || '未知'}`, '');
135
+ lines.push('', '检查详情:', ...result.checks.filter(item => item.status === 'ok').map(item => `- ${labels[item.id] || item.id}:${item.message}`));
136
+ if (info.kernel) lines.push('',
137
+ `运行内核:${info.kernel.version || '未知'};${info.kernel.executable || '路径未知'}`,
138
+ `候选内核:${info.kernel.selectedVersion || '未知'};来源 ${info.kernel.selectedSource || '未知'};${info.kernel.preferredExecutable || '路径未知'}`);
139
+ const recovery = result.desktopRecovery;
140
+ if (recovery && !['none', 'unsupported'].includes(recovery.status)) {
141
+ const status = { failed: '未完成', completed: '已完成', succeeded: '已完成', running: '进行中', unknown: '无法读取' }[recovery.status] || recovery.status;
142
+ lines.push('', `Desktop 接入排障记录(仅用于排查上次接入,不代表当前服务状态):上次 Codex Desktop 接入${status}。`);
143
+ if (recovery.error) lines.push(`记录原因:${recovery.error}`);
144
+ }
145
+ }
146
+ lines.push('', result.ready ? '添加手机:运行 relax pair 生成配对二维码。' : '按上述方法处理后,再运行 relax doctor。');
147
+ return lines.join('\n');
148
+ }
@@ -1,97 +1,97 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { randomUUID } from 'node:crypto';
4
- import { execFile } from 'node:child_process';
5
- import { promisify } from 'node:util';
6
- import { readRecord, writeRecord } from './service-lifecycle.mjs';
7
- import { assertPlainPath } from './skill-install.mjs';
8
-
9
- const exec = promisify(execFile);
10
- export function packageRoot(from) {
11
- let directory = path.resolve(from);
12
- while (true) {
13
- if (readRecord(path.join(directory, 'package.json'))?.name === '@walkhi/code-relax') return directory;
14
- const parent = path.dirname(directory);
15
- if (parent === directory) throw new Error('找不到 Code Relax 安装包。');
16
- directory = parent;
17
- }
18
- }
19
-
20
- export async function installService(source, target, executable, stateRoot, hooks = {}) {
21
- assertPlainPath(target);
22
- if (!fs.existsSync(path.join(source, 'dist/bin/codex-remote.mjs'))) throw new Error('缺少发布构建;源码目录请先执行 npm run build。');
23
- const build = readRecord(path.join(source, 'dist/build-platform.json'));
24
- if (build?.platform !== process.platform || build?.arch !== process.arch)
25
- throw new Error('构建产物与本机系统或 CPU 不匹配,请在目标工作站安装依赖并重新构建。');
26
- if (!fs.existsSync(path.join(source, 'node_modules'))) throw new Error('缺少包依赖,请重新安装完整 npm 包。');
27
- const parent = path.dirname(target);
28
- fs.mkdirSync(parent, { recursive: true });
29
- const stage = path.join(parent, `service-stage-${randomUUID()}`);
30
- const backup = path.join(parent, `service-backup-${randomUUID()}`);
31
- fs.mkdirSync(stage);
32
- try {
33
- for (const name of ['dist', 'node_modules', 'package.json', 'README.md']) {
34
- fs.cpSync(path.join(source, name), path.join(stage, name), { recursive: true });
35
- }
36
- await exec(executable, [path.join(stage, 'dist/bin/codex-remote.mjs'), '--help'], { windowsHide: true });
37
- await exec(executable, ['-e', 'const r=require("node:module").createRequire(process.argv[1]); r("sharp"); r("qrcode"); r("ws"); r("node-datachannel");', path.join(stage, 'package.json')], { windowsHide: true });
38
- const previousInstallation = readRecord(path.join(target, 'installation.json'));
39
- const npmCliPath = process.env.npm_execpath || previousInstallation?.npmCliPath;
40
- const npmPrefix = process.env.npm_config_prefix || previousInstallation?.npmPrefix;
41
- writeRecord(path.join(stage, 'installation.json'), {
42
- npmCliPath: npmCliPath && path.isAbsolute(npmCliPath) && fs.existsSync(npmCliPath) ? fs.realpathSync(npmCliPath) : null,
43
- npmPrefix: npmPrefix && path.isAbsolute(npmPrefix) ? npmPrefix : null,
44
- installedAt: new Date().toISOString(), source, nodeExecutable: executable, stateRoot,
45
- version: readRecord(path.join(source, 'package.json')).version });
46
- const previous = fs.existsSync(target);
47
- let previousMoved = false, activated = false;
48
- try {
49
- await hooks.beforeActivate?.();
50
- if (previous) { fs.renameSync(target, backup); previousMoved = true; }
51
- fs.renameSync(stage, target); activated = true;
52
- await hooks.verifyActivation?.({ serviceRoot: target, nodeExecutable: executable,
53
- version: readRecord(path.join(target, 'package.json')).version });
54
- } catch (error) {
55
- try {
56
- if (activated && fs.existsSync(target)) fs.rmSync(target, { recursive: true });
57
- if (previousMoved) fs.renameSync(backup, target);
58
- await hooks.restoreActivation?.();
59
- } catch (restoreError) {
60
- throw new AggregateError([error, restoreError], `服务更新失败,旧版本恢复也未完成:${restoreError.message}`);
61
- }
62
- throw error;
63
- }
64
- if (previous) {
65
- if (path.dirname(backup) !== parent || !path.basename(backup).startsWith('service-backup-')) throw new Error('Invalid backup path');
66
- fs.rmSync(backup, { recursive: true });
67
- }
68
- return { serviceRoot: target, nodeExecutable: executable, version: readRecord(path.join(target, 'package.json')).version };
69
- } finally {
70
- // Only this invocation's generated sibling stage may be cleaned up.
71
- if (path.dirname(stage) !== parent || !path.basename(stage).startsWith('service-stage-')) throw new Error('Invalid stage path');
72
- fs.rmSync(stage, { recursive: true, force: true });
73
- }
74
- }
75
-
76
- export async function uninstallService({ serviceRoot, stateRoot, uploadsRoot, credentialsRoot }, service, beforeRemove = async () => {}) {
77
- const target = path.resolve(serviceRoot);
78
- assertPlainPath(target);
79
- if (path.basename(target) !== 'service' || path.basename(path.dirname(target)) !== 'CodexRemote') throw new Error('拒绝删除未识别的服务目录。');
80
- const preserved = [stateRoot, uploadsRoot, credentialsRoot].filter(Boolean);
81
- for (const data of preserved) {
82
- const relative = path.relative(target, path.resolve(data));
83
- if (!relative || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative))) throw new Error('服务目录包含用户数据,拒绝删除。');
84
- }
85
- if (!fs.existsSync(target)) {
86
- await service.stop();
87
- await beforeRemove();
88
- return { removed: false, serviceRoot: target, preserved };
89
- }
90
- const pkg = readRecord(path.join(target, 'package.json'));
91
- const record = readRecord(path.join(target, 'installation.json'));
92
- if (pkg?.name !== '@walkhi/code-relax' || !record?.installedAt || !record?.nodeExecutable) throw new Error('缺少有效安装记录,未删除服务目录。');
93
- await service.stop();
94
- await beforeRemove();
95
- fs.rmSync(target, { recursive: true });
96
- return { removed: true, serviceRoot: target, preserved };
97
- }
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { randomUUID } from 'node:crypto';
4
+ import { execFile } from 'node:child_process';
5
+ import { promisify } from 'node:util';
6
+ import { readRecord, writeRecord } from './service-lifecycle.mjs';
7
+ import { assertPlainPath } from './skill-install.mjs';
8
+
9
+ const exec = promisify(execFile);
10
+ export function packageRoot(from) {
11
+ let directory = path.resolve(from);
12
+ while (true) {
13
+ if (readRecord(path.join(directory, 'package.json'))?.name === '@walkhi/code-relax') return directory;
14
+ const parent = path.dirname(directory);
15
+ if (parent === directory) throw new Error('找不到 Code Relax 安装包。');
16
+ directory = parent;
17
+ }
18
+ }
19
+
20
+ export async function installService(source, target, executable, stateRoot, hooks = {}) {
21
+ assertPlainPath(target);
22
+ if (!fs.existsSync(path.join(source, 'dist/bin/codex-remote.mjs'))) throw new Error('缺少发布构建;源码目录请先执行 npm run build。');
23
+ const build = readRecord(path.join(source, 'dist/build-platform.json'));
24
+ if (build?.platform !== process.platform || build?.arch !== process.arch)
25
+ throw new Error('构建产物与本机系统或 CPU 不匹配,请在目标工作站安装依赖并重新构建。');
26
+ if (!fs.existsSync(path.join(source, 'node_modules'))) throw new Error('缺少包依赖,请重新安装完整 npm 包。');
27
+ const parent = path.dirname(target);
28
+ fs.mkdirSync(parent, { recursive: true });
29
+ const stage = path.join(parent, `service-stage-${randomUUID()}`);
30
+ const backup = path.join(parent, `service-backup-${randomUUID()}`);
31
+ fs.mkdirSync(stage);
32
+ try {
33
+ for (const name of ['dist', 'node_modules', 'package.json', 'README.md']) {
34
+ fs.cpSync(path.join(source, name), path.join(stage, name), { recursive: true });
35
+ }
36
+ await exec(executable, [path.join(stage, 'dist/bin/codex-remote.mjs'), '--help'], { windowsHide: true });
37
+ await exec(executable, ['-e', 'const r=require("node:module").createRequire(process.argv[1]); r("sharp"); r("qrcode"); r("ws"); r("node-datachannel");', path.join(stage, 'package.json')], { windowsHide: true });
38
+ const previousInstallation = readRecord(path.join(target, 'installation.json'));
39
+ const npmCliPath = process.env.npm_execpath || previousInstallation?.npmCliPath;
40
+ const npmPrefix = process.env.npm_config_prefix || previousInstallation?.npmPrefix;
41
+ writeRecord(path.join(stage, 'installation.json'), {
42
+ npmCliPath: npmCliPath && path.isAbsolute(npmCliPath) && fs.existsSync(npmCliPath) ? fs.realpathSync(npmCliPath) : null,
43
+ npmPrefix: npmPrefix && path.isAbsolute(npmPrefix) ? npmPrefix : null,
44
+ installedAt: new Date().toISOString(), source, nodeExecutable: executable, stateRoot,
45
+ version: readRecord(path.join(source, 'package.json')).version });
46
+ const previous = fs.existsSync(target);
47
+ let previousMoved = false, activated = false;
48
+ try {
49
+ await hooks.beforeActivate?.();
50
+ if (previous) { fs.renameSync(target, backup); previousMoved = true; }
51
+ fs.renameSync(stage, target); activated = true;
52
+ await hooks.verifyActivation?.({ serviceRoot: target, nodeExecutable: executable,
53
+ version: readRecord(path.join(target, 'package.json')).version });
54
+ } catch (error) {
55
+ try {
56
+ if (activated && fs.existsSync(target)) fs.rmSync(target, { recursive: true });
57
+ if (previousMoved) fs.renameSync(backup, target);
58
+ await hooks.restoreActivation?.();
59
+ } catch (restoreError) {
60
+ throw new AggregateError([error, restoreError], `服务更新失败,旧版本恢复也未完成:${restoreError.message}`);
61
+ }
62
+ throw error;
63
+ }
64
+ if (previous) {
65
+ if (path.dirname(backup) !== parent || !path.basename(backup).startsWith('service-backup-')) throw new Error('Invalid backup path');
66
+ fs.rmSync(backup, { recursive: true });
67
+ }
68
+ return { serviceRoot: target, nodeExecutable: executable, version: readRecord(path.join(target, 'package.json')).version };
69
+ } finally {
70
+ // Only this invocation's generated sibling stage may be cleaned up.
71
+ if (path.dirname(stage) !== parent || !path.basename(stage).startsWith('service-stage-')) throw new Error('Invalid stage path');
72
+ fs.rmSync(stage, { recursive: true, force: true });
73
+ }
74
+ }
75
+
76
+ export async function uninstallService({ serviceRoot, stateRoot, uploadsRoot, credentialsRoot }, service, beforeRemove = async () => {}) {
77
+ const target = path.resolve(serviceRoot);
78
+ assertPlainPath(target);
79
+ if (path.basename(target) !== 'service' || path.basename(path.dirname(target)) !== 'CodexRemote') throw new Error('拒绝删除未识别的服务目录。');
80
+ const preserved = [stateRoot, uploadsRoot, credentialsRoot].filter(Boolean);
81
+ for (const data of preserved) {
82
+ const relative = path.relative(target, path.resolve(data));
83
+ if (!relative || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative))) throw new Error('服务目录包含用户数据,拒绝删除。');
84
+ }
85
+ if (!fs.existsSync(target)) {
86
+ await service.stop();
87
+ await beforeRemove();
88
+ return { removed: false, serviceRoot: target, preserved };
89
+ }
90
+ const pkg = readRecord(path.join(target, 'package.json'));
91
+ const record = readRecord(path.join(target, 'installation.json'));
92
+ if (pkg?.name !== '@walkhi/code-relax' || !record?.installedAt || !record?.nodeExecutable) throw new Error('缺少有效安装记录,未删除服务目录。');
93
+ await service.stop();
94
+ await beforeRemove();
95
+ fs.rmSync(target, { recursive: true });
96
+ return { removed: true, serviceRoot: target, preserved };
97
+ }