copilot-hub 0.1.20 → 0.1.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 (51) hide show
  1. package/README.md +3 -2
  2. package/apps/agent-engine/dist/config.js +58 -0
  3. package/apps/agent-engine/dist/index.js +90 -16
  4. package/apps/control-plane/dist/channels/codex-quota-cache.js +16 -0
  5. package/apps/control-plane/dist/channels/hub-model-utils.js +244 -24
  6. package/apps/control-plane/dist/channels/hub-ops-commands.js +631 -279
  7. package/apps/control-plane/dist/channels/telegram-channel.js +5 -7
  8. package/apps/control-plane/dist/config.js +58 -0
  9. package/apps/control-plane/dist/index.js +16 -0
  10. package/apps/control-plane/dist/test/hub-model-utils.test.js +110 -13
  11. package/package.json +3 -2
  12. package/packages/core/dist/agent-supervisor.d.ts +5 -0
  13. package/packages/core/dist/agent-supervisor.js +11 -0
  14. package/packages/core/dist/agent-supervisor.js.map +1 -1
  15. package/packages/core/dist/bot-manager.js +17 -1
  16. package/packages/core/dist/bot-manager.js.map +1 -1
  17. package/packages/core/dist/bot-runtime.d.ts +4 -0
  18. package/packages/core/dist/bot-runtime.js +5 -1
  19. package/packages/core/dist/bot-runtime.js.map +1 -1
  20. package/packages/core/dist/codex-app-client.d.ts +13 -2
  21. package/packages/core/dist/codex-app-client.js +51 -13
  22. package/packages/core/dist/codex-app-client.js.map +1 -1
  23. package/packages/core/dist/codex-app-utils.d.ts +6 -0
  24. package/packages/core/dist/codex-app-utils.js +49 -0
  25. package/packages/core/dist/codex-app-utils.js.map +1 -1
  26. package/packages/core/dist/codex-provider.d.ts +3 -1
  27. package/packages/core/dist/codex-provider.js +3 -1
  28. package/packages/core/dist/codex-provider.js.map +1 -1
  29. package/packages/core/dist/kernel-control-plane.d.ts +1 -0
  30. package/packages/core/dist/kernel-control-plane.js +132 -13
  31. package/packages/core/dist/kernel-control-plane.js.map +1 -1
  32. package/packages/core/dist/provider-factory.d.ts +2 -0
  33. package/packages/core/dist/provider-factory.js +3 -0
  34. package/packages/core/dist/provider-factory.js.map +1 -1
  35. package/packages/core/dist/provider-options.js +24 -17
  36. package/packages/core/dist/provider-options.js.map +1 -1
  37. package/packages/core/dist/state-store.d.ts +1 -0
  38. package/packages/core/dist/state-store.js +28 -2
  39. package/packages/core/dist/state-store.js.map +1 -1
  40. package/packages/core/dist/telegram-channel.d.ts +1 -0
  41. package/packages/core/dist/telegram-channel.js +3 -0
  42. package/packages/core/dist/telegram-channel.js.map +1 -1
  43. package/scripts/dist/cli.mjs +132 -203
  44. package/scripts/dist/codex-runtime.mjs +352 -0
  45. package/scripts/dist/codex-version.mjs +91 -0
  46. package/scripts/dist/daemon.mjs +58 -0
  47. package/scripts/src/cli.mts +166 -233
  48. package/scripts/src/codex-runtime.mts +499 -0
  49. package/scripts/src/codex-version.mts +114 -0
  50. package/scripts/src/daemon.mts +69 -0
  51. package/scripts/test/codex-version.test.mjs +21 -0
