@ccpocket/bridge 1.86.2 → 1.88.0
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/codex-process.d.ts +11 -0
- package/dist/codex-process.js +99 -1
- package/dist/codex-process.js.map +1 -1
- package/dist/codex-recovery.d.ts +35 -0
- package/dist/codex-recovery.js +121 -0
- package/dist/codex-recovery.js.map +1 -0
- package/dist/parser.d.ts +25 -0
- package/dist/parser.js +20 -0
- package/dist/parser.js.map +1 -1
- package/dist/performance-mode.d.ts +4 -0
- package/dist/performance-mode.js +58 -0
- package/dist/performance-mode.js.map +1 -0
- package/dist/session.js +3 -1
- package/dist/session.js.map +1 -1
- package/dist/sessions-index.js +138 -30
- package/dist/sessions-index.js.map +1 -1
- package/dist/websocket.d.ts +3 -0
- package/dist/websocket.js +103 -18
- package/dist/websocket.js.map +1 -1
- package/package.json +1 -1
package/dist/codex-process.d.ts
CHANGED
|
@@ -211,6 +211,17 @@ export declare class CodexProcess extends EventEmitter<CodexProcessEvents> {
|
|
|
211
211
|
* Sends thread/name/set which persists to ~/.codex/session_index.jsonl.
|
|
212
212
|
*/
|
|
213
213
|
renameThread(name: string): Promise<void>;
|
|
214
|
+
private manualInputVersion;
|
|
215
|
+
private recoveryFailure;
|
|
216
|
+
private upstreamWillRetry;
|
|
217
|
+
private inputTurnStarted;
|
|
218
|
+
private recoveryNotice;
|
|
219
|
+
private readonly recovery;
|
|
220
|
+
getRecoveryState(): import("./codex-recovery.js").CodexRecoveryState;
|
|
221
|
+
setRecoveryEnabled(enabled: boolean): void;
|
|
222
|
+
cancelRecovery(): void;
|
|
223
|
+
noteManualInput(): void;
|
|
224
|
+
private canDispatchRecovery;
|
|
214
225
|
private goalLookup;
|
|
215
226
|
/** Read the persisted goal attached to this Codex thread. */
|
|
216
227
|
getGoal(timeoutMs?: number): Promise<CodexGoal | null>;
|
package/dist/codex-process.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CodexRecovery } from "./codex-recovery.js";
|
|
1
2
|
import { EventEmitter } from "node:events";
|
|
2
3
|
import { randomUUID } from "node:crypto";
|
|
3
4
|
import { realpathSync, statSync } from "node:fs";
|
|
@@ -240,6 +241,57 @@ export class CodexProcess extends EventEmitter {
|
|
|
240
241
|
name,
|
|
241
242
|
});
|
|
242
243
|
}
|
|
244
|
+
manualInputVersion = 0;
|
|
245
|
+
recoveryFailure = null;
|
|
246
|
+
upstreamWillRetry = false;
|
|
247
|
+
inputTurnStarted = false;
|
|
248
|
+
recoveryNotice = "";
|
|
249
|
+
recovery = new CodexRecovery({
|
|
250
|
+
changed: (recovery) => {
|
|
251
|
+
this.emitMessage({ type: "codex_recovery_state", recovery });
|
|
252
|
+
const notice = `${recovery.phase}:${recovery.reason}`;
|
|
253
|
+
if (notice !== this.recoveryNotice && ["waiting", "blocked", "exhausted"].includes(recovery.phase)) {
|
|
254
|
+
this.emitMessage({ type: "error", errorCode: "codex_recovery_notice", message: recovery.phase === "waiting"
|
|
255
|
+
? `Automatic recovery is waiting for the usage limit (${recovery.attempts}/${recovery.maxAttempts} attempts used). You can cancel or send a message. ${recovery.reason ?? ""}`
|
|
256
|
+
: `Automatic recovery ${recovery.phase}. ${recovery.reason ?? ""}` });
|
|
257
|
+
}
|
|
258
|
+
this.recoveryNotice = notice;
|
|
259
|
+
},
|
|
260
|
+
canResume: async () => {
|
|
261
|
+
if (!this.canDispatchRecovery())
|
|
262
|
+
return false;
|
|
263
|
+
const goal = await this.getGoal();
|
|
264
|
+
return this.canDispatchRecovery() && (goal === null ||
|
|
265
|
+
((goal.status === "active" || goal.status === "usageLimited") &&
|
|
266
|
+
!(typeof goal.tokenBudget === "number" && goal.tokensUsed >= goal.tokenBudget)));
|
|
267
|
+
},
|
|
268
|
+
dispatch: (input) => {
|
|
269
|
+
if (!this.canDispatchRecovery())
|
|
270
|
+
return false;
|
|
271
|
+
const resolve = this.inputResolve;
|
|
272
|
+
this.inputResolve = null;
|
|
273
|
+
resolve({ ...input, recoveryGeneration: this.recovery.version });
|
|
274
|
+
return true;
|
|
275
|
+
},
|
|
276
|
+
resetAt: async () => {
|
|
277
|
+
const response = await this.readRateLimits(3_000);
|
|
278
|
+
const limits = response.rateLimitsByLimitId?.codex ?? response.rateLimits;
|
|
279
|
+
const resets = [limits?.primary, limits?.secondary]
|
|
280
|
+
.filter((window) => window && window.usedPercent >= 100 && typeof window.resetsAt === "number" && Number.isFinite(window.resetsAt))
|
|
281
|
+
.map((window) => window.resetsAt * 1000);
|
|
282
|
+
return resets.length ? Math.max(...resets) : undefined;
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
getRecoveryState() { return this.recovery.state; }
|
|
286
|
+
setRecoveryEnabled(enabled) { this.recovery.setEnabled(enabled); }
|
|
287
|
+
cancelRecovery() { this.manualInputVersion++; this.recovery.cancel(); }
|
|
288
|
+
noteManualInput() { this.manualInputVersion++; this.recovery.manualInput(); }
|
|
289
|
+
canDispatchRecovery() {
|
|
290
|
+
return !this.stopped && this._threadId !== null && this.inputResolve !== null &&
|
|
291
|
+
this.pendingTurnId === null && this.pendingTurnCompletion === null &&
|
|
292
|
+
this.pendingApprovals.size === 0 && !this.hasBlockingUserInput() &&
|
|
293
|
+
this.pendingPlanCompletion === null && this._pendingPlanInput === null;
|
|
294
|
+
}
|
|
243
295
|
goalLookup;
|
|
244
296
|
/** Read the persisted goal attached to this Codex thread. */
|
|
245
297
|
async getGoal(timeoutMs = 3_000) {
|
|
@@ -258,6 +310,7 @@ export class CodexProcess extends EventEmitter {
|
|
|
258
310
|
}
|
|
259
311
|
/** Create or update the persisted goal attached to this Codex thread. */
|
|
260
312
|
async setGoal(update) {
|
|
313
|
+
this.cancelRecovery();
|
|
261
314
|
if (!this._threadId) {
|
|
262
315
|
throw new Error("No thread ID available for goal update");
|
|
263
316
|
}
|
|
@@ -272,6 +325,7 @@ export class CodexProcess extends EventEmitter {
|
|
|
272
325
|
}
|
|
273
326
|
/** Remove the persisted goal attached to this Codex thread. */
|
|
274
327
|
async clearGoal() {
|
|
328
|
+
this.cancelRecovery();
|
|
275
329
|
if (!this._threadId) {
|
|
276
330
|
throw new Error("No thread ID available for goal clear");
|
|
277
331
|
}
|
|
@@ -601,6 +655,7 @@ export class CodexProcess extends EventEmitter {
|
|
|
601
655
|
}
|
|
602
656
|
stop() {
|
|
603
657
|
this.stopped = true;
|
|
658
|
+
this.recovery.setEnabled(false);
|
|
604
659
|
this.rejectReadiness(new Error("codex app-server stopped"));
|
|
605
660
|
if (this.inputResolve) {
|
|
606
661
|
this.inputResolve({ text: "" });
|
|
@@ -621,6 +676,7 @@ export class CodexProcess extends EventEmitter {
|
|
|
621
676
|
}
|
|
622
677
|
prepareLaunch(projectPath, options) {
|
|
623
678
|
this.stopped = false;
|
|
679
|
+
this.recovery.setEnabled(false);
|
|
624
680
|
this._threadId = null;
|
|
625
681
|
this._agentNickname = null;
|
|
626
682
|
this._agentRole = null;
|
|
@@ -702,6 +758,7 @@ export class CodexProcess extends EventEmitter {
|
|
|
702
758
|
transport.start(projectPath);
|
|
703
759
|
}
|
|
704
760
|
interrupt() {
|
|
761
|
+
this.cancelRecovery();
|
|
705
762
|
if (!this._threadId || !this.pendingTurnId)
|
|
706
763
|
return;
|
|
707
764
|
void this.request("turn/interrupt", {
|
|
@@ -714,6 +771,7 @@ export class CodexProcess extends EventEmitter {
|
|
|
714
771
|
});
|
|
715
772
|
}
|
|
716
773
|
sendInput(text) {
|
|
774
|
+
this.noteManualInput();
|
|
717
775
|
if (!this.inputResolve) {
|
|
718
776
|
console.error("[codex-process] No pending input resolver for sendInput");
|
|
719
777
|
return;
|
|
@@ -723,6 +781,7 @@ export class CodexProcess extends EventEmitter {
|
|
|
723
781
|
resolve({ text });
|
|
724
782
|
}
|
|
725
783
|
sendInputWithImages(text, images) {
|
|
784
|
+
this.noteManualInput();
|
|
726
785
|
if (!this.inputResolve) {
|
|
727
786
|
console.error("[codex-process] No pending input resolver for sendInputWithImages");
|
|
728
787
|
return;
|
|
@@ -735,6 +794,7 @@ export class CodexProcess extends EventEmitter {
|
|
|
735
794
|
this.sendInputStructured(text, { skills: [skill] });
|
|
736
795
|
}
|
|
737
796
|
sendInputStructured(text, options) {
|
|
797
|
+
this.noteManualInput();
|
|
738
798
|
if (!this.inputResolve) {
|
|
739
799
|
console.error("[codex-process] No pending input resolver for sendInputStructured");
|
|
740
800
|
return;
|
|
@@ -749,6 +809,7 @@ export class CodexProcess extends EventEmitter {
|
|
|
749
809
|
});
|
|
750
810
|
}
|
|
751
811
|
async steerInputStructured(text, options) {
|
|
812
|
+
this.noteManualInput();
|
|
752
813
|
if (!this._threadId || !this.pendingTurnId) {
|
|
753
814
|
throw new Error("No active Codex turn to steer");
|
|
754
815
|
}
|
|
@@ -1600,10 +1661,21 @@ export class CodexProcess extends EventEmitter {
|
|
|
1600
1661
|
});
|
|
1601
1662
|
continue;
|
|
1602
1663
|
}
|
|
1664
|
+
const manualVersion = this.manualInputVersion;
|
|
1665
|
+
this.recoveryFailure = null;
|
|
1666
|
+
this.upstreamWillRetry = false;
|
|
1667
|
+
this.inputTurnStarted = false;
|
|
1668
|
+
let startFailure = null;
|
|
1603
1669
|
const { input, tempPaths } = await this.toRpcInput(pendingInput);
|
|
1604
|
-
if (!input
|
|
1670
|
+
if (!input || (pendingInput.recoveryGeneration !== undefined &&
|
|
1671
|
+
(pendingInput.recoveryGeneration !== this.recovery.version || !this.recovery.state.enabled))) {
|
|
1672
|
+
await Promise.all(tempPaths.map((path) => rm(path, { force: true }).catch(() => { })));
|
|
1605
1673
|
continue;
|
|
1606
1674
|
}
|
|
1675
|
+
if (pendingInput.recoveryContinuation && !pendingInput.recoveryEchoed) {
|
|
1676
|
+
pendingInput.recoveryEchoed = true;
|
|
1677
|
+
this.emitMessage({ type: "user_input", text: pendingInput.text, timestamp: new Date().toISOString() });
|
|
1678
|
+
}
|
|
1607
1679
|
this.setStatus("running");
|
|
1608
1680
|
this.lastTokenUsage = null;
|
|
1609
1681
|
const completion = await new Promise((resolve, reject) => {
|
|
@@ -1657,6 +1729,9 @@ export class CodexProcess extends EventEmitter {
|
|
|
1657
1729
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
1658
1730
|
});
|
|
1659
1731
|
}).catch((err) => {
|
|
1732
|
+
// Only a confirmed turn/start rejection can replay original attachments.
|
|
1733
|
+
if (err instanceof CodexRpcError && !this.inputTurnStarted && !this.pendingTurnId)
|
|
1734
|
+
startFailure = err;
|
|
1660
1735
|
if (!this.stopped) {
|
|
1661
1736
|
const message = err instanceof Error ? err.message : String(err);
|
|
1662
1737
|
this.emitMessage({ type: "error", message });
|
|
@@ -1671,6 +1746,14 @@ export class CodexProcess extends EventEmitter {
|
|
|
1671
1746
|
});
|
|
1672
1747
|
await Promise.all(tempPaths.map((path) => rm(path, { force: true }).catch(() => { })));
|
|
1673
1748
|
void completion;
|
|
1749
|
+
if (!this.stopped && manualVersion === this.manualInputVersion && !this.upstreamWillRetry) {
|
|
1750
|
+
const failure = startFailure ?? this.recoveryFailure;
|
|
1751
|
+
if (failure)
|
|
1752
|
+
this.recovery.schedule(failure, startFailure ? pendingInput : {
|
|
1753
|
+
text: "Automatic recovery: Continue exactly where you left off. Do not repeat completed work. If the task is already complete, stop.",
|
|
1754
|
+
recoveryContinuation: true,
|
|
1755
|
+
});
|
|
1756
|
+
}
|
|
1674
1757
|
}
|
|
1675
1758
|
}
|
|
1676
1759
|
handleStdoutChunk(chunk) {
|
|
@@ -1955,6 +2038,8 @@ export class CodexProcess extends EventEmitter {
|
|
|
1955
2038
|
break;
|
|
1956
2039
|
}
|
|
1957
2040
|
case "turn/started": {
|
|
2041
|
+
this.inputTurnStarted = true;
|
|
2042
|
+
this.recovery.cancel();
|
|
1958
2043
|
const turn = params.turn;
|
|
1959
2044
|
if (typeof turn?.id === "string") {
|
|
1960
2045
|
this.pendingTurnId = turn.id;
|
|
@@ -1976,6 +2061,10 @@ export class CodexProcess extends EventEmitter {
|
|
|
1976
2061
|
}
|
|
1977
2062
|
case "thread/goal/updated": {
|
|
1978
2063
|
try {
|
|
2064
|
+
const goal = parseCodexGoal(params.goal);
|
|
2065
|
+
if (!["active", "usageLimited"].includes(goal.status) ||
|
|
2066
|
+
(typeof goal.tokenBudget === "number" && goal.tokensUsed >= goal.tokenBudget))
|
|
2067
|
+
this.cancelRecovery();
|
|
1979
2068
|
this.emitMessage({
|
|
1980
2069
|
type: "goal_state",
|
|
1981
2070
|
goal: parseCodexGoal(params.goal),
|
|
@@ -1987,6 +2076,7 @@ export class CodexProcess extends EventEmitter {
|
|
|
1987
2076
|
break;
|
|
1988
2077
|
}
|
|
1989
2078
|
case "thread/goal/cleared": {
|
|
2079
|
+
this.cancelRecovery();
|
|
1990
2080
|
this.emitMessage({ type: "goal_state", goal: null });
|
|
1991
2081
|
break;
|
|
1992
2082
|
}
|
|
@@ -2135,6 +2225,7 @@ export class CodexProcess extends EventEmitter {
|
|
|
2135
2225
|
const error = asRecord(params.error);
|
|
2136
2226
|
const message = stringValue(error?.message) ?? "Codex runtime error";
|
|
2137
2227
|
if (params.willRetry === true) {
|
|
2228
|
+
this.upstreamWillRetry = true;
|
|
2138
2229
|
console.warn(`[codex-process] Codex will retry: ${message}`);
|
|
2139
2230
|
break;
|
|
2140
2231
|
}
|
|
@@ -2174,6 +2265,13 @@ export class CodexProcess extends EventEmitter {
|
|
|
2174
2265
|
this.lastTokenUsage = null;
|
|
2175
2266
|
if (status === "failed") {
|
|
2176
2267
|
const errorObj = turn?.error;
|
|
2268
|
+
// A terminal failure ends upstream retry ownership. Warnings alone never
|
|
2269
|
+
// schedule work, and an explicitly retrying terminal payload is excluded.
|
|
2270
|
+
if (this.pendingTurnCompletion && errorObj?.willRetry !== true &&
|
|
2271
|
+
(!this.pendingTurnId || !turn?.id || turn.id === this.pendingTurnId)) {
|
|
2272
|
+
this.upstreamWillRetry = false;
|
|
2273
|
+
this.recoveryFailure = errorObj;
|
|
2274
|
+
}
|
|
2177
2275
|
const message = typeof errorObj?.message === "string"
|
|
2178
2276
|
? errorObj.message
|
|
2179
2277
|
: "Turn failed";
|