@walkhi/code-relax 0.1.0-beta.69 → 0.1.0-beta.71

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.
@@ -1 +1 @@
1
- {"layout":"npm-platform-packages","version":"0.1.0-beta.69"}
1
+ {"layout":"npm-platform-packages","version":"0.1.0-beta.71"}
@@ -11,7 +11,7 @@ const requiredMethods = ['thread/start', 'thread/resume', 'thread/list', 'thread
11
11
  'thread/settings/update', 'configRequirements/read', 'thread/queue/list', 'thread/queue/add',
12
12
  'thread/queue/delete', 'thread/queue/start', 'thread/read', 'thread/unsubscribe', 'config/read', 'account/rateLimits/read'];
13
13
 
14
- export function compareVersions(a, b) {
14
+ export function compareVersions(a, b) {
15
15
  const parse = v => v.split(/[.-]/).map(part => /^\d+$/.test(part) ? Number(part) : part);
16
16
  const aa = parse(a), bb = parse(b);
17
17
  for (let i = 0; i < Math.max(aa.length, bb.length); i++) {
@@ -21,7 +21,12 @@ export function compareVersions(a, b) {
21
21
  return typeof aa[i] === 'number' && typeof bb[i] === 'number' ? aa[i] - bb[i] : String(aa[i]).localeCompare(String(bb[i]));
22
22
  }
23
23
  return 0;
24
- }
24
+ }
25
+
26
+ export function newestRuntime(candidates) {
27
+ return [...candidates].sort((a, b) => compareVersions(b.version, a.version)
28
+ || Number(b.source === 'desktop') - Number(a.source === 'desktop'))[0];
29
+ }
25
30
 
26
31
  export async function verifyProtocol(executable, stateRoot) {
27
32
  const directory = fs.mkdtempSync(path.join(stateRoot, 'cli-schema-'));
@@ -1,3 +1,4 @@
1
1
  import { platformModule } from './select.mjs';
2
2
  const adapter = await import(platformModule('codex-runtime'));
3
3
  export const { discoverCodex, resolveCodexRuntime, runtimeFilesAvailable, codeModeHostPath, runtimeFingerprint, pruneRetainedDesktopRuntimes } = adapter;
4
+ export const runtimeInstallationFingerprint = adapter.runtimeInstallationFingerprint || (() => null);
@@ -4,7 +4,7 @@ import path from 'node:path';
4
4
  import { createRequire } from 'node:module';
5
5
  import { execFile, execFileSync } from 'node:child_process';
6
6
  import { promisify } from 'node:util';
7
- import { compareVersions, verifyProtocol } from '../codex-protocol.mjs';
7
+ import { newestRuntime, verifyProtocol } from '../codex-protocol.mjs';
8
8
  import { readRecord, writeRecord } from '../../service-lifecycle.mjs';
9
9
  import { retainRuntime, runtimeFingerprint, packageRoot } from './retained-runtime.mjs';
10
10
  export { runtimeFingerprint, pruneRetainedDesktopRuntimes } from './retained-runtime.mjs';
@@ -97,25 +97,25 @@ export async function resolveCodexRuntime(stateRoot, { refresh = false, environm
97
97
  const explicit = environment.CODE_RELAX_CODEX_PATH || (saved?.source === 'explicit' ? saved.sourceExecutable : '');
98
98
  if (!refresh && saved?.executable && (!explicit || explicit === saved.sourceExecutable) && codexHome === saved.codexHome
99
99
  && runtimeFilesAvailable(saved.executable) && saved.fingerprint === runtimeFingerprint(saved.executable)) return saved;
100
- const candidates = [], rejected = [];
100
+ const candidates = [];
101
101
  for (const candidate of discoverCodex({ ...environment, CODEX_HOME: codexHome, CODE_RELAX_CODEX_PATH: explicit })) {
102
102
  try {
103
- if (!runtimeFilesAvailable(candidate.executable)) throw new Error('缺少配套原生 codex-code-mode-host');
104
103
  const { stdout } = await exec(candidate.executable, ['--version'], { timeout: 10000, env: { ...environment, CODEX_HOME: codexHome } });
105
104
  const version = /^codex-cli (\d+\.\d+\.\d+(?:-[\w.]+)?)/m.exec(stdout)?.[1];
106
105
  if (!version) throw new Error('无法识别 Codex 版本');
107
106
  candidates.push({ ...candidate, version });
108
- } catch (error) { rejected.push(`${candidate.executable}: ${error.message}`); }
109
- }
110
- candidates.sort((a, b) => Number(b.source === 'desktop') - Number(a.source === 'desktop') || compareVersions(b.version, a.version));
111
- for (const candidate of candidates) {
112
- try {
107
+ } catch (error) { throw new Error(`无法确认 Codex 候选版本,未降级:${candidate.executable}: ${error.message}`); }
108
+ }
109
+ const candidate = newestRuntime(candidates);
110
+ if (candidate) {
111
+ try {
112
+ if (!runtimeFilesAvailable(candidate.executable)) throw new Error('缺少配套原生 codex-code-mode-host');
113
113
  const retained = await retainRuntime(candidate.executable, stateRoot, candidate.version);
114
114
  await verifyProtocol(retained.executable, stateRoot);
115
115
  const result = { ...candidate, ...retained, codexHome, fingerprint: runtimeFingerprint(retained.executable) };
116
116
  writeRecord(recordFile, result);
117
117
  return result;
118
- } catch (error) { rejected.push(`${candidate.executable}: ${error.message}`); }
119
- }
120
- throw new Error(`未找到具备所需协议能力的 Mac Codex 内核,请安装官方 Codex 应用或完整 CLI 并登录账号。${rejected.length ? '\n' + rejected.join('\n') : ''}`);
121
- }
118
+ } catch (error) { throw new Error(`最新 Codex 内核 ${candidate.version} 校验失败,未降级:${candidate.executable}: ${error.message}`); }
119
+ }
120
+ throw new Error('未找到 Mac Codex 内核,请安装官方 Codex 应用或完整 CLI 并登录账号。');
121
+ }
@@ -1,5 +1,5 @@
1
1
  import fs from 'node:fs';
