@walkhi/code-relax 0.1.0-beta.30 → 0.1.0-beta.32

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 (187) hide show
  1. package/README.md +95 -89
  2. package/dist/bin/cleanup-accounts.mjs +63 -63
  3. package/dist/bin/codex-remote.mjs +374 -367
  4. package/dist/bin/manage-relay-admin.mjs +20 -20
  5. package/dist/bin/migrate-relay-db.mjs +109 -109
  6. package/dist/bin/publish-android-release.mjs +63 -58
  7. package/dist/bin/publish-harmony-release.mjs +46 -0
  8. package/dist/bin/release-upload-server.mjs +174 -0
  9. package/dist/bin/self-relay-admin.mjs +8 -8
  10. package/dist/bin/self-relay-demo.mjs +33 -33
  11. package/dist/bin/self-relay-server.mjs +66 -66
  12. package/dist/build-platform.json +1 -1
  13. package/dist/get-lan-networks.ps1 +40 -40
  14. package/dist/get-shared-service-url.ps1 +6 -6
  15. package/dist/shared/app-server-events.cjs +422 -412
  16. package/dist/shared/code-browser.cjs +159 -159
  17. package/dist/shared/file-changes.cjs +31 -31
  18. package/dist/shared/file-citations.cjs +117 -117
  19. package/dist/shared/file-mentions.cjs +92 -92
  20. package/dist/shared/message-delivery.cjs +54 -54
  21. package/dist/shared/p2p-data-channel.cjs +72 -72
  22. package/dist/shared/p2p-diagnostics.cjs +28 -28
  23. package/dist/shared/p2p-file-channel.cjs +78 -74
  24. package/dist/shared/plan-pricing.cjs +32 -32
  25. package/dist/shared/question-attention.cjs +69 -0
  26. package/dist/shared/server-requests.cjs +219 -219
  27. package/dist/shared/timeline-sync.cjs +173 -173
  28. package/dist/skills/code-relax/SKILL.md +22 -22
  29. package/dist/src/archived-threads.mjs +22 -0
  30. package/dist/src/background-process.mjs +18 -18
  31. package/dist/src/bounded-process-log.mjs +63 -63
  32. package/dist/src/celsius-client.mjs +107 -107
  33. package/dist/src/code-browser.mjs +100 -100
  34. package/dist/src/desktop-launcher.mjs +46 -46
  35. package/dist/src/dots-approval-timeline.mjs +99 -99
  36. package/dist/src/dots-avatar.mjs +42 -42
  37. package/dist/src/dots-chat-projection.mjs +50 -50
  38. package/dist/src/dots-chat-server.mjs +461 -461
  39. package/dist/src/dots-message-images.mjs +319 -319
  40. package/dist/src/dots-profile.mjs +77 -77
  41. package/dist/src/dots-room-activity.mjs +94 -94
  42. package/dist/src/dots-room-client.mjs +135 -135
  43. package/dist/src/dots-room-projection.mjs +94 -94
  44. package/dist/src/dots-schedules.mjs +66 -66
  45. package/dist/src/dots-summary-tasks.mjs +32 -32
  46. package/dist/src/dots-task-catalog.mjs +430 -430
  47. package/dist/src/dots-task-files.mjs +52 -52
  48. package/dist/src/execution-health.mjs +26 -26
  49. package/dist/src/file-transfer-range.mjs +22 -22
  50. package/dist/src/file-upload-session.mjs +130 -130
  51. package/dist/src/hosted-history-app-server.mjs +103 -103
  52. package/dist/src/hosted-history-cache.mjs +113 -113
  53. package/dist/src/lan-socket-server.mjs +265 -263
  54. package/dist/src/managed-app-server.mjs +244 -244
  55. package/dist/src/message-images.mjs +9 -9
  56. package/dist/src/message-upload-files.mjs +74 -74
  57. package/dist/src/onboarding.mjs +12 -12
  58. package/dist/src/plan-pricing.mjs +47 -47
  59. package/dist/src/platform/README.md +12 -12
  60. package/dist/src/platform/codex-protocol.mjs +46 -46
  61. package/dist/src/platform/codex-runtime.mjs +3 -3
  62. package/dist/src/platform/credential-store.mjs +3 -3
  63. package/dist/src/platform/desktop-host.mjs +3 -3
  64. package/dist/src/platform/desktop-project-registration.mjs +3 -3
  65. package/dist/src/platform/desktop-project-store.mjs +208 -149
  66. package/dist/src/platform/file-opener.mjs +9 -0
  67. package/dist/src/platform/linux/README.md +3 -3
  68. package/dist/src/platform/linux/codex-runtime.mjs +56 -0
  69. package/dist/src/platform/linux/credential-store.mjs +28 -0
  70. package/dist/src/platform/linux/desktop-host.mjs +83 -0
  71. package/dist/src/platform/linux/desktop-installation.mjs +36 -0
  72. package/dist/src/platform/linux/desktop-project-registration.mjs +11 -0
  73. package/dist/src/platform/linux/network-info.mjs +11 -0
  74. package/dist/src/platform/linux/process.mjs +42 -0
  75. package/dist/src/platform/linux/project-locations.mjs +17 -0
  76. package/dist/src/platform/linux/resident-startup.mjs +52 -0
  77. package/dist/src/platform/linux/retained-runtime.mjs +76 -0
  78. package/dist/src/platform/linux/service-host.mjs +18 -0
  79. package/dist/src/platform/linux/system.mjs +49 -0
  80. package/dist/src/platform/linux/user-environment.mjs +7 -0
  81. package/dist/src/platform/macos/README.md +3 -3
  82. package/dist/src/platform/macos/codex-runtime.mjs +125 -125
  83. package/dist/src/platform/macos/credential-store.mjs +10 -10
  84. package/dist/src/platform/macos/desktop-host.mjs +82 -82
  85. package/dist/src/platform/macos/desktop-project-registration.mjs +11 -10
  86. package/dist/src/platform/macos/network-info.mjs +8 -28
  87. package/dist/src/platform/macos/project-locations.mjs +8 -8
  88. package/dist/src/platform/macos/resident-startup.mjs +84 -84
  89. package/dist/src/platform/macos/retained-runtime.mjs +127 -127
  90. package/dist/src/platform/macos/service-host.mjs +18 -18
  91. package/dist/src/platform/macos/system.mjs +29 -29
  92. package/dist/src/platform/macos/user-environment.mjs +7 -7
  93. package/dist/src/platform/native-package.mjs +58 -46
  94. package/dist/src/platform/network-info.mjs +3 -3
  95. package/dist/src/platform/path-identity.mjs +5 -5
  96. package/dist/src/platform/posix/network-candidates.mjs +23 -0
  97. package/dist/src/platform/posix/runtime-paths.mjs +25 -25
  98. package/dist/src/platform/project-locations.mjs +3 -3
  99. package/dist/src/platform/runtime-paths.mjs +8 -8
  100. package/dist/src/platform/select.mjs +5 -5
  101. package/dist/src/platform/service-host.mjs +3 -3
  102. package/dist/src/platform/user-environment.mjs +3 -3
  103. package/dist/src/platform/windows/IsolatedProcess.cs +79 -79
  104. package/dist/src/platform/windows/activate-desktop.ps1 +48 -48
  105. package/dist/src/platform/windows/background-process.mjs +30 -30
  106. package/dist/src/platform/windows/codex-runtime.mjs +215 -215
  107. package/dist/src/platform/windows/credential-store.mjs +27 -27
  108. package/dist/src/platform/windows/credential-store.ps1 +49 -49
  109. package/dist/src/platform/windows/desktop-helper.mjs +179 -179
  110. package/dist/src/platform/windows/desktop-host.mjs +58 -58
  111. package/dist/src/platform/windows/desktop-info.ps1 +14 -14
  112. package/dist/src/platform/windows/desktop-project-registration.mjs +4 -4
  113. package/dist/src/platform/windows/desktop-runtime.ps1 +10 -10
  114. package/dist/src/platform/windows/desktop-shortcuts.ps1 +21 -21
  115. package/dist/src/platform/windows/dots-profile.mjs +49 -49
  116. package/dist/src/platform/windows/launch-worker.mjs +41 -41
  117. package/dist/src/platform/windows/network-info.mjs +34 -34
  118. package/dist/src/platform/windows/project-locations.mjs +16 -16
  119. package/dist/src/platform/windows/resident-start.js +20 -20
  120. package/dist/src/platform/windows/resident-start.ps1 +7 -7
  121. package/dist/src/platform/windows/resident-startup.ps1 +83 -83
  122. package/dist/src/platform/windows/resolve-state-root.ps1 +14 -14
  123. package/dist/src/platform/windows/runtime-paths.mjs +20 -20
  124. package/dist/src/platform/windows/rustdesk-video-probe.mjs +167 -167
  125. package/dist/src/platform/windows/service-host.mjs +31 -31
  126. package/dist/src/platform/windows/start-hidden-console.ps1 +49 -49
  127. package/dist/src/platform/windows/stop-desktop.ps1 +22 -22
  128. package/dist/src/platform/windows/user-environment.mjs +50 -50
  129. package/dist/src/platform/windows/user-environment.ps1 +41 -41
  130. package/dist/src/project-archive.mjs +30 -0
  131. package/dist/src/project-create.mjs +121 -119
  132. package/dist/src/project-management.mjs +59 -69
  133. package/dist/src/project-remove.mjs +55 -0
  134. package/dist/src/project-update.mjs +56 -0
  135. package/dist/src/runtime-discovery.mjs +47 -47
  136. package/dist/src/self-relay/account-cleanup.mjs +30 -30
  137. package/dist/src/self-relay/account-connections.mjs +52 -52
  138. package/dist/src/self-relay/admin-cloud.mjs +101 -101
  139. package/dist/src/self-relay/admin-page.mjs +80 -80
  140. package/dist/src/self-relay/admin-server.mjs +74 -74
  141. package/dist/src/self-relay/admin-state.mjs +127 -127
  142. package/dist/src/self-relay/admin-style.mjs +14 -14
  143. package/dist/src/self-relay/android-releases.mjs +61 -61
  144. package/dist/src/self-relay/client.mjs +61 -61
  145. package/dist/src/self-relay/connector.mjs +439 -438
  146. package/dist/src/self-relay/debug-allowlist.mjs +33 -33
  147. package/dist/src/self-relay/demo.mjs +37 -37
  148. package/dist/src/self-relay/device-info.mjs +27 -0
  149. package/dist/src/self-relay/device-store.mjs +92 -84
  150. package/dist/src/self-relay/diagnostics.mjs +54 -54
  151. package/dist/src/self-relay/email-accounts.mjs +183 -183
  152. package/dist/src/self-relay/file-wire.mjs +13 -13
  153. package/dist/src/self-relay/lan-authorizations.mjs +71 -42
  154. package/dist/src/self-relay/lifecycle.mjs +264 -250
  155. package/dist/src/self-relay/long-pairing.mjs +40 -40
  156. package/dist/src/self-relay/mysql-state.mjs +118 -105
  157. package/dist/src/self-relay/p2p-probe.mjs +159 -159
  158. package/dist/src/self-relay/privacy-page.mjs +26 -26
  159. package/dist/src/self-relay/public-releases.mjs +52 -0
  160. package/dist/src/self-relay/rtc-file-bridge.mjs +41 -41
  161. package/dist/src/self-relay/server.mjs +1006 -932
  162. package/dist/src/self-relay/usage-daily.mjs +55 -55
  163. package/dist/src/self-relay/usage-meter.mjs +141 -141
  164. package/dist/src/self-relay/windows-route-helper.mjs +75 -75
  165. package/dist/src/self-relay/wire.mjs +36 -36
  166. package/dist/src/server.mjs +1861 -1778
  167. package/dist/src/service-control.mjs +82 -82
  168. package/dist/src/service-doctor.mjs +110 -110
  169. package/dist/src/service-health.mjs +33 -33
  170. package/dist/src/service-install.mjs +91 -91
  171. package/dist/src/service-lifecycle.mjs +138 -138
  172. package/dist/src/service-restart.mjs +81 -81
  173. package/dist/src/shared-app-server.mjs +615 -611
  174. package/dist/src/shared-catalog.mjs +132 -132
  175. package/dist/src/shared-recovery.mjs +146 -146
  176. package/dist/src/shared-thread-stream.mjs +164 -164
  177. package/dist/src/skill-install.mjs +51 -51
  178. package/dist/src/thread-title-generation.mjs +150 -150
  179. package/dist/src/turn-model-history.mjs +47 -47
  180. package/dist/src/usage-reset.mjs +17 -17
  181. package/dist/src/workstation-downloads.mjs +120 -120
  182. package/dist/tools/rtc-route-helper.ps1 +99 -99
  183. package/dist/tools/start-rtc-route-helper.ps1 +15 -15
  184. package/dist/web/vendor/markdown-it.LICENSE.txt +22 -22
  185. package/dist/web/vendor/markdown-it.umd.min.js +13 -13
  186. package/package.json +7 -6
  187. package/tools/postinstall.mjs +127 -127