@@ -0,0 +1,352 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import process from "node:process";
4
+ import { spawnSync } from "node:child_process";
5
+ import { compareSemver, codexVersionRequirementLabel, extractSemver, isCodexVersionCompatible, } from "./codex-version.mjs";
6
+ export function resolveCodexBinForStart({ repoRoot, agentEngineEnvPath, controlPlaneEnvPath, env = process.env, }) {
7
+ const fromEnv = nonEmpty(env.CODEX_BIN);
8
+ if (fromEnv) {
9
+ return buildResolvedCodexBin({
10
+ value: fromEnv,
11
+ source: "process_env",
12
+ env,
13
+ repoRoot,
14
+ });
15
+ }
16
+ for (const [source, envPath] of [
17
+ ["agent_env", agentEngineEnvPath],
18
+ ["control_plane_env", controlPlaneEnvPath],
19
+ ]) {
20
+ const value = readEnvValue(envPath, "CODEX_BIN");
21
+ if (value) {
22
+ return buildResolvedCodexBin({
23
+ value,
24
+ source,
25
+ env,
26
+ repoRoot,
27
+ });
28
+ }
29
+ }
30
+ const detected = findDetectedCodexBin(env, repoRoot);
31
+ if (detected) {
32
+ return {
33
+ bin: detected,
34
+ source: "detected",
35
+ userConfigured: false,
36
+ };
37
+ }
38
+ return {
39
+ bin: "codex",
40
+ source: "default",
41
+ userConfigured: false,
42
+ };
43
+ }
44
+ export function resolveCompatibleInstalledCodexBin({ repoRoot, env = process.env, }) {
45
+ const matches = [];
46
+ for (const candidate of listCodexBinCandidates(env, repoRoot)) {
47
+ const probe = probeCodexVersion({
48
+ codexBin: candidate,
49
+ repoRoot,
50
+ });
51
+ if (!probe.ok || !probe.compatible) {
52
+ continue;
53
+ }
54
+ matches.push({
55
+ candidate,
56
+ version: probe.version,
57
+ priority: getCodexCandidatePriority(candidate, env, repoRoot),
58
+ });
59
+ }
60
+ if (matches.length === 0) {
61
+ return "";
62
+ }
63
+ matches.sort((left, right) => {
64
+ const versionOrder = compareSemver(right.version, left.version);
65
+ if (versionOrder !== 0) {
66
+ return versionOrder;
67
+ }
68
+ return left.priority - right.priority;
69
+ });
70
+ return matches[0]?.candidate ?? "";
71
+ }
72
+ export function probeCodexVersion({ codexBin, repoRoot, }) {
73
+ const status = runCodex({
74
+ codexBin,
75
+ args: ["--version"],
76
+ repoRoot,
77
+ });
78
+ if (!status.ok) {
79
+ return {
80
+ ...status,
81
+ version: "",
82
+ rawVersion: "",
83
+ compatible: false,
84
+ };
85
+ }
86
+ const rawVersion = firstLine(status.stdout) || firstLine(status.stderr);
87
+ const version = extractSemver(rawVersion);
88
+ if (!version) {
89
+ return {
90
+ ok: false,
91
+ stdout: status.stdout,
92
+ stderr: status.stderr,
93
+ errorMessage: `Could not parse Codex version from '${rawVersion || "empty output"}'.`,
94
+ errorCode: "INVALID_VERSION",
95
+ version: "",
96
+ rawVersion,
97
+ compatible: false,
98
+ };
99
+ }
100
+ return {
101
+ ...status,
102
+ version,
103
+ rawVersion,
104
+ compatible: isCodexVersionCompatible(version),
105
+ };
106
+ }
107
+ export function buildCodexCompatibilitySummary({ resolved, probe, }) {
108
+ if (probe.ok) {
109
+ return `Codex binary '${resolved.bin}' is version ${probe.version}.`;
110
+ }
111
+ if (probe.errorCode === "ENOENT") {
112
+ return `Codex binary '${resolved.bin}' was not found.`;
113
+ }
114
+ return probe.errorMessage || `Codex binary '${resolved.bin}' is not usable.`;
115
+ }
116
+ export function buildCodexCompatibilityNotice({ resolved, probe, }) {
117
+ return [
118
+ buildCodexCompatibilitySummary({ resolved, probe }),
119
+ `copilot-hub requires Codex CLI ${codexVersionRequirementLabel}.`,
120
+ ].join("\n");
121
+ }
122
+ export function buildCodexCompatibilityError({ resolved, probe, includeInstallHint, installCommand, }) {
123
+ const lines = [
124
+ buildCodexCompatibilitySummary({ resolved, probe }),
125
+ `copilot-hub requires Codex CLI ${codexVersionRequirementLabel}.`,
126
+ ];
127
+ if (includeInstallHint) {
128
+ lines.push(`Install a compatible version with '${installCommand}', then retry.`);
129
+ }
130
+ else {
131
+ lines.push("Update that binary or point CODEX_BIN to a compatible executable, then retry.");
132
+ }
133
+ return lines.join("\n");
134
+ }
135
+ function buildResolvedCodexBin({ value, source, env, repoRoot, }) {
136
+ const normalized = String(value ?? "")
137
+ .trim()
138
+ .toLowerCase();
139
+ if (normalized && normalized !== "codex") {
140
+ return {
141
+ bin: value,
142
+ source,
143
+ userConfigured: true,
144
+ };
145
+ }
146
+ const detected = findDetectedCodexBin(env, repoRoot);
147
+ return {
148
+ bin: detected || "codex",
149
+ source,
150
+ userConfigured: false,
151
+ };
152
+ }
153
+ function findDetectedCodexBin(env, repoRoot) {
154
+ if (process.platform !== "win32") {
155
+ return "";
156
+ }
157
+ return findWindowsNpmGlobalCodexBin(env, repoRoot) || findVscodeCodexExe(env) || "";
158
+ }
159
+ function listCodexBinCandidates(env, repoRoot) {
160
+ return dedupe(["codex", findWindowsNpmGlobalCodexBin(env, repoRoot), findVscodeCodexExe(env)]);
161
+ }
162
+ function getCodexCandidatePriority(candidate, env, repoRoot) {
163
+ if (candidate === "codex") {
164
+ return 0;
165
+ }
166
+ const npmGlobal = findWindowsNpmGlobalCodexBin(env, repoRoot);
167
+ if (npmGlobal && candidate === npmGlobal) {
168
+ return 1;
169
+ }
170
+ const vscode = findVscodeCodexExe(env);
171
+ if (vscode && candidate === vscode) {
172
+ return 2;
173
+ }
174
+ return 3;
175
+ }
176
+ function findVscodeCodexExe(env) {
177
+ const userProfile = nonEmpty(env.USERPROFILE);
178
+ if (!userProfile) {
179
+ return "";
180
+ }
181
+ const extensionsDir = path.join(userProfile, ".vscode", "extensions");
182
+ if (!fs.existsSync(extensionsDir)) {
183
+ return "";
184
+ }
185
+ const candidates = fs
186
+ .readdirSync(extensionsDir, { withFileTypes: true })
187
+ .filter((entry) => entry.isDirectory())
188
+ .map((entry) => entry.name)
189
+ .filter((name) => name.startsWith("openai.chatgpt-"))
190
+ .sort()
191
+ .reverse();
192
+ for (const folder of candidates) {
193
+ const exePath = path.join(extensionsDir, folder, "bin", "windows-x86_64", "codex.exe");
194
+ if (fs.existsSync(exePath)) {
195
+ return exePath;
196
+ }
197
+ }
198
+ return "";
199
+ }
200
+ function findWindowsNpmGlobalCodexBin(env, repoRoot) {
201
+ if (process.platform !== "win32") {
202
+ return "";
203
+ }
204
+ const candidates = [];
205
+ const appData = nonEmpty(env.APPDATA);
206
+ if (appData) {
207
+ candidates.push(path.join(appData, "npm", "codex.cmd"));
208
+ candidates.push(path.join(appData, "npm", "codex.exe"));
209
+ candidates.push(path.join(appData, "npm", "codex"));
210
+ }
211
+ const npmPrefix = readNpmPrefix(repoRoot);
212
+ if (npmPrefix) {
213
+ candidates.push(path.join(npmPrefix, "codex.cmd"));
214
+ candidates.push(path.join(npmPrefix, "codex.exe"));
215
+ candidates.push(path.join(npmPrefix, "codex"));
216
+ }
217
+ for (const candidate of dedupe(candidates)) {
218
+ if (fs.existsSync(candidate)) {
219
+ return candidate;
220
+ }
221
+ }
222
+ return "";
223
+ }
224
+ function readNpmPrefix(repoRoot) {
225
+ const result = spawnNpm(["config", "get", "prefix"], repoRoot);
226
+ if (result.error || result.status !== 0) {
227
+ return "";
228
+ }
229
+ const value = String(result.stdout ?? "").trim();
230
+ if (!value || value.toLowerCase() === "undefined") {
231
+ return "";
232
+ }
233
+ return value;
234
+ }
235
+ function runCodex({ codexBin, args, repoRoot, }) {
236
+ const result = spawnCodex(codexBin, args, repoRoot);
237
+ if (result.error) {
238
+ return {
239
+ ok: false,
240
+ stdout: "",
241
+ stderr: "",
242
+ errorMessage: formatCodexSpawnError(codexBin, result.error),
243
+ errorCode: normalizeErrorCode(result.error),
244
+ };
245
+ }
246
+ const code = Number.isInteger(result.status) ? result.status : 1;
247
+ return {
248
+ ok: code === 0,
249
+ stdout: String(result.stdout ?? "").trim(),
250
+ stderr: String(result.stderr ?? "").trim(),
251
+ errorMessage: "",
252
+ errorCode: "",
253
+ };
254
+ }
255
+ function spawnCodex(codexBin, args, repoRoot) {
256
+ if (process.platform === "win32" && /\.(cmd|bat)$/i.test(codexBin)) {
257
+ const commandLine = [
258
+ quoteWindowsShellValue(codexBin),
259
+ ...args.map(quoteWindowsShellValue),
260
+ ].join(" ");
261
+ return spawnSync(commandLine, {
262
+ cwd: repoRoot,
263
+ stdio: ["ignore", "pipe", "pipe"],
264
+ shell: true,
265
+ encoding: "utf8",
266
+ });
267
+ }
268
+ return spawnSync(codexBin, args, {
269
+ cwd: repoRoot,
270
+ stdio: ["ignore", "pipe", "pipe"],
271
+ shell: false,
272
+ encoding: "utf8",
273
+ });
274
+ }
275
+ function readEnvValue(filePath, key) {
276
+ if (!fs.existsSync(filePath)) {
277
+ return "";
278
+ }
279
+ const lines = fs.readFileSync(filePath, "utf8").split(/\r?\n/);
280
+ const pattern = new RegExp(`^\\s*${escapeRegex(key)}\\s*=\\s*(.*)\\s*$`);
281
+ for (const line of lines) {
282
+ const match = line.match(pattern);
283
+ if (!match) {
284
+ continue;
285
+ }
286
+ return unquote(match[1]);
287
+ }
288
+ return "";
289
+ }
290
+ function unquote(value) {
291
+ const raw = String(value ?? "").trim();
292
+ if (!raw) {
293
+ return "";
294
+ }
295
+ if ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'"))) {
296
+ return raw.slice(1, -1).trim();
297
+ }
298
+ return raw;
299
+ }
300
+ function escapeRegex(value) {
301
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
302
+ }
303
+ function nonEmpty(value) {
304
+ const normalized = String(value ?? "").trim();
305
+ return normalized || "";
306
+ }
307
+ function firstLine(value) {
308
+ return (String(value ?? "")
309
+ .split(/\r?\n/)
310
+ .map((line) => line.trim())
311
+ .find(Boolean) ?? "");
312
+ }
313
+ function formatCodexSpawnError(command, error) {
314
+ const code = normalizeErrorCode(error);
315
+ if (code === "ENOENT") {
316
+ return `Codex binary '${command}' was not found. Install Codex CLI or set CODEX_BIN.`;
317
+ }
318
+ if (code === "EPERM") {
319
+ return `Codex binary '${command}' cannot be executed (EPERM). Check permissions or CODEX_BIN.`;
320
+ }
321
+ const message = error instanceof Error ? error.message : String(error);
322
+ return `Failed to execute '${command}': ${firstLine(message)}`;
323
+ }
324
+ function normalizeErrorCode(error) {
325
+ return String(error?.code ?? "")
326
+ .trim()
327
+ .toUpperCase();
328
+ }
329
+ function dedupe(values) {
330
+ return [...new Set(values.map((value) => String(value ?? "").trim()).filter(Boolean))];
331
+ }
332
+ function quoteWindowsShellValue(value) {
333
+ return `"${String(value ?? "").replace(/"/g, '\\"')}"`;
334
+ }
335
+ function spawnNpm(args, repoRoot) {
336
+ if (process.platform === "win32") {
337
+ const comspec = process.env.ComSpec || "cmd.exe";
338
+ const commandLine = ["npm", ...args].join(" ");
339
+ return spawnSync(comspec, ["/d", "/s", "/c", commandLine], {
340
+ cwd: repoRoot,
341
+ stdio: ["ignore", "pipe", "pipe"],
342
+ shell: false,
343
+ encoding: "utf8",
344
+ });
345
+ }
346
+ return spawnSync("npm", args, {
347
+ cwd: repoRoot,
348
+ stdio: ["ignore", "pipe", "pipe"],
349
+ shell: false,
350
+ encoding: "utf8",
351
+ });
352
+ }
@@ -0,0 +1,91 @@
1
+ export const codexNpmPackage = "@openai/codex";
2
+ export const minimumCodexVersion = "0.113.0";
3
+ export const maximumCodexVersionExclusive = "0.114.0";
4
+ export const codexInstallPackageSpec = `${codexNpmPackage}@${minimumCodexVersion}`;
5
+ export const codexVersionRequirementLabel = `>= ${minimumCodexVersion} < ${maximumCodexVersionExclusive}`;
6
+ export function extractSemver(value) {
7
+ const match = String(value ?? "").match(/\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?/);
8
+ return String(match?.[0] ?? "").trim();
9
+ }
10
+ export function compareSemver(left, right) {
11
+ const a = parseSemver(left);
12
+ const b = parseSemver(right);
13
+ if (!a || !b) {
14
+ return 0;
15
+ }
16
+ if (a.major !== b.major) {
17
+ return a.major > b.major ? 1 : -1;
18
+ }
19
+ if (a.minor !== b.minor) {
20
+ return a.minor > b.minor ? 1 : -1;
21
+ }
22
+ if (a.patch !== b.patch) {
23
+ return a.patch > b.patch ? 1 : -1;
24
+ }
25
+ if (a.prerelease.length === 0 && b.prerelease.length === 0) {
26
+ return 0;
27
+ }
28
+ if (a.prerelease.length === 0) {
29
+ return 1;
30
+ }
31
+ if (b.prerelease.length === 0) {
32
+ return -1;
33
+ }
34
+ const maxLength = Math.max(a.prerelease.length, b.prerelease.length);
35
+ for (let index = 0; index < maxLength; index += 1) {
36
+ const leftPart = a.prerelease[index];
37
+ const rightPart = b.prerelease[index];
38
+ if (leftPart === undefined) {
39
+ return -1;
40
+ }
41
+ if (rightPart === undefined) {
42
+ return 1;
43
+ }
44
+ if (leftPart === rightPart) {
45
+ continue;
46
+ }
47
+ const leftNumeric = /^\d+$/.test(leftPart);
48
+ const rightNumeric = /^\d+$/.test(rightPart);
49
+ if (leftNumeric && rightNumeric) {
50
+ const leftValue = Number.parseInt(leftPart, 10);
51
+ const rightValue = Number.parseInt(rightPart, 10);
52
+ if (leftValue !== rightValue) {
53
+ return leftValue > rightValue ? 1 : -1;
54
+ }
55
+ continue;
56
+ }
57
+ if (leftNumeric) {
58
+ return -1;
59
+ }
60
+ if (rightNumeric) {
61
+ return 1;
62
+ }
63
+ return leftPart > rightPart ? 1 : -1;
64
+ }
65
+ return 0;
66
+ }
67
+ export function isCodexVersionCompatible(version) {
68
+ const parsed = parseSemver(version);
69
+ if (!parsed || parsed.prerelease.length > 0) {
70
+ return false;
71
+ }
72
+ return (compareSemver(version, minimumCodexVersion) >= 0 &&
73
+ compareSemver(version, maximumCodexVersionExclusive) < 0);
74
+ }
75
+ function parseSemver(value) {
76
+ const normalized = extractSemver(value);
77
+ const match = normalized.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/);
78
+ if (!match) {
79
+ return null;
80
+ }
81
+ const prerelease = String(match[4] ?? "")
82
+ .split(".")
83
+ .map((part) => part.trim())
84
+ .filter(Boolean);
85
+ return {
86
+ major: Number.parseInt(match[1] ?? "", 10),
87
+ minor: Number.parseInt(match[2] ?? "", 10),
88
+ patch: Number.parseInt(match[3] ?? "", 10),
89
+ prerelease,
90
+ };
91
+ }
@@ -5,6 +5,8 @@ import process from "node:process";
5
5
  import { spawn, spawnSync } from "node:child_process";
