@walkhi/code-relax 0.1.0-beta.2 → 0.1.0-beta.21

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 (120) hide show
  1. package/README.md +55 -71
  2. package/dist/bin/codex-remote.mjs +150 -126
  3. package/dist/bin/manage-relay-admin.mjs +20 -0
  4. package/dist/bin/migrate-relay-db.mjs +107 -0
  5. package/dist/bin/self-relay-admin.mjs +8 -0
  6. package/dist/bin/self-relay-demo.mjs +2 -2
  7. package/dist/bin/self-relay-server.mjs +18 -10
  8. package/dist/shared/app-server-events.cjs +42 -15
  9. package/dist/shared/file-mentions.cjs +35 -0
  10. package/dist/shared/server-requests.cjs +86 -58
  11. package/dist/skills/code-relax/SKILL.md +7 -6
  12. package/dist/src/desktop-launcher.mjs +16 -21
  13. package/dist/src/file-upload-session.mjs +124 -0
  14. package/dist/src/lan-socket-server.mjs +52 -9
  15. package/dist/src/managed-app-server.mjs +44 -13
  16. package/dist/src/message-images.mjs +0 -18
  17. package/dist/src/onboarding.mjs +9 -11
  18. package/dist/src/platform/README.md +1 -1
  19. package/dist/src/platform/windows/background-process.mjs +1 -1
  20. package/dist/src/platform/windows/codex-runtime.mjs +251 -0
  21. package/dist/src/platform/windows/desktop-host.mjs +17 -3
  22. package/dist/src/platform/windows/desktop-info.ps1 +2 -4
  23. package/dist/src/platform/windows/desktop-runtime.ps1 +10 -0
  24. package/dist/src/platform/windows/desktop-shortcuts.ps1 +7 -14
  25. package/dist/src/platform/windows/launch-worker.mjs +42 -0
  26. package/dist/src/platform/windows/resident-start.js +20 -0
  27. package/dist/src/platform/windows/resident-startup.ps1 +76 -20
  28. package/dist/src/platform/windows/service-host.mjs +4 -16
  29. package/dist/src/platform/windows/start-hidden-console.ps1 +10 -5
  30. package/dist/src/platform/windows/stop-desktop.ps1 +7 -4
  31. package/dist/src/platform/windows/user-environment.mjs +1 -1
  32. package/dist/src/runtime-discovery.mjs +43 -0
  33. package/dist/src/self-relay/admin-cloud.mjs +101 -0
  34. package/dist/src/self-relay/admin-page.mjs +79 -0
  35. package/dist/src/self-relay/admin-server.mjs +66 -0
  36. package/dist/src/self-relay/admin-state.mjs +82 -19
  37. package/dist/src/self-relay/admin-style.mjs +9 -0
  38. package/dist/src/self-relay/admin-usage-page.mjs +28 -0
  39. package/dist/src/self-relay/connector.mjs +243 -30
  40. package/dist/src/self-relay/demo.mjs +3 -4
  41. package/dist/src/self-relay/email-accounts.mjs +141 -0
  42. package/dist/src/self-relay/lifecycle.mjs +99 -142
  43. package/dist/src/self-relay/mysql-state.mjs +104 -0
  44. package/dist/src/self-relay/p2p-probe.mjs +1 -1
  45. package/dist/src/self-relay/server.mjs +335 -59
  46. package/dist/src/self-relay/usage-diagnostics.mjs +46 -0
  47. package/dist/src/self-relay/usage-meter.mjs +125 -0
  48. package/dist/src/server.mjs +731 -1439
  49. package/dist/src/service-control.mjs +4 -4
  50. package/dist/src/service-doctor.mjs +27 -29
  51. package/dist/src/service-health.mjs +2 -2
  52. package/dist/src/service-install.mjs +1 -4
  53. package/dist/src/service-lifecycle.mjs +19 -19
  54. package/dist/src/shared-app-server.mjs +277 -270
  55. package/dist/src/shared-catalog.mjs +1 -11
  56. package/dist/src/shared-recovery.mjs +11 -9
  57. package/dist/src/shared-thread-stream.mjs +1 -1
  58. package/dist/src/skill-install.mjs +0 -13
  59. package/package.json +17 -18
  60. package/tools/postinstall.mjs +69 -15
  61. package/dist/src/app-server-client.mjs +0 -468
  62. package/dist/src/app-server-tasks.mjs +0 -358
  63. package/dist/src/debug-ui.mjs +0 -131
  64. package/dist/src/platform/windows/desktop-monitor.mjs +0 -34
  65. package/dist/src/platform/windows/desktop-shortcut-run.ps1 +0 -5
  66. package/dist/src/platform/windows/desktop-tools.mjs +0 -48
  67. package/dist/src/platform/windows/launch-worker.ps1 +0 -20
  68. package/dist/src/thread-catalog.mjs +0 -47
  69. package/dist/tools/find-desktop-pipe.mjs +0 -10
  70. package/dist/web/activity-view.js +0 -283
  71. package/dist/web/approval-ui.js +0 -188
  72. package/dist/web/capabilities.js +0 -57
  73. package/dist/web/chat-transport.js +0 -202
  74. package/dist/web/chat.css +0 -782
  75. package/dist/web/chat.js +0 -3830
  76. package/dist/web/client-platform.js +0 -11
  77. package/dist/web/community-view.js +0 -20
  78. package/dist/web/community.css +0 -77
  79. package/dist/web/community.html +0 -37
  80. package/dist/web/composer-controller.js +0 -101
  81. package/dist/web/conversation-controller.js +0 -99
  82. package/dist/web/disclosure-state-controller.js +0 -95
  83. package/dist/web/draft-controller.js +0 -103
  84. package/dist/web/harmony-platform.js +0 -52
  85. package/dist/web/highlight-powershell.min.js +0 -39
  86. package/dist/web/highlight.min.js +0 -1262
  87. package/dist/web/history-cache.js +0 -240
  88. package/dist/web/history-controller.js +0 -167
  89. package/dist/web/index.html +0 -325
  90. package/dist/web/lan-session.js +0 -122
  91. package/dist/web/link-action-controller.js +0 -212
  92. package/dist/web/message-send-controller.js +0 -177
  93. package/dist/web/message-view.js +0 -98
  94. package/dist/web/p2p-data-channel.js +0 -50
  95. package/dist/web/p2p-diagnostics.js +0 -28
  96. package/dist/web/p2p-probe.js +0 -123
  97. package/dist/web/page-resume.js +0 -28
  98. package/dist/web/pending-message-store.js +0 -108
  99. package/dist/web/queue-controller.js +0 -82
  100. package/dist/web/resources.json +0 -1
  101. package/dist/web/self-relay-session.js +0 -129
  102. package/dist/web/server-requests.js +0 -67
  103. package/dist/web/station-connection-controller.js +0 -75
  104. package/dist/web/task-list-view.js +0 -296
  105. package/dist/web/thread-attention-controller.js +0 -124
  106. package/dist/web/thread-context-controller.js +0 -30
  107. package/dist/web/thread-list-controller.js +0 -61
  108. package/dist/web/thread-list-sync.js +0 -86
  109. package/dist/web/thread-title-controller.js +0 -57
  110. package/dist/web/timeline-formatters.js +0 -249
  111. package/dist/web/timeline-reducer.js +0 -81
  112. package/dist/web/timeline-renderer.js +0 -161
  113. package/dist/web/timeline-scroll-controller.js +0 -50
  114. package/dist/web/timeline-sync.js +0 -67
  115. package/dist/web/usage-controller.js +0 -258
  116. package/dist/web/vendor/README.md +0 -14
  117. package/dist/web/vendor/highlight-powershell.min.js +0 -39
  118. package/dist/web/vendor/highlight.LICENSE.txt +0 -29
  119. package/dist/web/vendor/highlight.min.js +0 -1262
  120. package/dist/web/vendor/lucide.LICENSE.txt +0 -17
