@ccpocket/bridge 1.81.4 → 1.81.5
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 +3 -3
- package/dist/codex-process.js +135 -46
- package/dist/codex-process.js.map +1 -1
- package/dist/websocket.js +9 -11
- package/dist/websocket.js.map +1 -1
- package/package.json +1 -1
package/dist/codex-process.d.ts
CHANGED
|
@@ -226,9 +226,8 @@ export declare class CodexProcess extends EventEmitter<CodexProcessEvents> {
|
|
|
226
226
|
*/
|
|
227
227
|
archiveThread(threadId: string): Promise<void>;
|
|
228
228
|
readThread(threadId: string, includeTurns?: boolean): Promise<Record<string, unknown>>;
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
forkThread(): Promise<{
|
|
229
|
+
/** Fork at a complete turn boundary without mutating the source history. */
|
|
230
|
+
forkThreadAtUserTurn(userTurnOrdinal: number): Promise<{
|
|
232
231
|
threadId: string;
|
|
233
232
|
thread: Record<string, unknown>;
|
|
234
233
|
}>;
|
|
@@ -364,6 +363,7 @@ export declare class CodexProcess extends EventEmitter<CodexProcessEvents> {
|
|
|
364
363
|
private resetReadiness;
|
|
365
364
|
private resolveReadiness;
|
|
366
365
|
private rejectReadiness;
|
|
366
|
+
private hasBlockingUserInput;
|
|
367
367
|
private setStatus;
|
|
368
368
|
private emitMessage;
|
|
369
369
|
private extractToolUseId;
|
package/dist/codex-process.js
CHANGED
|
@@ -5,6 +5,7 @@ import { tmpdir } from "node:os";
|
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { rm, writeFile } from "node:fs/promises";
|
|
7
7
|
import { createCodexTransport, buildCodexSpawnSpec, } from "./codex-transport.js";
|
|
8
|
+
import { codexThreadToSessionHistory } from "./sessions-index.js";
|
|
8
9
|
import { codexCliJoinTarget } from "./codex-app-server-config.js";
|
|
9
10
|
import { normalizeCodexServiceTierForClient, normalizeCodexServiceTierForRpc, } from "./codex-service-tier.js";
|
|
10
11
|
import { resolvePlatformPath } from "./path-utils.js";
|
|
@@ -283,43 +284,108 @@ export class CodexProcess extends EventEmitter {
|
|
|
283
284
|
async readThread(threadId, includeTurns = true) {
|
|
284
285
|
const response = (await this.request("thread/read", {
|
|
285
286
|
threadId,
|
|
286
|
-
includeTurns,
|
|
287
|
+
includeTurns: false,
|
|
287
288
|
}));
|
|
288
289
|
const thread = response.thread;
|
|
289
290
|
if (!thread) {
|
|
290
291
|
throw new Error("thread/read returned no thread");
|
|
291
292
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
293
|
+
if (!includeTurns)
|
|
294
|
+
return thread;
|
|
295
|
+
const turns = [];
|
|
296
|
+
const seenCursors = new Set();
|
|
297
|
+
let cursor;
|
|
298
|
+
do {
|
|
299
|
+
let page;
|
|
300
|
+
try {
|
|
301
|
+
page = (await this.request("thread/turns/list", {
|
|
302
|
+
threadId,
|
|
303
|
+
...(cursor !== undefined ? { cursor } : {}),
|
|
304
|
+
limit: 50,
|
|
305
|
+
sortDirection: "asc",
|
|
306
|
+
itemsView: "full",
|
|
307
|
+
}));
|
|
308
|
+
}
|
|
309
|
+
catch (err) {
|
|
310
|
+
// New threads have metadata but no persisted turn history yet.
|
|
311
|
+
if (cursor === undefined &&
|
|
312
|
+
err instanceof CodexRpcError &&
|
|
313
|
+
err.code === -32600 &&
|
|
314
|
+
err.message.includes("is not materialized yet; thread/turns/list is unavailable before first user message")) {
|
|
315
|
+
return { ...thread, turns: [] };
|
|
316
|
+
}
|
|
317
|
+
// Older app-servers do not expose turn pagination.
|
|
318
|
+
if (!(err instanceof CodexRpcError) || err.code !== -32601)
|
|
319
|
+
throw err;
|
|
320
|
+
const legacy = (await this.request("thread/read", {
|
|
321
|
+
threadId,
|
|
322
|
+
includeTurns: true,
|
|
323
|
+
}));
|
|
324
|
+
if (!legacy.thread)
|
|
325
|
+
throw new Error("thread/read returned no thread");
|
|
326
|
+
return legacy.thread;
|
|
327
|
+
}
|
|
328
|
+
if (!Array.isArray(page.data)) {
|
|
329
|
+
throw new Error("thread/turns/list returned invalid data");
|
|
330
|
+
}
|
|
331
|
+
turns.push(...page.data);
|
|
332
|
+
if (page.nextCursor != null && typeof page.nextCursor !== "string") {
|
|
333
|
+
throw new Error("thread/turns/list returned an invalid cursor");
|
|
334
|
+
}
|
|
335
|
+
cursor = page.nextCursor ?? undefined;
|
|
336
|
+
if (cursor !== undefined) {
|
|
337
|
+
if (seenCursors.has(cursor)) {
|
|
338
|
+
throw new Error("thread/turns/list returned a repeated cursor");
|
|
339
|
+
}
|
|
340
|
+
seenCursors.add(cursor);
|
|
341
|
+
}
|
|
342
|
+
} while (cursor !== undefined);
|
|
343
|
+
return { ...thread, turns };
|
|
310
344
|
}
|
|
311
|
-
|
|
312
|
-
|
|
345
|
+
/** Fork at a complete turn boundary without mutating the source history. */
|
|
346
|
+
async forkThreadAtUserTurn(userTurnOrdinal) {
|
|
347
|
+
if (!this._threadId)
|
|
313
348
|
throw new Error("No thread ID available for fork");
|
|
349
|
+
if (!Number.isInteger(userTurnOrdinal) || userTurnOrdinal < 1) {
|
|
350
|
+
throw new Error("Invalid Codex fork target");
|
|
351
|
+
}
|
|
352
|
+
const source = await this.readThread(this._threadId);
|
|
353
|
+
let userCount = 0;
|
|
354
|
+
let lastTurnId;
|
|
355
|
+
for (const raw of Array.isArray(source.turns) ? source.turns : []) {
|
|
356
|
+
const turn = asRecord(raw);
|
|
357
|
+
if (!turn)
|
|
358
|
+
continue;
|
|
359
|
+
userCount += codexThreadToSessionHistory({ turns: [turn] }).filter((message) => message.role === "user").length;
|
|
360
|
+
if (userCount > userTurnOrdinal) {
|
|
361
|
+
throw new Error("Cannot fork within a Codex turn containing multiple user messages");
|
|
362
|
+
}
|
|
363
|
+
if (userCount === userTurnOrdinal) {
|
|
364
|
+
if (turn.status === "inProgress") {
|
|
365
|
+
throw new Error("Cannot fork an in-progress Codex turn");
|
|
366
|
+
}
|
|
367
|
+
lastTurnId = typeof turn.id === "string" ? turn.id : undefined;
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (!lastTurnId) {
|
|
372
|
+
throw new Error("Codex fork target was not found in stored history");
|
|
314
373
|
}
|
|
315
374
|
const response = (await this.request("thread/fork", {
|
|
316
375
|
threadId: this._threadId,
|
|
317
|
-
|
|
376
|
+
lastTurnId,
|
|
377
|
+
excludeTurns: true,
|
|
318
378
|
}));
|
|
319
|
-
const
|
|
320
|
-
const threadId = typeof
|
|
321
|
-
if (!
|
|
379
|
+
const metadata = asRecord(response.thread);
|
|
380
|
+
const threadId = typeof metadata?.id === "string" ? metadata.id : undefined;
|
|
381
|
+
if (!threadId)
|
|
322
382
|
throw new Error("thread/fork returned no thread id");
|
|
383
|
+
const thread = await this.readThread(threadId);
|
|
384
|
+
// Older servers may silently ignore lastTurnId. Never resume excess history.
|
|
385
|
+
const turns = Array.isArray(thread.turns) ? thread.turns : [];
|
|
386
|
+
const forkedUserCount = codexThreadToSessionHistory(thread).filter((message) => message.role === "user").length;
|
|
387
|
+
if (asRecord(turns.at(-1))?.id !== lastTurnId || forkedUserCount !== userTurnOrdinal) {
|
|
388
|
+
throw new Error("Codex did not fork at the requested turn; update Codex CLI");
|
|
323
389
|
}
|
|
324
390
|
return { threadId, thread };
|
|
325
391
|
}
|
|
@@ -637,7 +703,9 @@ export class CodexProcess extends EventEmitter {
|
|
|
637
703
|
this.pendingApprovals.delete(pending.toolUseId);
|
|
638
704
|
this.respondToServerRequest(pending.requestId, buildApprovalResponse(pending, "accept"));
|
|
639
705
|
this.emitToolResult(pending.toolUseId, "Approved", "approved");
|
|
640
|
-
if (this.pendingApprovals.size === 0
|
|
706
|
+
if (this.pendingApprovals.size === 0 &&
|
|
707
|
+
!this.hasBlockingUserInput() &&
|
|
708
|
+
!this.pendingPlanCompletion) {
|
|
641
709
|
this.setStatus("running");
|
|
642
710
|
}
|
|
643
711
|
return true;
|
|
@@ -655,7 +723,9 @@ export class CodexProcess extends EventEmitter {
|
|
|
655
723
|
this.pendingApprovals.delete(pending.toolUseId);
|
|
656
724
|
this.respondToServerRequest(pending.requestId, buildApprovalResponse(pending, "acceptForSession"));
|
|
657
725
|
this.emitToolResult(pending.toolUseId, "Approved (always)", "approved_for_session");
|
|
658
|
-
if (this.pendingApprovals.size === 0
|
|
726
|
+
if (this.pendingApprovals.size === 0 &&
|
|
727
|
+
!this.hasBlockingUserInput() &&
|
|
728
|
+
!this.pendingPlanCompletion) {
|
|
659
729
|
this.setStatus("running");
|
|
660
730
|
}
|
|
661
731
|
return true;
|
|
@@ -678,7 +748,9 @@ export class CodexProcess extends EventEmitter {
|
|
|
678
748
|
this.pendingApprovals.delete(pending.toolUseId);
|
|
679
749
|
this.respondToServerRequest(pending.requestId, buildApprovalResponse(pending, resolveApprovalRejectDecision(pending)));
|
|
680
750
|
this.emitToolResult(pending.toolUseId, "Rejected", "rejected");
|
|
681
|
-
if (this.pendingApprovals.size === 0
|
|
751
|
+
if (this.pendingApprovals.size === 0 &&
|
|
752
|
+
!this.hasBlockingUserInput() &&
|
|
753
|
+
!this.pendingPlanCompletion) {
|
|
682
754
|
this.setStatus("running");
|
|
683
755
|
}
|
|
684
756
|
return true;
|
|
@@ -692,8 +764,12 @@ export class CodexProcess extends EventEmitter {
|
|
|
692
764
|
this.pendingUserInputs.delete(pending.toolUseId);
|
|
693
765
|
this.respondToServerRequest(pending.requestId, buildUserInputResponse(pending, result));
|
|
694
766
|
this.emitToolResult(pending.toolUseId, "Answered", "answered");
|
|
695
|
-
if (this.pendingApprovals.size === 0 &&
|
|
696
|
-
this.
|
|
767
|
+
if (this.pendingApprovals.size === 0 &&
|
|
768
|
+
!this.hasBlockingUserInput() &&
|
|
769
|
+
!this.pendingPlanCompletion) {
|
|
770
|
+
if (pending.isBlocking !== false || this.pendingTurnId) {
|
|
771
|
+
this.setStatus("running");
|
|
772
|
+
}
|
|
697
773
|
}
|
|
698
774
|
return true;
|
|
699
775
|
}
|
|
@@ -831,8 +907,8 @@ export class CodexProcess extends EventEmitter {
|
|
|
831
907
|
resolvePendingUserInput(toolUseId) {
|
|
832
908
|
if (toolUseId)
|
|
833
909
|
return this.pendingUserInputs.get(toolUseId);
|
|
834
|
-
const
|
|
835
|
-
return
|
|
910
|
+
const requests = [...this.pendingUserInputs.values()];
|
|
911
|
+
return requests.find((request) => request.isBlocking !== false) ?? requests[0];
|
|
836
912
|
}
|
|
837
913
|
/**
|
|
838
914
|
* Approve a pending user-input request (McpElicitation fallback).
|
|
@@ -858,7 +934,9 @@ export class CodexProcess extends EventEmitter {
|
|
|
858
934
|
this.emitToolResult(pending.toolUseId, "Approved", result === "Allow for this session"
|
|
859
935
|
? "approved_for_session"
|
|
860
936
|
: "approved");
|
|
861
|
-
if (this.pendingApprovals.size === 0 &&
|
|
937
|
+
if (this.pendingApprovals.size === 0 &&
|
|
938
|
+
!this.hasBlockingUserInput() &&
|
|
939
|
+
!this.pendingPlanCompletion) {
|
|
862
940
|
this.setStatus("running");
|
|
863
941
|
}
|
|
864
942
|
return true;
|
|
@@ -884,7 +962,9 @@ export class CodexProcess extends EventEmitter {
|
|
|
884
962
|
toolUseId: pending.toolUseId,
|
|
885
963
|
});
|
|
886
964
|
this.emitToolResult(pending.toolUseId, toolResult);
|
|
887
|
-
if (this.pendingApprovals.size === 0 &&
|
|
965
|
+
if (this.pendingApprovals.size === 0 &&
|
|
966
|
+
!this.hasBlockingUserInput() &&
|
|
967
|
+
!this.pendingPlanCompletion) {
|
|
888
968
|
this.setStatus("running");
|
|
889
969
|
}
|
|
890
970
|
}
|
|
@@ -898,7 +978,9 @@ export class CodexProcess extends EventEmitter {
|
|
|
898
978
|
this.pendingUserInputs.delete(pending.toolUseId);
|
|
899
979
|
this.respondToServerRequest(pending.requestId, buildUserInputResponse(pending, resolveUserInputRejectResult(pending, result)));
|
|
900
980
|
this.emitToolResult(pending.toolUseId, "Rejected", "rejected");
|
|
901
|
-
if (this.pendingApprovals.size === 0 &&
|
|
981
|
+
if (this.pendingApprovals.size === 0 &&
|
|
982
|
+
!this.hasBlockingUserInput() &&
|
|
983
|
+
!this.pendingPlanCompletion) {
|
|
902
984
|
this.setStatus("running");
|
|
903
985
|
}
|
|
904
986
|
return true;
|
|
@@ -1040,6 +1122,7 @@ export class CodexProcess extends EventEmitter {
|
|
|
1040
1122
|
const method = options?.threadId ? "thread/resume" : "thread/start";
|
|
1041
1123
|
if (options?.threadId) {
|
|
1042
1124
|
threadParams.threadId = options.threadId;
|
|
1125
|
+
threadParams.excludeTurns = true;
|
|
1043
1126
|
}
|
|
1044
1127
|
else {
|
|
1045
1128
|
threadParams.experimentalRawEvents = false;
|
|
@@ -1634,6 +1717,7 @@ export class CodexProcess extends EventEmitter {
|
|
|
1634
1717
|
const toolUseId = this.extractToolUseId(params, id);
|
|
1635
1718
|
const questions = normalizeUserInputQuestions(params.questions);
|
|
1636
1719
|
const input = {
|
|
1720
|
+
isBlocking: params.isBlocking !== false,
|
|
1637
1721
|
questions: questions.map((q) => ({
|
|
1638
1722
|
id: q.id,
|
|
1639
1723
|
question: q.question,
|
|
@@ -1654,6 +1738,7 @@ export class CodexProcess extends EventEmitter {
|
|
|
1654
1738
|
})),
|
|
1655
1739
|
input,
|
|
1656
1740
|
kind: "questions",
|
|
1741
|
+
isBlocking: params.isBlocking !== false,
|
|
1657
1742
|
});
|
|
1658
1743
|
this.emitMessage({
|
|
1659
1744
|
type: "permission_request",
|
|
@@ -1661,7 +1746,8 @@ export class CodexProcess extends EventEmitter {
|
|
|
1661
1746
|
toolName: "AskUserQuestion",
|
|
1662
1747
|
input,
|
|
1663
1748
|
});
|
|
1664
|
-
|
|
1749
|
+
if (params.isBlocking !== false)
|
|
1750
|
+
this.setStatus("waiting_approval");
|
|
1665
1751
|
break;
|
|
1666
1752
|
}
|
|
1667
1753
|
case "item/permissions/requestApproval": {
|
|
@@ -2010,7 +2096,7 @@ export class CodexProcess extends EventEmitter {
|
|
|
2010
2096
|
else {
|
|
2011
2097
|
this.lastPlanItemText = null;
|
|
2012
2098
|
if (this.pendingApprovals.size === 0 &&
|
|
2013
|
-
this.
|
|
2099
|
+
!this.hasBlockingUserInput()) {
|
|
2014
2100
|
this.setStatus("idle");
|
|
2015
2101
|
}
|
|
2016
2102
|
}
|
|
@@ -2588,6 +2674,9 @@ export class CodexProcess extends EventEmitter {
|
|
|
2588
2674
|
waiter.reject(error);
|
|
2589
2675
|
this.readinessWaiters.clear();
|
|
2590
2676
|
}
|
|
2677
|
+
hasBlockingUserInput() {
|
|
2678
|
+
return [...this.pendingUserInputs.values()].some((request) => request.isBlocking !== false);
|
|
2679
|
+
}
|
|
2591
2680
|
setStatus(status) {
|
|
2592
2681
|
if (this._status !== status) {
|
|
2593
2682
|
this._status = status;
|
|
@@ -2631,7 +2720,7 @@ export class CodexProcess extends EventEmitter {
|
|
|
2631
2720
|
}
|
|
2632
2721
|
if (!this.pendingPlanCompletion &&
|
|
2633
2722
|
this.pendingApprovals.size === 0 &&
|
|
2634
|
-
this.
|
|
2723
|
+
!this.hasBlockingUserInput()) {
|
|
2635
2724
|
this.setStatus(this.pendingTurnId ? "running" : "idle");
|
|
2636
2725
|
}
|
|
2637
2726
|
}
|
|
@@ -2910,16 +2999,16 @@ function extractReasoningEfforts(raw) {
|
|
|
2910
2999
|
return efforts;
|
|
2911
3000
|
}
|
|
2912
3001
|
function extractServiceTiers(raw) {
|
|
2913
|
-
//
|
|
3002
|
+
// Older app-server versions expose the user-facing Fast option as
|
|
2914
3003
|
// `additionalSpeedTiers: ["fast"]`, while the lower-level service tier is
|
|
2915
|
-
// advertised as `{ id: "priority", name: "Fast" }`.
|
|
2916
|
-
//
|
|
2917
|
-
|
|
2918
|
-
|
|
3004
|
+
// advertised as `{ id: "priority", name: "Fast" }`. Prefer the current
|
|
3005
|
+
// catalog and normalize its values for the client.
|
|
3006
|
+
// The current catalog is authoritative, including an explicitly empty list.
|
|
3007
|
+
const values = Array.isArray(raw.serviceTiers)
|
|
3008
|
+
? raw.serviceTiers
|
|
3009
|
+
: Array.isArray(raw.additionalSpeedTiers)
|
|
2919
3010
|
? raw.additionalSpeedTiers
|
|
2920
|
-
: []
|
|
2921
|
-
...(Array.isArray(raw.serviceTiers) ? raw.serviceTiers : []),
|
|
2922
|
-
];
|
|
3011
|
+
: [];
|
|
2923
3012
|
const seen = new Set();
|
|
2924
3013
|
const tiers = [];
|
|
2925
3014
|
for (const value of values) {
|