6
6
  import { createInterface } from "node:readline/promises";
7
7
  import { fileURLToPath } from "node:url";
8
+ import { codexInstallPackageSpec } from "./codex-version.mjs";
9
+ import { buildCodexCompatibilityError, probeCodexVersion, resolveCodexBinForStart, resolveCompatibleInstalledCodexBin, } from "./codex-runtime.mjs";
8
10
  const __filename = fileURLToPath(import.meta.url);
9
11
  const __dirname = path.dirname(__filename);
10
12
  const repoRoot = path.resolve(__dirname, "..", "..");
@@ -19,6 +21,9 @@ const agentEngineLogPath = path.join(logsDir, "agent-engine.log");
19
21
  const daemonScriptPath = path.join(repoRoot, "scripts", "dist", "daemon.mjs");
20
22
  const supervisorScriptPath = path.join(repoRoot, "scripts", "dist", "supervisor.mjs");
21
23
  const nodeBin = process.execPath;
24
+ const agentEngineEnvPath = path.join(repoRoot, "apps", "agent-engine", ".env");
25
+ const controlPlaneEnvPath = path.join(repoRoot, "apps", "control-plane", ".env");
26
+ const codexInstallCommand = `npm install -g ${codexInstallPackageSpec}`;
22
27
  const BASE_CHECK_MS = 5000;