2
- import { compareVersions, verifyProtocol } from '../codex-protocol.mjs';
2
+ import { newestRuntime, verifyProtocol } from '../codex-protocol.mjs';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import { createRequire } from 'node:module';
@@ -24,11 +24,16 @@ async function fileHash(file) {
24
24
  return hash.digest('hex');
25
25
  }
26
26
 
27
- async function installedDesktopRuntime() {
27
+ async function installedDesktopRuntime() {
28
28
  const { stdout } = await exec(powershell, ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass',
29
29
  '-File', desktopRuntimeScript], { windowsHide: true, encoding: 'utf8', timeout: 30000 });
30
30
  return JSON.parse(stdout.trim());
31
- }
31
+ }
32
+
33
+ export async function runtimeInstallationFingerprint() {
34
+ const installed = await installedDesktopRuntime();
35
+ return installed ? [installed.package, fingerprint(installed.executable)] : null;
36
+ }
32
37
 
33
38
  async function matchesInstalledDesktop(executable, installed) {
34
39
  if (!installed || !runtimeFilesAvailable(executable) || !runtimeFilesAvailable(installed.executable)) return false;
@@ -183,25 +188,30 @@ export async function resolveCodexRuntime(stateRoot, { refresh = false, environm
183
188
  CODE_RELAX_CODEX_PATH: environment.CODE_RELAX_CODEX_PATH || (saved?.source === 'explicit' ? saved.executable : '') };
184
189
  if (!refresh && saved?.executable && runtimeFilesAvailable(saved.executable)
185
190
  && saved.fingerprint === fingerprint(saved.executable)) return saved;
186
- const candidates = [], rejected = [];
187
- let installed = null;
188
- try { installed = await installedDesktopRuntime(); }
189
- catch (error) { rejected.push(`无法读取当前 Codex Desktop 安装包:${error.message}`); }
190
- for (const candidate of discoverCodex(environment)) {
191
- try {
192
- if (!runtimeFilesAvailable(candidate.executable)) throw new Error('缺少同目录的 codex-code-mode-host.exe');
193
- if (candidate.source === 'desktop' && !await matchesInstalledDesktop(candidate.executable, installed)) {
194
- throw new Error('不是当前 Codex Desktop 安装包配套的 CLI');
191
+ const candidates = [];
192
+ let installed = null;
193
+ if (!environment.CODE_RELAX_CODEX_PATH) {
194
+ try { installed = await installedDesktopRuntime(); }
195
+ catch (error) { throw new Error(`无法确认最新 Codex Desktop 内核,未降级到其他 CLI:${error.message}`); }
196
+ }
197
+ for (const candidate of discoverCodex(environment)) {
198
+ try {
199
+ if (candidate.source === 'desktop' && !await matchesInstalledDesktop(candidate.executable, installed)) {
200
+ continue; // Superseded Desktop extraction directories are not installed candidates.
195
201
  }
196
202
  const { stdout } = await exec(candidate.executable, ['--version'], { windowsHide: true, timeout: 10000 });
197
203
  const version = /^codex-cli (\d+\.\d+\.\d+(?:-[\w.]+)?)/m.exec(stdout)?.[1];
198
204
  if (!version) throw new Error('无法识别版本');
199
205
  candidates.push({ ...candidate, version });
200
- } catch (error) { rejected.push(`${candidate.executable}: ${error.message}`); }
201
- }
202
- candidates.sort((a, b) => Number(b.source === 'desktop') - Number(a.source === 'desktop') || compareVersions(b.version, a.version));
203
- for (const candidate of candidates) {
204
- try {
206
+ } catch (error) { throw new Error(`无法确认 Codex 候选版本,未降级:${candidate.executable}: ${error.message}`); }
207
+ }
208
+ if (installed && !candidates.some(candidate => candidate.source === 'desktop')) {
209
+ throw new Error(`当前 Codex Desktop 内核尚未准备完整,未降级到旧 CLI:${installed.package}。请打开更新后的桌面端完成内核提取。`);
210
+ }
211
+ const candidate = newestRuntime(candidates);
212
+ if (candidate) {
213
+ try {
214
+ if (!runtimeFilesAvailable(candidate.executable)) throw new Error('缺少同目录的 codex-code-mode-host.exe');
205
215
  const executable = candidate.source === 'desktop'
206
216
  ? await retainDesktopRuntime(candidate.executable, installed, stateRoot) : candidate.executable;
207
217
  await verifyProtocol(executable, stateRoot);
@@ -209,7 +219,7 @@ export async function resolveCodexRuntime(stateRoot, { refresh = false, environm
209
219
  codexHome: path.resolve(environment.CODEX_HOME || path.join(os.homedir(), '.codex')) };
210
220
  writeRecord(recordFile, result);
211
221
  return result;
212
- } catch (error) { rejected.push(`${candidate.executable}: ${error.message}`); }
213
- }
214
- throw new Error(`未找到兼容的 Codex CLI,请安装或更新 Codex CLI 后执行 relax setup。${rejected.length ? '\n' + rejected.join('\n') : ''}`);
215
- }
222
+ } catch (error) { throw new Error(`最新 Codex 内核 ${candidate.version} 校验失败,未降级:${candidate.executable}: ${error.message}`); }
223
+ }
224
+ throw new Error('未找到 Codex CLI,请安装或更新 Codex CLI 后执行 relax setup。');
225
+ }
@@ -1,4 +1,4 @@
1
- import { discoverCodex, resolveCodexRuntime, runtimeFingerprint } from './platform/codex-runtime.mjs';
1
+ import { discoverCodex, resolveCodexRuntime, runtimeFingerprint, runtimeInstallationFingerprint } from './platform/codex-runtime.mjs';
2
2
 
3
3
  // Advisory discovery is independent of execution health. Failure here must never
4
4
  // disable an already running app-server or cause an automatic process restart.
@@ -13,10 +13,10 @@ export class RuntimeDiscovery {
13
13
  const errors = [];
14
14
  try {
15
15
  const candidates = discoverCodex();
16
- const signature = JSON.stringify(candidates.map(candidate => {
16
+ const signature = JSON.stringify([await runtimeInstallationFingerprint(), candidates.map(candidate => {
17
17
  try { return [candidate.executable, runtimeFingerprint(candidate.executable)]; }
18
18
  catch (error) { return [candidate.executable, error.message]; }
19
- }));
19
+ })]);
20
20
  if (signature !== this.signature) {
21
21
  await resolveCodexRuntime(this.stateRoot, { refresh: true });
22
22
  this.signature = signature;