copilot-hub 0.1.20 → 0.1.23
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.
- package/README.md +22 -13
- package/apps/agent-engine/dist/config.js +91 -16
- package/apps/agent-engine/dist/index.js +90 -16
- package/apps/control-plane/dist/channels/codex-quota-cache.js +16 -0
- package/apps/control-plane/dist/channels/hub-model-utils.js +244 -24
- package/apps/control-plane/dist/channels/hub-ops-commands.js +631 -279
- package/apps/control-plane/dist/channels/telegram-channel.js +7 -8
- package/apps/control-plane/dist/config.js +91 -16
- package/apps/control-plane/dist/copilot-hub.js +2 -2
- package/apps/control-plane/dist/index.js +16 -0
- package/apps/control-plane/dist/test/hub-model-utils.test.js +110 -13
- package/package.json +3 -3
- package/packages/core/dist/agent-supervisor.d.ts +5 -0
- package/packages/core/dist/agent-supervisor.js +11 -0
- package/packages/core/dist/agent-supervisor.js.map +1 -1
- package/packages/core/dist/bot-manager.js +17 -1
- package/packages/core/dist/bot-manager.js.map +1 -1
- package/packages/core/dist/bot-runtime.d.ts +4 -0
- package/packages/core/dist/bot-runtime.js +5 -1
- package/packages/core/dist/bot-runtime.js.map +1 -1
- package/packages/core/dist/codex-app-client.d.ts +13 -2
- package/packages/core/dist/codex-app-client.js +51 -13
- package/packages/core/dist/codex-app-client.js.map +1 -1
- package/packages/core/dist/codex-app-utils.d.ts +6 -0
- package/packages/core/dist/codex-app-utils.js +49 -0
- package/packages/core/dist/codex-app-utils.js.map +1 -1
- package/packages/core/dist/codex-provider.d.ts +3 -1
- package/packages/core/dist/codex-provider.js +3 -1
- package/packages/core/dist/codex-provider.js.map +1 -1
- package/packages/core/dist/config-paths.d.ts +11 -0
- package/packages/core/dist/config-paths.js +42 -0
- package/packages/core/dist/config-paths.js.map +1 -0
- package/packages/core/dist/index.d.ts +1 -0
- package/packages/core/dist/index.js +1 -0
- package/packages/core/dist/index.js.map +1 -1
- package/packages/core/dist/kernel-control-plane.d.ts +1 -0
- package/packages/core/dist/kernel-control-plane.js +132 -13
- package/packages/core/dist/kernel-control-plane.js.map +1 -1
- package/packages/core/dist/provider-factory.d.ts +2 -0
- package/packages/core/dist/provider-factory.js +3 -0
- package/packages/core/dist/provider-factory.js.map +1 -1
- package/packages/core/dist/provider-options.js +24 -17
- package/packages/core/dist/provider-options.js.map +1 -1
- package/packages/core/dist/state-store.d.ts +1 -0
- package/packages/core/dist/state-store.js +28 -2
- package/packages/core/dist/state-store.js.map +1 -1
- package/packages/core/dist/telegram-channel.d.ts +1 -0
- package/packages/core/dist/telegram-channel.js +5 -1
- package/packages/core/dist/telegram-channel.js.map +1 -1
- package/packages/core/package.json +4 -0
- package/scripts/dist/cli.mjs +115 -267
- package/scripts/dist/codex-runtime.mjs +352 -0
- package/scripts/dist/codex-version.mjs +91 -0
- package/scripts/dist/configure.mjs +8 -9
- package/scripts/dist/daemon.mjs +65 -4
- package/scripts/dist/install-layout.mjs +140 -0
- package/scripts/dist/service.mjs +9 -6
- package/scripts/dist/supervisor.mjs +35 -8
- package/scripts/src/cli.mts +136 -308
- package/scripts/src/codex-runtime.mts +499 -0
- package/scripts/src/codex-version.mts +114 -0
- package/scripts/src/configure.mts +9 -10
- package/scripts/src/daemon.mts +76 -4
- package/scripts/src/install-layout.mts +207 -0
- package/scripts/src/service.mts +9 -6
- package/scripts/src/supervisor.mts +36 -8
- package/scripts/test/codex-version.test.mjs +21 -0
- package/scripts/test/install-layout.test.mjs +82 -0
package/README.md
CHANGED
|
@@ -82,6 +82,7 @@ copilot-hub start
|
|
|
82
82
|
|
|
83
83
|
`start` runs guided setup automatically if required values are missing.
|
|
84
84
|
On interactive terminals, `start` can also offer OS-native service installation when it is not yet configured.
|
|
85
|
+
`start` also verifies the supported Codex CLI range and can install the validated version automatically when needed.
|
|
85
86
|
|
|
86
87
|
## Quick start from source
|
|
87
88
|
|
|
@@ -160,16 +161,29 @@ Default values are already applied, and actions start from that agent workspace
|
|
|
160
161
|
|
|
161
162
|
- Never commit real bot tokens.
|
|
162
163
|
- If a token is leaked, regenerate it in `@BotFather` using `/revoke`.
|
|
163
|
-
- Keep local runtime files
|
|
164
|
+
- Keep local runtime files private.
|
|
164
165
|
|
|
165
166
|
## Startup troubleshooting
|
|
166
167
|
|
|
167
168
|
- If `npm run start` fails, first read the error and follow the suggested action.
|
|
168
|
-
- `npm run start` now
|
|
169
|
+
- `npm run start` now checks that Codex CLI is inside the supported range and can install the validated version automatically if missing or outside that range.
|
|
169
170
|
- For Codex login issues, run `codex login` (or the configured `CODEX_BIN`) and retry `npm run start`.
|
|
170
|
-
- If auto-install is skipped or unavailable, install Codex CLI with `npm install -g @openai/codex` or set `CODEX_BIN` in
|
|
171
|
+
- If auto-install is skipped or unavailable, install Codex CLI with `npm install -g @openai/codex@0.113.0` or set `CODEX_BIN` in your Copilot Hub config to a binary in the supported `0.113.x` range.
|
|
171
172
|
- If you are still stuck, ask your favorite LLM with the exact error output.
|
|
172
173
|
|
|
174
|
+
## Upgrades
|
|
175
|
+
|
|
176
|
+
Use the standard npm flow:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npm install -g copilot-hub@latest
|
|
180
|
+
copilot-hub restart
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
If Copilot Hub is not already running, use `copilot-hub start` after the install instead of `copilot-hub restart`.
|
|
184
|
+
|
|
185
|
+
Tokens, registry data, logs and runtime state are now stored in a persistent per-user Copilot Hub directory, so they survive package upgrades. The runtime also no longer keeps the installed package directory as its working directory, so `npm install -g copilot-hub@latest` can replace the package cleanly on Windows while the service is running.
|
|
186
|
+
|
|
173
187
|
## Commands
|
|
174
188
|
|
|
175
189
|
```bash
|
|
@@ -179,19 +193,12 @@ npm run restart
|
|
|
179
193
|
npm run status
|
|
180
194
|
npm run logs
|
|
181
195
|
npm run configure
|
|
182
|
-
npm run update
|
|
183
196
|
npm run test
|
|
184
197
|
npm run lint
|
|
185
198
|
npm run format:check
|
|
186
199
|
npm run check:apps
|
|
187
200
|
```
|
|
188
201
|
|
|
189
|
-
Global update command:
|
|
190
|
-
|
|
191
|
-
```bash
|
|
192
|
-
copilot-hub update
|
|
193
|
-
```
|
|
194
|
-
|
|
195
202
|
Service mode (optional, OS-native):
|
|
196
203
|
|
|
197
204
|
```bash
|
|
@@ -237,11 +244,13 @@ Authentication options:
|
|
|
237
244
|
|
|
238
245
|
## Runtime files
|
|
239
246
|
|
|
240
|
-
-
|
|
241
|
-
-
|
|
247
|
+
- Config, tokens, registry, logs and runtime state live in the per-user Copilot Hub home directory.
|
|
248
|
+
- Windows default: `%APPDATA%\\copilot-hub`
|
|
249
|
+
- macOS default: `~/Library/Application Support/copilot-hub`
|
|
250
|
+
- Linux default: `${XDG_CONFIG_HOME:-~/.config}/copilot-hub`
|
|
242
251
|
|
|
243
252
|
## Security
|
|
244
253
|
|
|
245
254
|
- Never commit real tokens.
|
|
246
|
-
- Keep
|
|
255
|
+
- Keep your Copilot Hub home directory private.
|
|
247
256
|
- Rotate leaked tokens immediately.
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
3
4
|
import dotenv from "dotenv";
|
|
4
5
|
import { createWorkspaceBoundaryPolicy, assertWorkspaceAllowed, parseWorkspaceAllowedRoots, } from "@copilot-hub/core/workspace-policy";
|
|
5
6
|
import { parseTurnActivityTimeoutSetting } from "@copilot-hub/core/codex-app-utils";
|
|
7
|
+
import { resolveConfigBaseDir, resolveOptionalPathFromBase, resolvePathFromBase, } from "@copilot-hub/core/config-paths";
|
|
6
8
|
import { getDefaultExternalWorkspaceBasePath, getKernelRootPath, } from "@copilot-hub/core/workspace-paths";
|
|
7
|
-
|
|
9
|
+
const envBaseDir = loadEnvironment();
|
|
8
10
|
const kernelRootPath = getKernelRootPath();
|
|
9
11
|
const configuredDefaultWorkspaceRoot = String(process.env.DEFAULT_WORKSPACE_ROOT ?? "").trim();
|
|
10
12
|
const defaultWorkspaceRoot = resolveWorkspaceRoot(configuredDefaultWorkspaceRoot || getDefaultExternalWorkspaceBasePath(kernelRootPath));
|
|
11
13
|
const configuredProjectsBaseDir = String(process.env.PROJECTS_BASE_DIR ?? "").trim();
|
|
12
|
-
const projectsBaseDir =
|
|
14
|
+
const projectsBaseDir = resolvePathFromBase(configuredProjectsBaseDir || defaultWorkspaceRoot, envBaseDir);
|
|
13
15
|
const workspaceStrictMode = parseBoolean(process.env.WORKSPACE_STRICT_MODE ?? "true");
|
|
14
16
|
const workspaceAllowedRoots = parseWorkspaceAllowedRoots(process.env.WORKSPACE_ALLOWED_ROOTS ?? "", {
|
|
15
|
-
cwd:
|
|
17
|
+
cwd: envBaseDir,
|
|
16
18
|
});
|
|
17
19
|
const workspacePolicy = createWorkspaceBoundaryPolicy({
|
|
18
20
|
kernelRootPath,
|
|
@@ -26,15 +28,19 @@ assertWorkspaceAllowed({
|
|
|
26
28
|
policy: workspacePolicy,
|
|
27
29
|
label: "DEFAULT_WORKSPACE_ROOT",
|
|
28
30
|
});
|
|
29
|
-
const dataDir =
|
|
30
|
-
const botRegistryFilePath =
|
|
31
|
-
const secretStoreFilePath =
|
|
31
|
+
const dataDir = resolvePathFromBase(process.env.BOT_DATA_DIR ?? path.join(envBaseDir, "data"), envBaseDir);
|
|
32
|
+
const botRegistryFilePath = resolvePathFromBase(process.env.BOT_REGISTRY_FILE ?? path.join(dataDir, "bot-registry.json"), envBaseDir);
|
|
33
|
+
const secretStoreFilePath = resolvePathFromBase(process.env.SECRET_STORE_FILE ?? path.join(dataDir, "secrets.json"), envBaseDir);
|
|
32
34
|
const instanceLockEnabled = parseBoolean(process.env.INSTANCE_LOCK_ENABLED ?? "true");
|
|
33
|
-
const instanceLockFilePath =
|
|
35
|
+
const instanceLockFilePath = resolvePathFromBase(process.env.INSTANCE_LOCK_FILE ?? path.join(dataDir, "runtime.lock"), envBaseDir);
|
|
34
36
|
const bootstrapTelegramToken = String(process.env.TELEGRAM_BOT_TOKEN ?? "").trim();
|
|
35
37
|
const defaultProviderKind = normalizeProviderKind(process.env.DEFAULT_PROVIDER_KIND ?? "codex");
|
|
36
38
|
const codexBin = resolveCodexBin(process.env.CODEX_BIN);
|
|
37
|
-
const codexHomeDir =
|
|
39
|
+
const codexHomeDir = resolveOptionalPathFromBase(process.env.CODEX_HOME_DIR, envBaseDir);
|
|
40
|
+
if (codexHomeDir) {
|
|
41
|
+
process.env.CODEX_HOME_DIR = codexHomeDir;
|
|
42
|
+
process.env.CODEX_HOME = codexHomeDir;
|
|
43
|
+
}
|
|
38
44
|
const codexSandbox = normalizeCodexSandbox(process.env.CODEX_SANDBOX ?? "danger-full-access");
|
|
39
45
|
const codexApprovalPolicy = normalizeApprovalPolicy(process.env.CODEX_APPROVAL_POLICY ?? "never");
|
|
40
46
|
const turnActivityTimeoutMs = parseTurnActivityTimeoutSetting(process.env.TURN_ACTIVITY_TIMEOUT_MS ?? "0", 0);
|
|
@@ -78,6 +84,7 @@ const defaultAllowedChatIds = new Set((process.env.TELEGRAM_ALLOWED_CHAT_IDS ??
|
|
|
78
84
|
.filter(Boolean));
|
|
79
85
|
fs.mkdirSync(dataDir, { recursive: true });
|
|
80
86
|
export const config = {
|
|
87
|
+
envBaseDir,
|
|
81
88
|
defaultProviderKind,
|
|
82
89
|
providerDefaults: {
|
|
83
90
|
defaultKind: defaultProviderKind,
|
|
@@ -117,6 +124,24 @@ export const config = {
|
|
|
117
124
|
defaultSharedThreadId,
|
|
118
125
|
defaultAllowedChatIds,
|
|
119
126
|
};
|
|
127
|
+
function loadEnvironment() {
|
|
128
|
+
const configuredEnvPath = String(process.env.COPILOT_HUB_ENV_PATH ?? "").trim();
|
|
129
|
+
const resolvedEnvPath = configuredEnvPath ? path.resolve(configuredEnvPath) : "";
|
|
130
|
+
const baseDir = resolveConfigBaseDir({
|
|
131
|
+
configuredBaseDir: process.env.COPILOT_HUB_ENV_BASE_DIR,
|
|
132
|
+
configuredEnvPath: resolvedEnvPath,
|
|
133
|
+
cwd: process.cwd(),
|
|
134
|
+
});
|
|
135
|
+
if (configuredEnvPath) {
|
|
136
|
+
process.env.COPILOT_HUB_ENV_PATH = resolvedEnvPath;
|
|
137
|
+
dotenv.config({ path: resolvedEnvPath });
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
dotenv.config();
|
|
141
|
+
}
|
|
142
|
+
process.env.COPILOT_HUB_ENV_BASE_DIR = baseDir;
|
|
143
|
+
return baseDir;
|
|
144
|
+
}
|
|
120
145
|
function resolveCodexBin(rawValue) {
|
|
121
146
|
const value = String(rawValue ?? "").trim();
|
|
122
147
|
const normalized = value.toLowerCase();
|
|
@@ -124,6 +149,10 @@ function resolveCodexBin(rawValue) {
|
|
|
124
149
|
return value;
|
|
125
150
|
}
|
|
126
151
|
if (process.platform === "win32") {
|
|
152
|
+
const npmGlobalCodex = findWindowsNpmGlobalCodexBin();
|
|
153
|
+
if (npmGlobalCodex) {
|
|
154
|
+
return npmGlobalCodex;
|
|
155
|
+
}
|
|
127
156
|
const vscodeCodex = findVscodeCodexExe();
|
|
128
157
|
if (vscodeCodex) {
|
|
129
158
|
return vscodeCodex;
|
|
@@ -155,6 +184,59 @@ function findVscodeCodexExe() {
|
|
|
155
184
|
}
|
|
156
185
|
return null;
|
|
157
186
|
}
|
|
187
|
+
function findWindowsNpmGlobalCodexBin() {
|
|
188
|
+
if (process.platform !== "win32") {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
const candidates = [];
|
|
192
|
+
const appData = String(process.env.APPDATA ?? "").trim();
|
|
193
|
+
if (appData) {
|
|
194
|
+
candidates.push(path.join(appData, "npm", "codex.cmd"));
|
|
195
|
+
candidates.push(path.join(appData, "npm", "codex.exe"));
|
|
196
|
+
candidates.push(path.join(appData, "npm", "codex"));
|
|
197
|
+
}
|
|
198
|
+
const npmPrefix = readNpmPrefix();
|
|
199
|
+
if (npmPrefix) {
|
|
200
|
+
candidates.push(path.join(npmPrefix, "codex.cmd"));
|
|
201
|
+
candidates.push(path.join(npmPrefix, "codex.exe"));
|
|
202
|
+
candidates.push(path.join(npmPrefix, "codex"));
|
|
203
|
+
}
|
|
204
|
+
for (const candidate of candidates) {
|
|
205
|
+
if (fs.existsSync(candidate)) {
|
|
206
|
+
return candidate;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
function readNpmPrefix() {
|
|
212
|
+
const result = spawnNpm(["config", "get", "prefix"]);
|
|
213
|
+
if (result.error || result.status !== 0) {
|
|
214
|
+
return "";
|
|
215
|
+
}
|
|
216
|
+
const value = String(result.stdout ?? "").trim();
|
|
217
|
+
if (!value || value.toLowerCase() === "undefined") {
|
|
218
|
+
return "";
|
|
219
|
+
}
|
|
220
|
+
return value;
|
|
221
|
+
}
|
|
222
|
+
function spawnNpm(args) {
|
|
223
|
+
if (process.platform === "win32") {
|
|
224
|
+
const comspec = process.env.ComSpec || "cmd.exe";
|
|
225
|
+
const commandLine = ["npm", ...args].join(" ");
|
|
226
|
+
return spawnSync(comspec, ["/d", "/s", "/c", commandLine], {
|
|
227
|
+
cwd: envBaseDir,
|
|
228
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
229
|
+
shell: false,
|
|
230
|
+
encoding: "utf8",
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
return spawnSync("npm", args, {
|
|
234
|
+
cwd: envBaseDir,
|
|
235
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
236
|
+
shell: false,
|
|
237
|
+
encoding: "utf8",
|
|
238
|
+
});
|
|
239
|
+
}
|
|
158
240
|
function normalizeThreadMode(value) {
|
|
159
241
|
const mode = String(value ?? "single")
|
|
160
242
|
.trim()
|
|
@@ -176,13 +258,6 @@ function parseBoolean(value) {
|
|
|
176
258
|
}
|
|
177
259
|
throw new Error("Invalid boolean value in environment.");
|
|
178
260
|
}
|
|
179
|
-
function resolveOptionalPath(value) {
|
|
180
|
-
const raw = String(value ?? "").trim();
|
|
181
|
-
if (!raw) {
|
|
182
|
-
return null;
|
|
183
|
-
}
|
|
184
|
-
return path.resolve(raw);
|
|
185
|
-
}
|
|
186
261
|
function normalizeCodexSandbox(value) {
|
|
187
262
|
const mode = String(value ?? "")
|
|
188
263
|
.trim()
|
|
@@ -218,5 +293,5 @@ function resolveWorkspaceRoot(value) {
|
|
|
218
293
|
if (!raw) {
|
|
219
294
|
throw new Error("DEFAULT_WORKSPACE_ROOT must not be empty.");
|
|
220
295
|
}
|
|
221
|
-
return
|
|
296
|
+
return resolvePathFromBase(raw, envBaseDir);
|
|
222
297
|
}
|
|
@@ -5,7 +5,9 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
import express from "express";
|
|
6
6
|
import { BotManager } from "@copilot-hub/core/bot-manager";
|
|
7
7
|
import { CodexAppClient } from "@copilot-hub/core/codex-app-client";
|
|
8
|
+
import { buildShellWrappedCommandLine, requiresShellWrappedSpawn, } from "@copilot-hub/core/codex-app-utils";
|
|
8
9
|
import { loadBotRegistry } from "@copilot-hub/core/bot-registry";
|
|
10
|
+
import { invalidateCodexQuotaUsageCache } from "@copilot-hub/core/telegram-channel";
|
|
9
11
|
import { config } from "./config.js";
|
|
10
12
|
import { InstanceLock } from "@copilot-hub/core/instance-lock";
|
|
11
13
|
import { KernelControlPlane } from "@copilot-hub/core/kernel-control-plane";
|
|
@@ -215,6 +217,7 @@ function buildApiApp({ botManager, controlPlane, registryFilePath, }) {
|
|
|
215
217
|
const refreshed = await refreshRunningBotProviders({
|
|
216
218
|
botManager: requireBotManager(),
|
|
217
219
|
});
|
|
220
|
+
invalidateCodexQuotaUsageCache();
|
|
218
221
|
res.json({
|
|
219
222
|
ok: true,
|
|
220
223
|
switched: true,
|
|
@@ -273,8 +276,18 @@ function buildApiApp({ botManager, controlPlane, registryFilePath, }) {
|
|
|
273
276
|
.trim()
|
|
274
277
|
.toLowerCase();
|
|
275
278
|
const hasModel = Object.prototype.hasOwnProperty.call(req.body ?? {}, "model");
|
|
279
|
+
const hasReasoningEffort = Object.prototype.hasOwnProperty.call(req.body ?? {}, "reasoningEffort");
|
|
280
|
+
const hasServiceTier = Object.prototype.hasOwnProperty.call(req.body ?? {}, "serviceTier");
|
|
276
281
|
const rawModel = req.body?.model;
|
|
282
|
+
const rawReasoningEffort = req.body?.reasoningEffort;
|
|
283
|
+
const rawServiceTier = req.body?.serviceTier;
|
|
277
284
|
const model = rawModel === null || rawModel === undefined ? null : String(rawModel).trim();
|
|
285
|
+
const reasoningEffort = rawReasoningEffort === null || rawReasoningEffort === undefined
|
|
286
|
+
? null
|
|
287
|
+
: String(rawReasoningEffort).trim().toLowerCase();
|
|
288
|
+
const serviceTier = rawServiceTier === null || rawServiceTier === undefined
|
|
289
|
+
? null
|
|
290
|
+
: String(rawServiceTier).trim().toLowerCase();
|
|
278
291
|
if (!sandboxMode) {
|
|
279
292
|
res.status(400).json({ error: "Field 'sandboxMode' is required." });
|
|
280
293
|
return;
|
|
@@ -291,6 +304,12 @@ function buildApiApp({ botManager, controlPlane, registryFilePath, }) {
|
|
|
291
304
|
if (hasModel) {
|
|
292
305
|
payload.model = model;
|
|
293
306
|
}
|
|
307
|
+
if (hasReasoningEffort) {
|
|
308
|
+
payload.reasoningEffort = reasoningEffort;
|
|
309
|
+
}
|
|
310
|
+
if (hasServiceTier) {
|
|
311
|
+
payload.serviceTier = serviceTier;
|
|
312
|
+
}
|
|
294
313
|
const result = await controlPlane.runSystemAction(CONTROL_ACTIONS.BOTS_SET_POLICY, payload);
|
|
295
314
|
res.json(result);
|
|
296
315
|
}));
|
|
@@ -476,13 +495,21 @@ function startCodexDeviceAuthSession() {
|
|
|
476
495
|
refreshedBots: [],
|
|
477
496
|
refreshFailures: [],
|
|
478
497
|
};
|
|
479
|
-
const child =
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
498
|
+
const child = requiresShellWrappedSpawn(codexBin)
|
|
499
|
+
? spawn(buildShellWrappedCommandLine(codexBin, ["login", "--device-auth"]), {
|
|
500
|
+
cwd: config.kernelRootPath,
|
|
501
|
+
shell: true,
|
|
502
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
503
|
+
windowsHide: true,
|
|
504
|
+
env: process.env,
|
|
505
|
+
})
|
|
506
|
+
: spawn(codexBin, ["login", "--device-auth"], {
|
|
507
|
+
cwd: config.kernelRootPath,
|
|
508
|
+
shell: false,
|
|
509
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
510
|
+
windowsHide: true,
|
|
511
|
+
env: process.env,
|
|
512
|
+
});
|
|
486
513
|
session.child = child;
|
|
487
514
|
codexDeviceAuthSession = session;
|
|
488
515
|
child.stdout.on("data", (chunk) => {
|
|
@@ -507,6 +534,7 @@ function startCodexDeviceAuthSession() {
|
|
|
507
534
|
botManager: requireBotManager(),
|
|
508
535
|
})
|
|
509
536
|
.then((refreshed) => {
|
|
537
|
+
invalidateCodexQuotaUsageCache();
|
|
510
538
|
session.refreshedBots = refreshed.refreshedBotIds;
|
|
511
539
|
session.refreshFailures = refreshed.failures;
|
|
512
540
|
session.status = "succeeded";
|
|
@@ -706,6 +734,8 @@ function normalizeModelCatalog(rawModels) {
|
|
|
706
734
|
displayName: String(entry?.displayName ?? model).trim() || model,
|
|
707
735
|
description: String(entry?.description ?? "").trim(),
|
|
708
736
|
isDefault: entry?.isDefault === true,
|
|
737
|
+
supportedReasoningEfforts: normalizeReasoningCatalog(entry?.supportedReasoningEfforts),
|
|
738
|
+
defaultReasoningEffort: normalizeReasoningEffortValue(entry?.defaultReasoningEffort),
|
|
709
739
|
});
|
|
710
740
|
}
|
|
711
741
|
return normalized.sort((a, b) => {
|
|
@@ -725,17 +755,58 @@ function looksLikeCodexApiKey(value) {
|
|
|
725
755
|
}
|
|
726
756
|
return key.startsWith("sk-");
|
|
727
757
|
}
|
|
758
|
+
function normalizeReasoningCatalog(value) {
|
|
759
|
+
const options = [];
|
|
760
|
+
const seen = new Set();
|
|
761
|
+
for (const entry of Array.isArray(value) ? value : []) {
|
|
762
|
+
const option = isRecord(entry) ? entry : {};
|
|
763
|
+
const reasoningEffort = normalizeReasoningEffortValue(option.reasoningEffort ?? option.effort ?? option.id);
|
|
764
|
+
if (!reasoningEffort || seen.has(reasoningEffort)) {
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
seen.add(reasoningEffort);
|
|
768
|
+
options.push({
|
|
769
|
+
reasoningEffort,
|
|
770
|
+
description: String(option.description ?? "").trim(),
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
return options;
|
|
774
|
+
}
|
|
775
|
+
function normalizeReasoningEffortValue(value) {
|
|
776
|
+
const normalized = String(value ?? "")
|
|
777
|
+
.trim()
|
|
778
|
+
.toLowerCase();
|
|
779
|
+
if (normalized === "none" ||
|
|
780
|
+
normalized === "minimal" ||
|
|
781
|
+
normalized === "low" ||
|
|
782
|
+
normalized === "medium" ||
|
|
783
|
+
normalized === "high" ||
|
|
784
|
+
normalized === "xhigh") {
|
|
785
|
+
return normalized;
|
|
786
|
+
}
|
|
787
|
+
return null;
|
|
788
|
+
}
|
|
728
789
|
function runCodexCommand(args, { inputText = "" } = {}) {
|
|
729
790
|
const codexBin = String(config.codexBin ?? "codex").trim() || "codex";
|
|
730
|
-
const result =
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
791
|
+
const result = requiresShellWrappedSpawn(codexBin)
|
|
792
|
+
? spawnSync(buildShellWrappedCommandLine(codexBin, args), {
|
|
793
|
+
cwd: config.kernelRootPath,
|
|
794
|
+
shell: true,
|
|
795
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
796
|
+
windowsHide: true,
|
|
797
|
+
encoding: "utf8",
|
|
798
|
+
input: inputText,
|
|
799
|
+
env: process.env,
|
|
800
|
+
})
|
|
801
|
+
: spawnSync(codexBin, args, {
|
|
802
|
+
cwd: config.kernelRootPath,
|
|
803
|
+
shell: false,
|
|
804
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
805
|
+
windowsHide: true,
|
|
806
|
+
encoding: "utf8",
|
|
807
|
+
input: inputText,
|
|
808
|
+
env: process.env,
|
|
809
|
+
});
|
|
739
810
|
if (result.error) {
|
|
740
811
|
return {
|
|
741
812
|
ok: false,
|
|
@@ -764,6 +835,9 @@ function formatCodexSpawnError(codexBin, error) {
|
|
|
764
835
|
if (code === "EPERM") {
|
|
765
836
|
return `Codex binary '${codexBin}' cannot be executed (EPERM).`;
|
|
766
837
|
}
|
|
838
|
+
if (code === "EINVAL" && requiresShellWrappedSpawn(codexBin)) {
|
|
839
|
+
return `Codex binary '${codexBin}' must be launched through the Windows shell.`;
|
|
840
|
+
}
|
|
767
841
|
const message = error instanceof Error ? error.message : String(error);
|
|
768
842
|
return `Failed to execute '${codexBin}': ${firstNonEmptyLine(message)}`;
|
|
769
843
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
let cachedCodexUsage = null;
|
|
2
|
+
export function readCachedCodexQuotaSnapshot(now) {
|
|
3
|
+
if (cachedCodexUsage && now < cachedCodexUsage.expiresAt) {
|
|
4
|
+
return cachedCodexUsage.snapshot;
|
|
5
|
+
}
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
export function writeCachedCodexQuotaSnapshot(snapshot, expiresAt) {
|
|
9
|
+
cachedCodexUsage = {
|
|
10
|
+
expiresAt,
|
|
11
|
+
snapshot,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function invalidateCodexQuotaUsageCache() {
|
|
15
|
+
cachedCodexUsage = null;
|
|
16
|
+
}
|