ccus-cli 0.2.29 → 0.2.30
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/dist/lib/codex-fetcher.js +53 -10
- package/package.json +1 -1
|
@@ -20,6 +20,7 @@ const paths_1 = require("./paths");
|
|
|
20
20
|
// app-server 子进程参数:read-only + never,避免新版 Codex 拒绝已移除的 untrusted 审批策略。
|
|
21
21
|
const CODEX_ARGS = ["-s", "read-only", "-a", "never", "app-server"];
|
|
22
22
|
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
23
|
+
const CLEANUP_TIMEOUT_MS = 2_000;
|
|
23
24
|
// notify 每 turn 触发,额度缓存 5 分钟,命中秒回避免阻塞 Codex 主流程。
|
|
24
25
|
const DEFAULT_TTL_MS = 5 * 60 * 1000;
|
|
25
26
|
// ChatGPT 后端 wham/usage 回退:app-server 未返回可用额度时走,直连读 auth.json 的 OAuth token。
|
|
@@ -187,6 +188,38 @@ const defaultSpawn = (command, args, options) => {
|
|
|
187
188
|
const merged = process.platform === "win32" ? { ...options, shell: true } : options;
|
|
188
189
|
return (0, node_child_process_1.spawn)(command, args, merged);
|
|
189
190
|
};
|
|
191
|
+
/** Windows 必须先清理整棵树,再关闭管道;先杀 shell 会失去后代进程的关联。 */
|
|
192
|
+
async function cleanupCodexChild(child, kill) {
|
|
193
|
+
try {
|
|
194
|
+
if (kill) {
|
|
195
|
+
if (process.platform === "win32" && child.pid !== undefined) {
|
|
196
|
+
await new Promise((resolve, reject) => {
|
|
197
|
+
(0, node_child_process_1.execFile)("taskkill.exe", ["/PID", String(child.pid), "/T", "/F"], {
|
|
198
|
+
windowsHide: true,
|
|
199
|
+
timeout: CLEANUP_TIMEOUT_MS,
|
|
200
|
+
}, (error) => error ? reject(error) : resolve());
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
child.kill();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
(0, debug_1.debugLog)("codex", "process cleanup failed", error instanceof Error ? error.message : String(error));
|
|
210
|
+
try {
|
|
211
|
+
child.kill();
|
|
212
|
+
}
|
|
213
|
+
catch { /* 清理失败不覆盖查询结果。 */ }
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
// 即使 taskkill 失败,也不让后代继承的管道或进程句柄阻塞 Hook 退出。
|
|
217
|
+
child.stdin.destroy();
|
|
218
|
+
child.stdout.destroy();
|
|
219
|
+
child.stderr.destroy();
|
|
220
|
+
child.unref();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
190
223
|
/**
|
|
191
224
|
* spawn `codex app-server` 走 JSON-RPC 握手 + `account/rateLimits/read` 拉额度。
|
|
192
225
|
*
|
|
@@ -220,16 +253,12 @@ async function fetchCodexQuota(options = {}) {
|
|
|
220
253
|
clearTimeout(timer);
|
|
221
254
|
timer = null;
|
|
222
255
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
catch {
|
|
229
|
-
// kill 失败不影响结果。
|
|
230
|
-
}
|
|
256
|
+
if (child) {
|
|
257
|
+
void cleanupCodexChild(child, kill).then(() => resolve(outcome));
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
resolve(outcome);
|
|
231
261
|
}
|
|
232
|
-
resolve(outcome);
|
|
233
262
|
};
|
|
234
263
|
try {
|
|
235
264
|
child = spawnFn("codex", CODEX_ARGS, {
|
|
@@ -247,17 +276,30 @@ async function fetchCodexQuota(options = {}) {
|
|
|
247
276
|
(0, debug_1.debugLog)("codex", "rpc timeout", { timeoutMs });
|
|
248
277
|
finish(empty("error"), true);
|
|
249
278
|
}, timeoutMs);
|
|
279
|
+
// 管道错误(例如清理期间的 EPIPE)是异步事件,不能仅靠 write 的 try/catch。
|
|
280
|
+
for (const stream of [child.stdin, child.stdout, child.stderr]) {
|
|
281
|
+
stream.on("error", (error) => {
|
|
282
|
+
(0, debug_1.debugLog)("codex", "stdio error", error.message);
|
|
283
|
+
finish(empty("error"), true);
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
child.stderr.resume();
|
|
250
287
|
const send = (message) => {
|
|
288
|
+
if (settled)
|
|
289
|
+
return;
|
|
251
290
|
try {
|
|
252
291
|
child?.stdin.write(message);
|
|
253
292
|
}
|
|
254
293
|
catch (error) {
|
|
255
294
|
(0, debug_1.debugLog)("codex", "stdin write failed", error instanceof Error ? error.message : String(error));
|
|
295
|
+
finish(empty("error"), true);
|
|
256
296
|
}
|
|
257
297
|
};
|
|
258
298
|
const initId = (rpcId += 1);
|
|
259
299
|
send(buildRpcMessage(initId, "initialize", { clientInfo: { name: "ccus", version: "1.0.0" } }));
|
|
260
300
|
child.stdout.on("data", (chunk) => {
|
|
301
|
+
if (settled)
|
|
302
|
+
return;
|
|
261
303
|
buffer += chunk.toString();
|
|
262
304
|
let newline;
|
|
263
305
|
while ((newline = buffer.indexOf("\n")) !== -1) {
|
|
@@ -292,13 +334,14 @@ async function fetchCodexQuota(options = {}) {
|
|
|
292
334
|
const quota = parseRateLimitsResult(message.result);
|
|
293
335
|
(0, debug_1.debugLog)("codex", "quota parsed", quota);
|
|
294
336
|
finish({ ...quota, status: "ok" }, true);
|
|
337
|
+
return;
|
|
295
338
|
}
|
|
296
339
|
}
|
|
297
340
|
});
|
|
298
341
|
child.on("error", (err) => {
|
|
299
342
|
const enoent = err.code === "ENOENT";
|
|
300
343
|
(0, debug_1.debugLog)("codex", "spawn error", { code: err.code, message: err.message });
|
|
301
|
-
finish(empty(enoent ? "unavailable" : "error"));
|
|
344
|
+
finish(empty(enoent ? "unavailable" : "error"), true);
|
|
302
345
|
});
|
|
303
346
|
child.on("close", (code) => {
|
|
304
347
|
(0, debug_1.debugLog)("codex", "app-server exited before quota", { code });
|