23
28
  const MAX_BACKOFF_MS = 60000;
24
29
  const action = String(process.argv[2] ?? "status")
@@ -103,6 +108,26 @@ async function runDaemonLoop() {
103
108
  });
104
109
  const state = { stopping: false, shuttingDown: false };
105
110
  setupSignalHandlers(state);
111
+ try {
112
+ ensureCompatibleCodexForDaemon();
113
+ clearLastStartupError();
114
+ }
115
+ catch (error) {
116
+ writeLastStartupError({
117
+ detectedAt: new Date().toISOString(),
118
+ reason: getErrorMessage(error),
119
+ action: buildCodexCompatibilityAction(error),
120
+ });
121
+ console.error(`[daemon] fatal startup error: ${getErrorMessage(error)}`);
122
+ console.error(`[daemon] action required: ${buildCodexCompatibilityAction(error)}`);
123
+ state.stopping = true;
124
+ await shutdownDaemon(state, {
125
+ reason: "fatal-codex-compatibility",
126
+ exitCode: 1,
127
+ pauseBeforeExit: true,
128
+ });
129
+ return;
130
+ }
106
131
  console.log(`[daemon] running (pid ${process.pid})`);
107
132
  let failureCount = 0;
108
133
  while (!state.stopping) {
@@ -529,6 +554,39 @@ function printLastStartupError() {
529
554
  console.log(`action: ${String(issue.action)}`);
530
555
  }
531
556
  }
