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

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 (185) 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 +62 -58
  7. package/dist/bin/self-relay-admin.mjs +8 -8
  8. package/dist/bin/self-relay-demo.mjs +33 -33
  9. package/dist/bin/self-relay-server.mjs +66 -66
  10. package/dist/build-platform.json +1 -1
  11. package/dist/get-lan-networks.ps1 +40 -40
  12. package/dist/get-shared-service-url.ps1 +6 -6
  13. package/dist/shared/app-server-events.cjs +422 -412
  14. package/dist/shared/code-browser.cjs +159 -159
  15. package/dist/shared/file-changes.cjs +31 -31
  16. package/dist/shared/file-citations.cjs +117 -117
  17. package/dist/shared/file-mentions.cjs +92 -92
  18. package/dist/shared/message-delivery.cjs +54 -54
  19. package/dist/shared/p2p-data-channel.cjs +72 -72
  20. package/dist/shared/p2p-diagnostics.cjs +28 -28
  21. package/dist/shared/p2p-file-channel.cjs +74 -74
  22. package/dist/shared/plan-pricing.cjs +32 -32
  23. package/dist/shared/question-attention.cjs +40 -0
  24. package/dist/shared/server-requests.cjs +219 -219
  25. package/dist/shared/timeline-sync.cjs +173 -173
  26. package/dist/skills/code-relax/SKILL.md +22 -22
  27. package/dist/src/archived-threads.mjs +22 -0
  28. package/dist/src/background-process.mjs +18 -18
  29. package/dist/src/bounded-process-log.mjs +63 -63
  30. package/dist/src/celsius-client.mjs +107 -107
  31. package/dist/src/code-browser.mjs +100 -100
  32. package/dist/src/desktop-launcher.mjs +46 -46
  33. package/dist/src/dots-approval-timeline.mjs +99 -99
  34. package/dist/src/dots-avatar.mjs +42 -42
  35. package/dist/src/dots-chat-projection.mjs +50 -50
  36. package/dist/src/dots-chat-server.mjs +461 -461
  37. package/dist/src/dots-message-images.mjs +319 -319
  38. package/dist/src/dots-profile.mjs +77 -77
  39. package/dist/src/dots-room-activity.mjs +94 -94
  40. package/dist/src/dots-room-client.mjs +135 -135
  41. package/dist/src/dots-room-projection.mjs +94 -94
  42. package/dist/src/dots-schedules.mjs +66 -66
  43. package/dist/src/dots-summary-tasks.mjs +32 -32
  44. package/dist/src/dots-task-catalog.mjs +430 -430
  45. package/dist/src/dots-task-files.mjs +52 -52
  46. package/dist/src/execution-health.mjs +26 -26
  47. package/dist/src/file-transfer-range.mjs +22 -22
  48. package/dist/src/file-upload-session.mjs +130 -130
  49. package/dist/src/hosted-history-app-server.mjs +103 -103
  50. package/dist/src/hosted-history-cache.mjs +113 -113
  51. package/dist/src/lan-socket-server.mjs +265 -263
  52. package/dist/src/managed-app-server.mjs +244 -244
  53. package/dist/src/message-images.mjs +9 -9
  54. package/dist/src/message-upload-files.mjs +74 -74
  55. package/dist/src/onboarding.mjs +12 -12
  56. package/dist/src/plan-pricing.mjs +47 -47
  57. package/dist/src/platform/README.md +12 -12
  58. package/dist/src/platform/codex-protocol.mjs +46 -46
  59. package/dist/src/platform/codex-runtime.mjs +3 -3
  60. package/dist/src/platform/credential-store.mjs +3 -3
  61. package/dist/src/platform/desktop-host.mjs +3 -3
  62. package/dist/src/platform/desktop-project-registration.mjs +3 -3
  63. package/dist/src/platform/desktop-project-store.mjs +208 -149
  64. package/dist/src/platform/file-opener.mjs +9 -0
  65. package/dist/src/platform/linux/README.md +3 -3
  66. package/dist/src/platform/linux/codex-runtime.mjs +56 -0
  67. package/dist/src/platform/linux/credential-store.mjs +28 -0
  68. package/dist/src/platform/linux/desktop-host.mjs +83 -0
  69. package/dist/src/platform/linux/desktop-installation.mjs +36 -0
  70. package/dist/src/platform/linux/desktop-project-registration.mjs +11 -0
  71. package/dist/src/platform/linux/network-info.mjs +11 -0
  72. package/dist/src/platform/linux/process.mjs +42 -0
  73. package/dist/src/platform/linux/project-locations.mjs +17 -0
  74. package/dist/src/platform/linux/resident-startup.mjs +52 -0
  75. package/dist/src/platform/linux/retained-runtime.mjs +76 -0
  76. package/dist/src/platform/linux/service-host.mjs +18 -0
  77. package/dist/src/platform/linux/system.mjs +49 -0
  78. package/dist/src/platform/linux/user-environment.mjs +7 -0
  79. package/dist/src/platform/macos/README.md +3 -3
  80. package/dist/src/platform/macos/codex-runtime.mjs +125 -125
  81. package/dist/src/platform/macos/credential-store.mjs +10 -10
  82. package/dist/src/platform/macos/desktop-host.mjs +82 -82
  83. package/dist/src/platform/macos/desktop-project-registration.mjs +11 -10
  84. package/dist/src/platform/macos/network-info.mjs +8 -28
  85. package/dist/src/platform/macos/project-locations.mjs +8 -8
  86. package/dist/src/platform/macos/resident-startup.mjs +84 -84
  87. package/dist/src/platform/macos/retained-runtime.mjs +127 -127
  88. package/dist/src/platform/macos/service-host.mjs +18 -18
  89. package/dist/src/platform/macos/system.mjs +29 -29
  90. package/dist/src/platform/macos/user-environment.mjs +7 -7
  91. package/dist/src/platform/native-package.mjs +58 -46
  92. package/dist/src/platform/network-info.mjs +3 -3
  93. package/dist/src/platform/path-identity.mjs +5 -5
  94. package/dist/src/platform/posix/network-candidates.mjs +23 -0
  95. package/dist/src/platform/posix/runtime-paths.mjs +25 -25
  96. package/dist/src/platform/project-locations.mjs +3 -3
  97. package/dist/src/platform/runtime-paths.mjs +8 -8
  98. package/dist/src/platform/select.mjs +5 -5
  99. package/dist/src/platform/service-host.mjs +3 -3
  100. package/dist/src/platform/user-environment.mjs +3 -3
  101. package/dist/src/platform/windows/IsolatedProcess.cs +79 -79
  102. package/dist/src/platform/windows/activate-desktop.ps1 +48 -48
  103. package/dist/src/platform/windows/background-process.mjs +30 -30
  104. package/dist/src/platform/windows/codex-runtime.mjs +215 -215
  105. package/dist/src/platform/windows/credential-store.mjs +27 -27
  106. package/dist/src/platform/windows/credential-store.ps1 +49 -49
  107. package/dist/src/platform/windows/desktop-helper.mjs +179 -179
  108. package/dist/src/platform/windows/desktop-host.mjs +58 -58
  109. package/dist/src/platform/windows/desktop-info.ps1 +14 -14
  110. package/dist/src/platform/windows/desktop-project-registration.mjs +4 -4
  111. package/dist/src/platform/windows/desktop-runtime.ps1 +10 -10
  112. package/dist/src/platform/windows/desktop-shortcuts.ps1 +21 -21
  113. package/dist/src/platform/windows/dots-profile.mjs +49 -49
  114. package/dist/src/platform/windows/launch-worker.mjs +41 -41
  115. package/dist/src/platform/windows/network-info.mjs +34 -34
  116. package/dist/src/platform/windows/project-locations.mjs +16 -16
  117. package/dist/src/platform/windows/resident-start.js +20 -20
  118. package/dist/src/platform/windows/resident-start.ps1 +7 -7
  119. package/dist/src/platform/windows/resident-startup.ps1 +83 -83
  120. package/dist/src/platform/windows/resolve-state-root.ps1 +14 -14
  121. package/dist/src/platform/windows/runtime-paths.mjs +20 -20
  122. package/dist/src/platform/windows/rustdesk-video-probe.mjs +167 -167
  123. package/dist/src/platform/windows/service-host.mjs +31 -31
  124. package/dist/src/platform/windows/start-hidden-console.ps1 +49 -49
  125. package/dist/src/platform/windows/stop-desktop.ps1 +22 -22
  126. package/dist/src/platform/windows/user-environment.mjs +50 -50
  127. package/dist/src/platform/windows/user-environment.ps1 +41 -41
  128. package/dist/src/project-archive.mjs +30 -0
  129. package/dist/src/project-create.mjs +121 -119
  130. package/dist/src/project-management.mjs +59 -69
  131. package/dist/src/project-remove.mjs +55 -0
  132. package/dist/src/project-update.mjs +56 -0
  133. package/dist/src/runtime-discovery.mjs +47 -47
  134. package/dist/src/self-relay/account-cleanup.mjs +30 -30
  135. package/dist/src/self-relay/account-connections.mjs +52 -52
  136. package/dist/src/self-relay/admin-cloud.mjs +101 -101
  137. package/dist/src/self-relay/admin-page.mjs +80 -80
  138. package/dist/src/self-relay/admin-server.mjs +74 -74
  139. package/dist/src/self-relay/admin-state.mjs +127 -127
  140. package/dist/src/self-relay/admin-style.mjs +14 -14
  141. package/dist/src/self-relay/android-releases.mjs +61 -61
  142. package/dist/src/self-relay/client.mjs +61 -61
  143. package/dist/src/self-relay/connector.mjs +439 -438
  144. package/dist/src/self-relay/debug-allowlist.mjs +33 -33
  145. package/dist/src/self-relay/demo.mjs +37 -37
  146. package/dist/src/self-relay/device-info.mjs +27 -0
  147. package/dist/src/self-relay/device-store.mjs +92 -84
  148. package/dist/src/self-relay/diagnostics.mjs +54 -54
  149. package/dist/src/self-relay/email-accounts.mjs +183 -183
  150. package/dist/src/self-relay/file-wire.mjs +13 -13
  151. package/dist/src/self-relay/lan-authorizations.mjs +71 -42
  152. package/dist/src/self-relay/lifecycle.mjs +264 -250
  153. package/dist/src/self-relay/long-pairing.mjs +40 -40
  154. package/dist/src/self-relay/mysql-state.mjs +118 -105
  155. package/dist/src/self-relay/p2p-probe.mjs +159 -159
  156. package/dist/src/self-relay/privacy-page.mjs +26 -26
  157. package/dist/src/self-relay/public-releases.mjs +34 -0
  158. package/dist/src/self-relay/rtc-file-bridge.mjs +41 -41
  159. package/dist/src/self-relay/server.mjs +1006 -932
  160. package/dist/src/self-relay/usage-daily.mjs +55 -55
  161. package/dist/src/self-relay/usage-meter.mjs +141 -141
  162. package/dist/src/self-relay/windows-route-helper.mjs +75 -75
  163. package/dist/src/self-relay/wire.mjs +36 -36
  164. package/dist/src/server.mjs +1861 -1778
  165. package/dist/src/service-control.mjs +82 -82
  166. package/dist/src/service-doctor.mjs +110 -110
  167. package/dist/src/service-health.mjs +33 -33
  168. package/dist/src/service-install.mjs +91 -91
  169. package/dist/src/service-lifecycle.mjs +138 -138
  170. package/dist/src/service-restart.mjs +81 -81
  171. package/dist/src/shared-app-server.mjs +615 -611
  172. package/dist/src/shared-catalog.mjs +132 -132
  173. package/dist/src/shared-recovery.mjs +146 -146
  174. package/dist/src/shared-thread-stream.mjs +164 -164
  175. package/dist/src/skill-install.mjs +51 -51
  176. package/dist/src/thread-title-generation.mjs +150 -150
  177. package/dist/src/turn-model-history.mjs +47 -47
  178. package/dist/src/usage-reset.mjs +17 -17
  179. package/dist/src/workstation-downloads.mjs +120 -120
  180. package/dist/tools/rtc-route-helper.ps1 +99 -99
  181. package/dist/tools/start-rtc-route-helper.ps1 +15 -15
  182. package/dist/web/vendor/markdown-it.LICENSE.txt +22 -22
  183. package/dist/web/vendor/markdown-it.umd.min.js +13 -13
  184. package/package.json +7 -6
  185. 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
+ }