@@ -1,17 +1,7 @@
1
1
  import events from '../shared/app-server-events.cjs';
2
2
 
3
3
  export function sharedThreadProjectId(thread, projects) {
4
- if (thread.projectId) return thread.projectId;
5
- const local = thread.environments?.find(environment => environment.environmentId === 'local');
6
- if (local && Array.isArray(local.runtimeWorkspaceRoots)) {
7
- for (const root of local.runtimeWorkspaceRoots) {
8
- const projectId = events.threadProjectId({ cwd: root }, projects);
9
- if (projectId) return projectId;
10
- }
11
- return null;
12
- }
13
- // Unloaded Desktop history omits environments and often has no persisted project ID.
14
- return events.threadProjectId({ cwd: thread.cwd }, projects);
4
+ return events.catalogThreadProjectId(thread, projects);
15
5
  }
16
6
 
17
7
  // Mobile catalog order comes entirely from app-server authority, independent of Desktop UI state.
@@ -8,6 +8,7 @@ import { startBackground } from './background-process.mjs';
8
8
  import { desktopHost } from './platform/windows/desktop-host.mjs';
9
9
  import * as serviceHost from './platform/windows/service-host.mjs';
10
10
  import { launchDesktop, portAvailable } from './desktop-launcher.mjs';
11
+ import { configureManagedEnvironment } from './platform/windows/user-environment.mjs';
11
12
 