557
+ function ensureCompatibleCodexForDaemon() {
558
+ const resolved = resolveCodexBinForStart({
559
+ repoRoot,
560
+ agentEngineEnvPath,
561
+ controlPlaneEnvPath,
562
+ });
563
+ const currentProbe = probeCodexVersion({
564
+ codexBin: resolved.bin,
565
+ repoRoot,
566
+ });
567
+ if (currentProbe.ok && currentProbe.compatible) {
568
+ return;
569
+ }
570
+ if (!resolved.userConfigured) {
571
+ const compatibleInstalled = resolveCompatibleInstalledCodexBin({ repoRoot });
572
+ if (compatibleInstalled) {
573
+ return;
574
+ }
575
+ }
576
+ throw new Error(buildCodexCompatibilityError({
577
+ resolved,
578
+ probe: currentProbe,
579
+ includeInstallHint: !resolved.userConfigured,
580
+ installCommand: codexInstallCommand,
581
+ }));
582
+ }
583
+ function buildCodexCompatibilityAction(error) {
584
+ const message = getErrorMessage(error);
585
+ if (message.includes("Install a compatible version with")) {
586
+ return `Install a compatible version with '${codexInstallCommand}', then restart the service.`;
587
+ }
588
+ return "Update that binary or point CODEX_BIN to a compatible executable, then restart the service.";
589
+ }
532
590
  function printUsage() {
533
591
  console.log("Usage: node scripts/dist/daemon.mjs <start|run|stop|status|help>");
534
592
  }