@@ -1,82 +1,82 @@
1
- import http from 'node:http';
2
- import { timingSafeEqual } from 'node:crypto';
3
-
4
- function matches(left, right) {
5
- const a = Buffer.from(left || '');
6
- const b = Buffer.from(right || '');
7
- return a.length === b.length && timingSafeEqual(a, b);
8
- }
9
-
10
- // Separate from the LAN listener: mobile credentials never authorize lifecycle operations.
11
- export async function openControl({ instanceId, secret, shutdown, status = () => ({}), actions = {} }) {
12
- const server = http.createServer((request, response) => {
13
- if (!matches(request.headers.authorization, `Bearer ${secret}`)) {
14
- response.writeHead(403).end();
15
- return;
16
- }
17
- const action = Object.keys(actions).find(name => request.url === `/${instanceId}/${name}`);
18
- if ((!action && request.url !== `/${instanceId}`) || !['GET', 'POST'].includes(request.method) || (action && request.method !== 'POST')) {
19
- response.writeHead(404).end();
20
- return;
21
- }
22
- response.setHeader('Content-Type', 'application/json');
23
- response.setHeader('Connection', 'close');
24
- if (action) {
25
- Promise.resolve().then(async () => {
26
- let body = '';
27
- request.setEncoding('utf8');
28
- for await (const chunk of request) {
29
- body += chunk;
30
- if (Buffer.byteLength(body) > 4096) throw new Error('管理请求过大');
31
- }
32
- const details = body ? JSON.parse(body) : {};
33
- if (!details || typeof details !== 'object' || Array.isArray(details)) throw new Error('无效管理请求');
34
- return actions[action](details);
35
- }).then(result => {
36
- response.end(JSON.stringify({ ...result, instanceId, pid: process.pid }));
37
- }).catch(error => { response.writeHead(409).end(JSON.stringify({ error: error.message })); });
38
- return;
39
- }
40
- if (request.method === 'POST') response.once('finish', () => {
41
- Promise.resolve(shutdown()).catch(error => {
42
- console.error('桌面服务关闭失败:', error.message);
43
- process.exitCode = 1;
44
- });
45
- });
46
- response.end(JSON.stringify({ ...status(), instanceId, pid: process.pid }));
47
- });
48
- await new Promise((resolve, reject) => {
49
- server.once('error', reject);
50
- server.listen(0, '127.0.0.1', resolve);
51
- });
52
- return { port: server.address().port, close: () => new Promise((resolve, reject) => {
53
- server.close(error => error ? reject(error) : resolve());
54
- }) };
55
- }
56
-
57
- export function controlRequest(session, method = 'GET', action = '', details) {
58
- return new Promise((resolve, reject) => {
59
- const request = http.request({ hostname: '127.0.0.1', port: session.controlPort,
60
- path: `/${session.instanceId}${action ? '/' + action : ''}`, method, agent: false,
61
- headers: { Authorization: `Bearer ${session.controlSecret}` }, timeout: action ? 25000 : 2000 }, response => {
62
- let body = '';
63
- response.setEncoding('utf8');
64
- response.on('data', chunk => { body += chunk; });
65
- response.on('error', reject);
66
- response.on('end', () => {
67
- try {
68
- if (response.statusCode === 409) throw new Error(JSON.parse(body).error);
69
- if (response.statusCode !== 200) throw new Error('桌面服务管理通道身份验证失败。');
70
- const identity = JSON.parse(body);
71
- if (identity.instanceId !== session.instanceId || identity.pid !== session.pid) {
72
- throw new Error('桌面服务实例身份不匹配。');
73
- }
74
- resolve(identity);
75
- } catch (error) { reject(error); }
76
- });
77
- });
78
- request.on('timeout', () => request.destroy(new Error('桌面服务管理通道响应超时。')));
79
- request.on('error', reject);
80
- request.end(details === undefined ? undefined : JSON.stringify(details));
81
- });
82
- }
1
+ import http from 'node:http';
2
+ import { timingSafeEqual } from 'node:crypto';
3
+
4
+ function matches(left, right) {
5
+ const a = Buffer.from(left || '');
6
+ const b = Buffer.from(right || '');
7
+ return a.length === b.length && timingSafeEqual(a, b);
8
+ }
9
+
10
+ // Separate from the LAN listener: mobile credentials never authorize lifecycle operations.
11
+ export async function openControl({ instanceId, secret, shutdown, status = () => ({}), actions = {} }) {
12
+ const server = http.createServer((request, response) => {
13
+ if (!matches(request.headers.authorization, `Bearer ${secret}`)) {
14
+ response.writeHead(403).end();
15
+ return;
16
+ }
17
+ const action = Object.keys(actions).find(name => request.url === `/${instanceId}/${name}`);
18
+ if ((!action && request.url !== `/${instanceId}`) || !['GET', 'POST'].includes(request.method) || (action && request.method !== 'POST')) {
19
+ response.writeHead(404).end();
20
+ return;
21
+ }
22
+ response.setHeader('Content-Type', 'application/json');
23
+ response.setHeader('Connection', 'close');
24
+ if (action) {
25
+ Promise.resolve().then(async () => {
26
+ let body = '';
27
+ request.setEncoding('utf8');
28
+ for await (const chunk of request) {
29
+ body += chunk;
30
+ if (Buffer.byteLength(body) > 4096) throw new Error('管理请求过大');
31
+ }
32
+ const details = body ? JSON.parse(body) : {};
33
+ if (!details || typeof details !== 'object' || Array.isArray(details)) throw new Error('无效管理请求');
34
+ return actions[action](details);
35
+ }).then(result => {
36
+ response.end(JSON.stringify({ ...result, instanceId, pid: process.pid }));
37
+ }).catch(error => { response.writeHead(409).end(JSON.stringify({ error: error.message })); });
38
+ return;
39
+ }
40
+ if (request.method === 'POST') response.once('finish', () => {
41
+ Promise.resolve(shutdown()).catch(error => {
42
+ console.error('桌面服务关闭失败:', error.message);
43
+ process.exitCode = 1;
44
+ });
45
+ });
46
+ response.end(JSON.stringify({ ...status(), instanceId, pid: process.pid }));
47
+ });
48
+ await new Promise((resolve, reject) => {
49
+ server.once('error', reject);
50
+ server.listen(0, '127.0.0.1', resolve);
51
+ });
52
+ return { port: server.address().port, close: () => new Promise((resolve, reject) => {
53
+ server.close(error => error ? reject(error) : resolve());
54
+ }) };
55
+ }
56
+
57
+ export function controlRequest(session, method = 'GET', action = '', details) {
58
+ return new Promise((resolve, reject) => {
59
+ const request = http.request({ hostname: '127.0.0.1', port: session.controlPort,
60
+ path: `/${session.instanceId}${action ? '/' + action : ''}`, method, agent: false,
61
+ headers: { Authorization: `Bearer ${session.controlSecret}` }, timeout: action ? 25000 : 2000 }, response => {
62
+ let body = '';
63
+ response.setEncoding('utf8');
64
+ response.on('data', chunk => { body += chunk; });
65
+ response.on('error', reject);
66
+ response.on('end', () => {
67
+ try {
68
+ if (response.statusCode === 409) throw new Error(JSON.parse(body).error);
69
+ if (response.statusCode !== 200) throw new Error('桌面服务管理通道身份验证失败。');
70
+ const identity = JSON.parse(body);
71
+ if (identity.instanceId !== session.instanceId || identity.pid !== session.pid) {
72
+ throw new Error('桌面服务实例身份不匹配。');
73
+ }
74
+ resolve(identity);
75
+ } catch (error) { reject(error); }
76
+ });
77
+ });
78
+ request.on('timeout', () => request.destroy(new Error('桌面服务管理通道响应超时。')));
79
+ request.on('error', reject);
80
+ request.end(details === undefined ? undefined : JSON.stringify(details));
81
+ });
82
+ }
@@ -1,110 +1,110 @@
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 async function doctor({ paths, host, service, skillTarget, ensureAutostart, diagnostics = false }) {
9
- const checks = [];
10
- let installedVersion;
11
- let kernelUpdate = null;
12
- let desktopRecovery = null;
13
- let serviceState = null;
14
- const add = (id, status, message) => checks.push({ id, status, message });
15
- const check = async (id, task) => {
16
- try { await task(); } catch (error) { add(id, 'error', error.message); }
17
- };
18
- await check('installation', async () => {
19
- const record = readRecord(path.join(paths.serviceRoot, 'installation.json'));
20
- if (!record || !fs.existsSync(path.join(paths.serviceRoot, 'dist/bin/codex-remote.mjs'))) throw Error('尚未完整部署服务,请重新安装 Code Relax。');
21
- if (!record.nodeExecutable || !fs.existsSync(record.nodeExecutable)) throw Error('记录的 Node 已失效,请用可用的 Node 重新安装 Code Relax。');
22
- installedVersion = record.version;
23
- add('installation', 'ok', `已安装版本:${record.version};Node:${record.nodeExecutable}`);
24
- });
25
- await check('bridge-runtime', async () => {
26
- await host.bridgeEnvironment();
27
- add('bridge-runtime', 'ok', '桌面服务启动环境正常。');
28
- });
29
- await check('network', async () => {
30
- const address = await host.lanAddress();
31
- if (!address) { add('network', 'warning', '未连接局域网;需要局域网连接时,请接入 Wi-Fi 或有线网络。'); return; }
32
- add('network', 'ok', `局域网地址:${address}`);
33
- });
34
- await check('autostart', async () => {
35
- const state = await ensureAutostart();
36
- add('autostart', 'ok', `当前用户登录自启:${state.repaired ? '已自动恢复' : '已启用'}。`);
37
- });
38
- await check('service', async () => {
39
- const state = await service.status();
40
- serviceState = state;
41
- add('service', state.running ? 'ok' : 'error', state.running
42
- ? `本机服务正常:${state.mode},端口 ${state.port},运行版本:${state.version || '未知'}` : '服务未就绪;安装后执行 relax start。');
43
- if (state.running && installedVersion && state.version !== installedVersion) add('service-version', 'warning', state.version
44
- ? `已安装版本 ${installedVersion},但当前运行版本为 ${state.version};请重新执行安装或重启工作站服务。`
45
- : `已安装版本 ${installedVersion},但当前运行的桌面服务未报告版本,可能仍是旧进程;请重新执行安装或重启桌面服务。`);
46
- const currentVersion = state.managedAppServer?.version || null;
47
- const selectedVersion = state.managedAppServer?.selectedVersion || null;
48
- if (state.managedAppServer) kernelUpdate = { pending: state.managedAppServer.updatePending === true,
49
- currentVersion, selectedVersion,
50
- command: state.managedAppServer.updatePending ? 'relax kernel-restart --confirm' : null };
51
- if (state.running) add('managed-app-server', state.managedAppServer?.state === 'ready' ? 'ok' : 'error',
52
- state.managedAppServer?.state === 'ready' ? `Codex 内核已就绪:版本 ${currentVersion || '未知'}。`
53
- : `${state.managedAppServer?.error || 'Codex 内核尚未就绪。'} 请执行 relax setup 修复;若提示未安装或未登录 Codex,请先完成安装和登录。`);
54
- if (state.managedAppServer?.updatePending) {
55
- const versionDetail = currentVersion && selectedVersion && currentVersion === selectedVersion
56
- ? `内核版本号未变(${currentVersion}),重启后将使用更新后的安装文件`
57
- : `当前内核 ${currentVersion || '未知'},待应用 ${selectedVersion || '未知'}`;
58
- add('managed-app-server-update', 'warning',
59
- `Codex 更新待应用:${versionDetail};执行 relax kernel-restart --confirm 应用更新。`);
60
- }
61
- if (state.managedAppServer?.launcherRestartRequired) add('managed-app-server-launcher', 'warning',
62
- '工作站更新尚未完全生效,请执行 relax setup。');
63
- });
64
- if (diagnostics) {
65
- try { desktopRecovery = await recoveryStatus(paths.stateRoot); }
66
- catch (error) { desktopRecovery = { status: 'unknown', error: error.message }; }
67
- }
68
- await check('skill', async () => {
69
- const state = inspectSkill(skillTarget);
70
- add('skill', state.managed ? 'ok' : 'warning', state.managed ? '本包管理的 Skill 已安装。'
71
- : state.installed ? '存在同名自定义 Skill,本包不会覆盖。' : 'Skill 未安装;重新执行 relax setup。');
72
- });
73
- return { ready: !checks.some(item => item.status === 'error'), kernelUpdate, checks, ...(diagnostics ? { desktopRecovery, diagnostics: {
74
- collectedAt: new Date().toISOString(), platform: process.platform, arch: process.arch, osRelease: os.release(),
75
- nodeVersion: process.version, nodeExecutable: process.execPath, serviceRoot: paths.serviceRoot,
76
- servicePid: serviceState?.pid || null, port: serviceState?.port || null, serviceVersion: serviceState?.version || null,
77
- } } : {}) };
78
- }
79
-
80
- export function formatDoctorOutput(result, detail = false) {
81
- const labels = { installation: '安装文件', 'bridge-runtime': '运行环境', network: '局域网',
82
- autostart: '登录自启动', service: '工作站服务', 'managed-app-server': 'Codex 内核',
83
- 'service-version': '服务版本', 'managed-app-server-update': 'Codex 更新',
84
- 'managed-app-server-launcher': '工作站更新', skill: 'Codex Skill' };
85
- const byId = id => result.checks.find(item => item.id === id);
86
- const summary = (id, success) => {
87
- const item = byId(id);
88
- return `${labels[id]}:${!item ? '未检查(工作站服务尚未启动)' : item.status === 'ok' ? success : '异常,见下方处理方法'}`;
89
- };
90
- const issues = result.checks.filter(item => item.status !== 'ok');
91
- const lines = [result.ready ? '工作站检查通过。' : '工作站检查发现问题。',
92
- summary('installation', '完整'), summary('service', '正在运行'), summary('managed-app-server', '已就绪')];
93
- if (issues.length) lines.push('', '需要处理或留意:', ...issues.map(item => `- ${labels[item.id] || item.id}:${item.message}`));
94
- if (detail) {
95
- const info = result.diagnostics || {};
96
- lines.unshift('Code Relax 诊断报告(供客服或技术人员排查)', `采集时间:${info.collectedAt || '未知'}`,
97
- `系统:${info.platform || process.platform} ${info.arch || process.arch} ${info.osRelease || ''}`,
98
- `Node:${info.nodeVersion || process.version};${info.nodeExecutable || process.execPath}`,
99
- `服务目录:${info.serviceRoot || '未知'}`, `服务进程:${info.servicePid || '未运行'};端口:${info.port || '未知'}`, '');
100
- lines.push('', '检查详情:', ...result.checks.filter(item => item.status === 'ok').map(item => `- ${labels[item.id] || item.id}:${item.message}`));
101
- const recovery = result.desktopRecovery;
102
- if (recovery && !['none', 'unsupported'].includes(recovery.status)) {
103
- const status = { failed: '未完成', completed: '已完成', succeeded: '已完成', running: '进行中', unknown: '无法读取' }[recovery.status] || recovery.status;
104
- lines.push('', `Desktop 接入排障记录(仅用于排查上次接入,不代表当前服务状态):上次 Codex Desktop 接入${status}。`);
105
- if (recovery.error) lines.push(`记录原因:${recovery.error}`);
106
- }
107
- }
108
- lines.push('', result.ready ? '添加手机:运行 relax pair 生成配对二维码。' : '按上述方法处理后,再运行 relax doctor。');
109
- return lines.join('\n');
110
- }
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 async function doctor({ paths, host, service, skillTarget, ensureAutostart, diagnostics = false }) {
9
+ const checks = [];
10
+ let installedVersion;
11
+ let kernelUpdate = null;
12
+ let desktopRecovery = null;
13
+ let serviceState = null;
14
+ const add = (id, status, message) => checks.push({ id, status, message });
15
+ const check = async (id, task) => {
16
+ try { await task(); } catch (error) { add(id, 'error', error.message); }
17
+ };
18
+ await check('installation', async () => {
19
+ const record = readRecord(path.join(paths.serviceRoot, 'installation.json'));
20
+ if (!record || !fs.existsSync(path.join(paths.serviceRoot, 'dist/bin/codex-remote.mjs'))) throw Error('尚未完整部署服务,请重新安装 Code Relax。');
21
+ if (!record.nodeExecutable || !fs.existsSync(record.nodeExecutable)) throw Error('记录的 Node 已失效,请用可用的 Node 重新安装 Code Relax。');
22
+ installedVersion = record.version;
23
+ add('installation', 'ok', `已安装版本:${record.version};Node:${record.nodeExecutable}`);
24
+ });
25
+ await check('bridge-runtime', async () => {
26
+ await host.bridgeEnvironment();
27
+ add('bridge-runtime', 'ok', '桌面服务启动环境正常。');
28
+ });
29
+ await check('network', async () => {
30
+ const address = await host.lanAddress();
31
+ if (!address) { add('network', 'warning', '未连接局域网;需要局域网连接时,请接入 Wi-Fi 或有线网络。'); return; }
32
+ add('network', 'ok', `局域网地址:${address}`);
33
+ });
34
+ await check('autostart', async () => {
35
+ const state = await ensureAutostart();
36
+ add('autostart', 'ok', `当前用户登录自启:${state.repaired ? '已自动恢复' : '已启用'}。`);
37
+ });
38
+ await check('service', async () => {
39
+ const state = await service.status();
40
+ serviceState = state;
41
+ add('service', state.running ? 'ok' : 'error', state.running
42
+ ? `本机服务正常:${state.mode},端口 ${state.port},运行版本:${state.version || '未知'}` : '服务未就绪;安装后执行 relax start。');
43
+ if (state.running && installedVersion && state.version !== installedVersion) add('service-version', 'warning', state.version
44
+ ? `已安装版本 ${installedVersion},但当前运行版本为 ${state.version};请重新执行安装或重启工作站服务。`
45
+ : `已安装版本 ${installedVersion},但当前运行的桌面服务未报告版本,可能仍是旧进程;请重新执行安装或重启桌面服务。`);
46
+ const currentVersion = state.managedAppServer?.version || null;
47
+ const selectedVersion = state.managedAppServer?.selectedVersion || null;
48
+ if (state.managedAppServer) kernelUpdate = { pending: state.managedAppServer.updatePending === true,
49
+ currentVersion, selectedVersion,
50
+ command: state.managedAppServer.updatePending ? 'relax kernel-restart --confirm' : null };
51
+ if (state.running) add('managed-app-server', state.managedAppServer?.state === 'ready' ? 'ok' : 'error',
52
+ state.managedAppServer?.state === 'ready' ? `Codex 内核已就绪:版本 ${currentVersion || '未知'}。`
53
+ : `${state.managedAppServer?.error || 'Codex 内核尚未就绪。'} 请执行 relax setup 修复;若提示未安装或未登录 Codex,请先完成安装和登录。`);
54
+ if (state.managedAppServer?.updatePending) {
55
+ const versionDetail = currentVersion && selectedVersion && currentVersion === selectedVersion
56
+ ? `内核版本号未变(${currentVersion}),重启后将使用更新后的安装文件`
57
+ : `当前内核 ${currentVersion || '未知'},待应用 ${selectedVersion || '未知'}`;
58
+ add('managed-app-server-update', 'warning',
59
+ `Codex 更新待应用:${versionDetail};执行 relax kernel-restart --confirm 应用更新。`);
60
+ }
61
+ if (state.managedAppServer?.launcherRestartRequired) add('managed-app-server-launcher', 'warning',
62
+ '工作站更新尚未完全生效,请执行 relax setup。');
63
+ });
64
+ if (diagnostics) {
65
+ try { desktopRecovery = await recoveryStatus(paths.stateRoot); }
66
+ catch (error) { desktopRecovery = { status: 'unknown', error: error.message }; }
67
+ }
68
+ await check('skill', async () => {
69
+ const state = inspectSkill(skillTarget);
70
+ add('skill', state.managed ? 'ok' : 'warning', state.managed ? '本包管理的 Skill 已安装。'
71
+ : state.installed ? '存在同名自定义 Skill,本包不会覆盖。' : 'Skill 未安装;重新执行 relax setup。');
72
+ });
73
+ return { ready: !checks.some(item => item.status === 'error'), kernelUpdate, checks, ...(diagnostics ? { desktopRecovery, diagnostics: {
74
+ collectedAt: new Date().toISOString(), platform: process.platform, arch: process.arch, osRelease: os.release(),
75
+ nodeVersion: process.version, nodeExecutable: process.execPath, serviceRoot: paths.serviceRoot,
76
+ servicePid: serviceState?.pid || null, port: serviceState?.port || null, serviceVersion: serviceState?.version || null,
77
+ } } : {}) };
78
+ }
79
+
80
+ export function formatDoctorOutput(result, detail = false) {
81
+ const labels = { installation: '安装文件', 'bridge-runtime': '运行环境', network: '局域网',
82
+ autostart: '登录自启动', service: '工作站服务', 'managed-app-server': 'Codex 内核',
83
+ 'service-version': '服务版本', 'managed-app-server-update': 'Codex 更新',
84
+ 'managed-app-server-launcher': '工作站更新', skill: 'Codex Skill' };
85
+ const byId = id => result.checks.find(item => item.id === id);
86
+ const summary = (id, success) => {
87
+ const item = byId(id);
88
+ return `${labels[id]}:${!item ? '未检查(工作站服务尚未启动)' : item.status === 'ok' ? success : '异常,见下方处理方法'}`;
89
+ };
90
+ const issues = result.checks.filter(item => item.status !== 'ok');
91
+ const lines = [result.ready ? '工作站检查通过。' : '工作站检查发现问题。',
92
+ summary('installation', '完整'), summary('service', '正在运行'), summary('managed-app-server', '已就绪')];
93
+ if (issues.length) lines.push('', '需要处理或留意:', ...issues.map(item => `- ${labels[item.id] || item.id}:${item.message}`));
94
+ if (detail) {
95
+ const info = result.diagnostics || {};
96
+ lines.unshift('Code Relax 诊断报告(供客服或技术人员排查)', `采集时间:${info.collectedAt || '未知'}`,
97
+ `系统:${info.platform || process.platform} ${info.arch || process.arch} ${info.osRelease || ''}`,
98
+ `Node:${info.nodeVersion || process.version};${info.nodeExecutable || process.execPath}`,
99
+ `服务目录:${info.serviceRoot || '未知'}`, `服务进程:${info.servicePid || '未运行'};端口:${info.port || '未知'}`, '');
100
+ lines.push('', '检查详情:', ...result.checks.filter(item => item.status === 'ok').map(item => `- ${labels[item.id] || item.id}:${item.message}`));
101
+ const recovery = result.desktopRecovery;
102
+ if (recovery && !['none', 'unsupported'].includes(recovery.status)) {
103
+ const status = { failed: '未完成', completed: '已完成', succeeded: '已完成', running: '进行中', unknown: '无法读取' }[recovery.status] || recovery.status;
104
+ lines.push('', `Desktop 接入排障记录(仅用于排查上次接入,不代表当前服务状态):上次 Codex Desktop 接入${status}。`);
105
+ if (recovery.error) lines.push(`记录原因:${recovery.error}`);
106
+ }
107
+ }
108
+ lines.push('', result.ready ? '添加手机:运行 relax pair 生成配对二维码。' : '按上述方法处理后,再运行 relax doctor。');
109
+ return lines.join('\n');
110
+ }
@@ -1,33 +1,33 @@
1
- import WebSocket from 'ws';
2
-
3
- // Use the same authenticated LAN protocol as mobile clients; never replay requests.
4
- export function serviceHealth(session) {
5
- if (!session) return Promise.resolve(null);
6
- return new Promise((resolve, reject) => {
7
- const token = new URL(session.accessUrl).searchParams.get('token');
8
- const socket = new WebSocket(`ws://127.0.0.1:${session.port}/api/socket`);
9
- let done = false;
10
- const finish = (error, value = null) => {
11
- if (done) return;
12
- done = true;
13
- clearTimeout(timer);
14
- socket.terminate();
15
- if (error) reject(error); else resolve(value);
16
- };
17
- const timer = setTimeout(() => finish(new Error('桌面服务健康检查超时。')), 6000);
18
- socket.on('open', () => socket.send(JSON.stringify({ type: 'auth', token })));
19
- socket.on('message', data => {
20
- try {
21
- const message = JSON.parse(data.toString());
22
- if (message.type === 'ready') {
23
- socket.send(JSON.stringify({ type: 'request', id: 1, method: 'GET', path: '/api/health' }));
24
- } else if (message.type === 'response' && message.id === 1) {
25
- finish(null, message.status === 200 ? message.body : null);
26
- }
27
- } catch (error) { finish(error); }
28
- });
29
- socket.on('error', error => finish(error.code === 'ECONNREFUSED' ? null : error));
30
- socket.on('close', code => finish(new Error(code === 1008
31
- ? '桌面服务健康检查鉴权失败。' : '桌面服务健康检查连接已断开。')));
32
- });
33
- }
1
+ import WebSocket from 'ws';
2
+
3
+ // Use the same authenticated LAN protocol as mobile clients; never replay requests.
4
+ export function serviceHealth(session) {
5
+ if (!session) return Promise.resolve(null);
6
+ return new Promise((resolve, reject) => {
7
+ const token = new URL(session.accessUrl).searchParams.get('token');
8
+ const socket = new WebSocket(`ws://127.0.0.1:${session.port}/api/socket`);
9
+ let done = false;
10
+ const finish = (error, value = null) => {
11
+ if (done) return;
12
+ done = true;
13
+ clearTimeout(timer);
14
+ socket.terminate();
15
+ if (error) reject(error); else resolve(value);
16
+ };
17
+ const timer = setTimeout(() => finish(new Error('桌面服务健康检查超时。')), 6000);
18
+ socket.on('open', () => socket.send(JSON.stringify({ type: 'auth', token })));
19
+ socket.on('message', data => {
20
+ try {
21
+ const message = JSON.parse(data.toString());
22
+ if (message.type === 'ready') {
23
+ socket.send(JSON.stringify({ type: 'request', id: 1, method: 'GET', path: '/api/health' }));
24
+ } else if (message.type === 'response' && message.id === 1) {
25
+ finish(null, message.status === 200 ? message.body : null);
26
+ }
27
+ } catch (error) { finish(error); }
28
+ });
29
+ socket.on('error', error => finish(error.code === 'ECONNREFUSED' ? null : error));
30
+ socket.on('close', code => finish(new Error(code === 1008
31
+ ? '桌面服务健康检查鉴权失败。' : '桌面服务健康检查连接已断开。')));
32
+ });
33
+ }