12
13
  const script = fileURLToPath(import.meta.url);
13
14
  const recordPath = root => path.join(root, 'shared-recovery.json');
@@ -17,9 +18,9 @@ const active = value => value && ['scheduled', 'running'].includes(value.status)
17
18
  const recoverySteps = () => [
18
19
  ['checking', '检查启动条件'], ['stopping-desktop', '关闭当前 Codex'],
19
20
  ['starting-app-server', '确认 Codex 内核'], ['starting-desktop', '重新打开 Codex'],
20
- ['checking-execution', '确认手机控制就绪'],
21
+ ['checking-execution', '确认移动端控制就绪'],
21
22
  ].map(([id, label]) => ({ id, label, status: 'pending' }));
22
- export const recoveryWarning = 'Codex Desktop 需要重新打开,才能接入同一个 Codex 内核。手机完整控制和 Codex 内核会保持运行;旧 Desktop 实例中的任务可能中断,未发送内容请先保存。切换不会清空项目、对话历史、设置或设备授权。';
23
+ export const recoveryWarning = 'Codex Desktop 需要重新打开,才能接入同一个 Codex 内核。移动端连接和 Codex 内核会保持运行;旧 Desktop 实例中的任务可能中断,未发送内容请先保存。切换不会清空项目、对话历史、设置或设备授权。';
23
24
 
24
25
  export async function recoveryStatus(stateRoot, host = desktopHost) {
25
26
  const record = readRecord(recordPath(stateRoot));
@@ -40,11 +41,11 @@ export async function recoveryStatus(stateRoot, host = desktopHost) {
40
41
  export async function planRecovery(stateRoot, host, available = portAvailable) {
41
42
  const info = await host.inspect();
42
43
  const action = await host.sharedServiceUrl() ? 'restart' : 'switch';
43
- if (!info.desktop || !info.cli) throw new Error('未找到 Codex Desktop 或执行程序,请先在电脑完成安装和登录。');
44
- const record = readRecord(path.join(stateRoot, 'managed-app-server.json')) || readRecord(path.join(stateRoot, 'shared-desktop.json'));
44
+ if (!info.desktop) throw new Error('未找到 Codex Desktop;CLI 用户无需执行 Desktop 接入。');
45
+ const record = readRecord(path.join(stateRoot, 'managed-app-server.json'));
45
46
  const actual = record ? await host.process(record.pid) : null;
46
47
  if (actual && (!sameProcess(record, actual) || !actual.command?.includes('app-server') || !actual.command.includes('ws://127.0.0.1:45839'))) {
47
- throw new Error('完整控制进程身份与记录不符,未结束任何进程。');
48
+ throw new Error('Codex 内核进程身份与记录不符,未结束任何进程。');
48
49
  }
49
50
  if (!actual && !await available()) throw new Error('45839 被未识别进程占用,未关闭 Desktop 或结束任何进程。');
50
51
  return { desktop: info.desktop, action };
@@ -63,7 +64,7 @@ export async function requestRecovery(stateRoot, { confirmed, operationId = rand
63
64
  catch (error) { if (error.code === 'EEXIST') throw new Error('已有恢复操作,请先查看恢复状态。'); throw error; }
64
65
  fs.writeFileSync(lock, JSON.stringify({ operationId })); fs.closeSync(lock);
65
66
  const record = { operationId, status: 'scheduled', stage: 'prepared', startedAt: new Date().toISOString(),
66
- message: '完整控制请求已接受,电脑管理连接保持运行。', steps: recoverySteps(), ...planned };
67
+ message: 'Codex 连接请求已接受,电脑管理连接保持运行。', steps: recoverySteps(), ...planned };
67
68
  try {
68
69
  writeRecord(recordPath(stateRoot), record);
69
70
  const log = path.join(stateRoot, 'shared-recovery.log');
@@ -83,7 +84,7 @@ export async function runRecovery(stateRoot, operationId, dependencies = {}) {
83
84
  const host = dependencies.host || desktopHost;
84
85
  const service = dependencies.service || lifecycle({ stateRoot, host: serviceHost });
85
86
  const available = dependencies.available || portAvailable;
86
- const start = dependencies.launch || (options => launchDesktop('shared', options));
87
+ const start = dependencies.launch || launchDesktop;
87
88
  let record = readRecord(recordPath(stateRoot));
88
89
  if (record?.operationId !== operationId || readRecord(lockPath(stateRoot))?.operationId !== operationId) throw new Error('恢复实例已失效。');
89
90
  record.steps ||= recoverySteps();
@@ -99,6 +100,7 @@ export async function runRecovery(stateRoot, operationId, dependencies = {}) {
99
100
  try {
100
101
  stage('checking');
101
102
  const planned = await planRecovery(stateRoot, host, available);
103
+ await configureManagedEnvironment(stateRoot);
102
104
  record = { ...record, ...planned };
103
105
  if (fs.existsSync(path.join(stateRoot, 'desktop-launch.lock'))) throw new Error('Desktop 正在启动,未与启动操作并行恢复。');
104
106
  stage('stopping-desktop'); await host.closeDesktop(record.desktop);
@@ -109,9 +111,9 @@ export async function runRecovery(stateRoot, operationId, dependencies = {}) {
109
111
  await waitFor(async () => {
110
112
  const health = await service.status();
111
113
  return health.running && health.mode === 'lan-shared' && health.execution?.ready === true;
112
- }, '完整控制未就绪;Bridge 和中继管理连接仍保留。');
114
+ }, 'Codex 内核未就绪;桌面服务和中继连接仍保留。');
113
115
  record = { ...record, status: 'completed', stage: 'ready', steps: record.steps.map(step => ({ ...step, status: 'completed' })),
114
- message: 'Codex Desktop 已接入 Codex 内核;手机完整控制保持就绪,原任务未自动重发。' };
116
+ message: 'Codex Desktop 已接入 Codex 内核;移动端控制保持就绪,原任务未自动重发。' };
115
117
  } catch (error) { record = { ...record, status: 'failed', error: error.message,
116
118
  steps: record.steps.map(step => ({ ...step, status: step.status === 'running' ? 'failed' : step.status })),
117
119
  message: 'Codex Desktop 未完成接入,已停止后续步骤。' }; }
@@ -43,7 +43,7 @@ export async function streamSharedThread(response, threadId, client, normalize,
43
43
  if (!threadListNotificationMethods.has(method)) return;
44
44
  emit('thread-list-changed', { method, threadId: params.threadId || params.thread?.id || '' });
45
45
  };
46
- const onDisconnected = () => { emit('bridge-error', { error: '完整控制已断开,请重新连接。' }); response.end(); finish(); };
46
+ const onDisconnected = () => { emit('bridge-error', { error: 'Codex 内核连接已断开,请重新连接。' }); response.end(); finish(); };
47
47
  response.once('close', () => { closed = true; finish(); });
48
48
  client.on('turn', onTurn); client.on('settings', onSettings); client.on('disconnected', onDisconnected);
49
49
  client.on('queue-changed', onQueue);
@@ -6,11 +6,8 @@ import { readRecord, writeRecord } from './service-lifecycle.mjs';
6
6
 
7
7
  const owner = '@walkhi/code-relax';
8
8
  const markerName = '.code-relax-install.json';
9
- const legacyOwner = '@closedhi/codex-remote';
10
- const legacyMarkerName = '.codex-remote-install.json';
11
9
  const digest = value => createHash('sha256').update(value).digest('hex');
12
10
  export const skillDirectory = (home = os.homedir()) => path.join(home, '.agents/skills/code-relax');
13
- const legacySkillDirectory = (home = os.homedir()) => path.join(home, '.agents/skills/codex-remote');
14
11
 
15
12
  // Refuse junctions/symlinks along managed paths before writing or removing files.
16
13
  export function assertPlainPath(target) {
@@ -33,15 +30,6 @@ export function inspectSkill(target = skillDirectory()) {
33
30
  return { installed: true, managed, target };
34
31
  }
35
32
 
36
- export function removeLegacyManagedSkill(target = legacySkillDirectory()) {
37
- const marker = readRecord(path.join(target, legacyMarkerName));
38
- const file = path.join(target, 'SKILL.md');
39
- if (marker?.owner !== legacyOwner || !fs.existsSync(file) || marker.sha256 !== digest(fs.readFileSync(file))
40
- || !fs.readdirSync(target).every(name => ['SKILL.md', legacyMarkerName].includes(name))) return false;
41
- fs.unlinkSync(file); fs.unlinkSync(path.join(target, legacyMarkerName)); fs.rmdirSync(target);
42
- return true;
43
- }
44
-
45
33
  export function installSkill(source, target = skillDirectory()) {
46
34
  const state = inspectSkill(target);
47
35
  if (state.installed && !state.managed) throw new Error(`同名 Skill 非本包管理或已被修改,请保留并手动处理:${target}`);
@@ -49,7 +37,6 @@ export function installSkill(source, target = skillDirectory()) {
49
37
  fs.mkdirSync(target, { recursive: true });
50
38
  fs.writeFileSync(path.join(target, 'SKILL.md'), content);
51
39
  writeRecord(path.join(target, markerName), { owner, sha256: digest(content) });
52
- if (path.resolve(target) === path.resolve(skillDirectory())) removeLegacyManagedSkill();
53
40
  return { installed: true, target, message: 'Skill 已安装/更新;若 Codex 尚未显示,请重新打开 Codex。' };
54
41
  }
55
42
 
package/package.json CHANGED
@@ -1,27 +1,26 @@
1
1
  {
2
- "name": "@walkhi/code-relax",
3
- "version": "0.1.0-beta.2",
4
- "license": "UNLICENSED",
2
+ "name": "@walkhi/code-relax",
3
+ "version": "0.1.0-beta.21",
4
+ "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "postinstall": "node ./tools/postinstall.mjs",
8
8
  "relay:demo": "node ./bin/self-relay-demo.mjs",
9
9
  "relay:check": "node ./bin/self-relay-demo.mjs --check",
10
- "probe:app-server": "node ./tools/probe-app-server.mjs",
11
- "probe:app-server-tasks": "node ./tools/probe-app-server-tasks.mjs",
12
10
  "build": "node ./tools/build-package.mjs",
13
11
  "prepack": "npm run build"
14
12
  },
15
13
  "dependencies": {
14
+ "mysql2": "^3.24.4",
16
15
  "node-datachannel": "0.33.4",
17
16
  "qrcode": "1.5.4",
18
17
  "sharp": "0.35.4",
19
18
  "ws": "8.21.3"
20
19
  },
21
- "bin": {
22
- "relax": "dist/bin/codex-remote.mjs",
23
- "code-relax": "dist/bin/codex-remote.mjs"
24
- },
20
+ "bin": {
21
+ "relax": "dist/bin/codex-remote.mjs",
22
+ "code-relax": "dist/bin/codex-remote.mjs"
23
+ },
25
24
  "description": "Windows Code Relax LAN Bridge and CLI",
26
25
  "engines": {
27
26
  "node": ">=22.12.0"
@@ -36,15 +35,15 @@
36
35
  "dist/",
37
36
  "tools/postinstall.mjs"
38
37
  ],
39
- "bundleDependencies": [
38
+ "bundleDependencies": [
40
39
  "node-datachannel",
41
40
  "qrcode",
42
41
  "sharp",
43
- "ws"
44
- ],
45
- "publishConfig": {
46
- "access": "public",
47
- "registry": "https://registry.npmjs.org",
48
- "tag": "beta"
49
- }
50
- }
42
+ "ws"
43
+ ],
44
+ "publishConfig": {
45
+ "access": "public",
46
+ "registry": "https://registry.npmjs.org",
47
+ "tag": "beta"
48
+ }
49
+ }
@@ -6,6 +6,65 @@ import { fileURLToPath } from 'node:url';
6
6
 
7
7
  const serviceRoot = path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
8
8
  'Programs', 'CodexRemote', 'service');
9
+ const stateRoot = path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'), 'CodexRemote');
10
+ const installLog = path.join(stateRoot, 'install.log');
11
+
12
+ function sanitizeLog(value) {
13
+ let result = String(value).replace(/npm_[A-Za-z0-9]+/gu, '[redacted npm token]')
14
+ .replace(/([?&]token=)[^&\s"']+/giu, '$1[redacted]');
15
+ const paths = [['LOCALAPPDATA', process.env.LOCALAPPDATA], ['APPDATA', process.env.APPDATA],
16
+ ['USERPROFILE', process.env.USERPROFILE], ['TEMP', process.env.TEMP], ['TMP', process.env.TMP]]
17
+ .filter(([, item]) => item).sort((left, right) => right[1].length - left[1].length);
18
+ for (const [name, item] of paths) {
19
+ const pattern = item.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
20
+ result = result.replace(new RegExp(pattern, 'giu'), `%${name}%`);
21
+ }
22
+ return result;
23
+ }
24
+
25
+ function appendLog(message) {
26
+ fs.mkdirSync(stateRoot, { recursive: true });
27
+ fs.appendFileSync(installLog, `${new Date().toISOString()} ${sanitizeLog(message)}\n`, 'utf8');
28
+ }
29
+
30
+ function systemSummary() {
31
+ const npm = String(process.env.npm_config_user_agent || 'unknown').replace(/[\r\n]/gu, ' ');
32
+ return `platform=${process.platform} osRelease=${os.release()} osVersion=${os.version()} `
33
+ + `osArch=${os.arch()} processArch=${process.arch} node=${process.version} npmUserAgent=${npm}`;
34
+ }
35
+
36
+ function runStage(entry, stage, args, formatOutput = value => value) {
37
+ appendLog(`[${stage}] start`);
38
+ try {
39
+ const output = execFileSync(process.execPath, [entry, ...args], { encoding: 'utf8', windowsHide: true,
40
+ stdio: ['inherit', 'pipe', 'pipe'] });
41
+ if (output) { process.stdout.write(formatOutput(output)); appendLog(`[${stage}] stdout\n${output.trimEnd()}`); }
42
+ appendLog(`[${stage}] success`);
43
+ } catch (error) {
44
+ if (error?.stdout) { process.stdout.write(error.stdout); appendLog(`[${stage}] stdout\n${String(error.stdout).trimEnd()}`); }
45
+ if (error?.stderr) { process.stderr.write(error.stderr); appendLog(`[${stage}] stderr\n${String(error.stderr).trimEnd()}`); }
46
+ appendLog(`[${stage}] failed: ${error?.stack || error?.message || String(error)}`);
47
+ throw error;
48
+ }
49
+ }
50
+
51
+ export function formatSetupOutput(output, previous = {}) {
52
+ try {
53
+ const result = JSON.parse(output);
54
+ const migration = result.managedAppServerMigration || {};
55
+ const pid = migration.currentPid || result.managedAppServer?.pid || '未知';
56
+ const appServer = migration.restarted
57
+ ? `已重启(旧启动链迁移,PID ${migration.previousPid || '未知'} → ${pid})`
58
+ : previous.valid
59
+ ? `未重启(PID ${pid},启动代次 ${migration.launcherRevision ?? '未知'} 已是最新)`
60
+ : `已启动(PID ${pid},启动代次 ${migration.launcherRevision ?? '未知'})`;
61
+ return `Code Relax ${result.version || '未知版本'} 安装成功。\n`
62
+ + `Codex 内核:${appServer}\n`
63
+ + `桌面服务:${result.restarted ? '已恢复并健康' : '已启动并健康'}\n`
64
+ + `${result.desktopRestarted ? `Codex Desktop:已干净重启并重新接入(${result.desktopRestartReason === 'attach-managed-server' ? '接入 Codex 内核' : '内核迁移'})\n` : ''}`
65
+ + `${result.desktopRestartRecommended ? 'Codex Desktop:建议完全退出后重新打开。\n' : ''}`;
66
+ } catch { return output; }
67
+ }
9
68
 
10
69
  function installationSnapshot() {
11
70
  const recordPath = path.join(serviceRoot, 'installation.json');
@@ -47,27 +106,22 @@ function setupFailureGuidance(previous, current) {
47
106
  return '这是首次安装,尚未形成可确认的固定服务。\n修复上方错误后重新运行 npm 安装命令;无需执行卸载。';
48
107
  }
49
108
 
50
- function skillFailureGuidance(previous, current) {
51
- const service = current.valid
52
- ? `${previous.valid ? `服务已从 ${previous.version} 更新到` : '固定服务已安装为'} ${current.version}`
53
- : 'setup 已返回成功,但当前固定服务记录无法确认';
54
- return `${service};Codex Skill 安装失败。\n请执行 relax skill-install,再执行 relax doctor;无需重新卸载服务。`;
55
- }
56
-
57
- if (process.env.npm_config_global === 'true') {
109
+ const invokedDirectly = process.argv[1]
110
+ && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
111
+ if (invokedDirectly && process.env.npm_config_global === 'true') {
58
112
  const entry = fileURLToPath(new URL('../dist/bin/codex-remote.mjs', import.meta.url));
59
113
  const previous = installationSnapshot();
60
- let stage = 'setup';
114
+ appendLog(`[postinstall] start package=${process.env.npm_package_version || 'unknown'} ${systemSummary()}`);
61
115
  try {
62
- execFileSync(process.execPath, [entry, 'setup', '--json'], { stdio: 'inherit', windowsHide: true });
63
- stage = 'skill-install';
64
- execFileSync(process.execPath, [entry, 'skill-install', '--json'], { stdio: 'inherit', windowsHide: true });
116
+ runStage(entry, 'setup', ['setup', '--json'], output => formatSetupOutput(output, previous));
117
+ console.log(`安装日志:${installLog}`);
118
+ appendLog('[postinstall] success');
65
119
  } catch (error) {
66
120
  const current = installationSnapshot();
67
- console.error(`\nCode Relax 安装未完全完成:\n${stage === 'setup'
68
- ? setupFailureGuidance(previous, current)
69
- : skillFailureGuidance(previous, current)}`);
121
+ console.error(`\nCode Relax 安装未完全完成:\n${setupFailureGuidance(previous, current)}`);
70
122
  if (error?.code && error.code !== 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER') console.error(`启动安装步骤失败:${error.message}`);
123
+ console.error(`安装日志:${installLog}`);
124
+ appendLog('[postinstall] failed stage=setup');
71
125
  process.exitCode = Number.isInteger(error?.status) && error.status > 0 ? error.status : 1;
72
126
  }
73
127
  }