@ccpocket/bridge 1.68.1 → 1.69.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.js +43 -4
- package/dist/codex-process.js.map +1 -1
- package/dist/parser.d.ts +5 -0
- package/dist/parser.js.map +1 -1
- package/dist/session.d.ts +11 -0
- package/dist/session.js +4 -1
- package/dist/session.js.map +1 -1
- package/dist/websocket.d.ts +3 -0
- package/dist/websocket.js +184 -80
- package/dist/websocket.js.map +1 -1
- package/package.json +1 -1
package/dist/websocket.js
CHANGED
|
@@ -5,7 +5,7 @@ import { lstat, readFile, readlink, stat, unlink } from "node:fs/promises";
|
|
|
5
5
|
import { resolve, extname } from "node:path";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
7
|
import { WebSocketServer, WebSocket } from "ws";
|
|
8
|
-
import { SessionManager, } from "./session.js";
|
|
8
|
+
import { SessionManager, MAX_HISTORY_PER_SESSION, } from "./session.js";
|
|
9
9
|
import { SdkProcess, listAvailableClaudeModels, } from "./sdk-process.js";
|
|
10
10
|
import { CodexProcess, } from "./codex-process.js";
|
|
11
11
|
import { stopManagedCodexAppServers } from "./codex-transport.js";
|
|
@@ -90,8 +90,12 @@ const CODEX_USER_TURN_UUID_RE = /^codex:user-turn:(\d+)$/;
|
|
|
90
90
|
const OPT_IN_SERVER_MESSAGES = new Set([
|
|
91
91
|
"conversation_queue",
|
|
92
92
|
"goal_state",
|
|
93
|
+
"guardian_approval",
|
|
93
94
|
"prompt_history_status",
|
|
94
95
|
]);
|
|
96
|
+
function isRecord(value) {
|
|
97
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
98
|
+
}
|
|
95
99
|
function parseCodexUserTurnOrdinal(uuid) {
|
|
96
100
|
if (!uuid)
|
|
97
101
|
return null;
|
|
@@ -979,89 +983,110 @@ export class BridgeWebSocketServer {
|
|
|
979
983
|
message,
|
|
980
984
|
}));
|
|
981
985
|
this.applyCodexCanonicalHistoryBaseline(session, history, entries);
|
|
982
|
-
return
|
|
986
|
+
return entries;
|
|
983
987
|
}
|
|
984
988
|
applyCodexCanonicalHistoryBaseline(session, history, canonicalEntries) {
|
|
985
|
-
const
|
|
989
|
+
const orderedRevision = session.codexOrderedHistoryRevision ?? 0;
|
|
990
|
+
const liveEntries = [
|
|
991
|
+
...(session.codexOrderedHistoryEntries ?? []),
|
|
992
|
+
...session.historyEntries.filter((entry) => entry.seq > orderedRevision),
|
|
993
|
+
].map((entry) => ({
|
|
986
994
|
seq: entry.seq,
|
|
987
995
|
message: entry.message,
|
|
988
996
|
}));
|
|
989
|
-
const
|
|
997
|
+
const latestUserInput = session.codexLatestUserInput;
|
|
998
|
+
const latestUserKeys = latestUserInput
|
|
999
|
+
? this.codexHistoryMessageIdentityKeys(latestUserInput)
|
|
1000
|
+
: [];
|
|
1001
|
+
if (latestUserInput &&
|
|
1002
|
+
!liveEntries.some((entry) => entry.message === latestUserInput ||
|
|
1003
|
+
(entry.message.type === "user_input" &&
|
|
1004
|
+
this.codexHistoryMessageIdentityKeys(entry.message).some((key) => latestUserKeys.includes(key))))) {
|
|
1005
|
+
const historySeq = latestUserInput
|
|
1006
|
+
.historySeq;
|
|
1007
|
+
liveEntries.unshift({
|
|
1008
|
+
seq: typeof historySeq === "number" ? historySeq : 0,
|
|
1009
|
+
message: latestUserInput,
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
990
1012
|
const canonicalUserUuids = new Set();
|
|
991
1013
|
for (const entry of canonicalEntries) {
|
|
992
|
-
for (const key of this.codexHistoryMessageIdentityKeys(entry.message)) {
|
|
993
|
-
canonicalKeys.add(key);
|
|
994
|
-
}
|
|
995
1014
|
const message = entry.message;
|
|
996
1015
|
if (message.type === "user_input" && message.userMessageUuid) {
|
|
997
1016
|
canonicalUserUuids.add(message.userMessageUuid);
|
|
998
1017
|
}
|
|
999
1018
|
}
|
|
1000
1019
|
this.seedCodexCanonicalUserTurnUuidMap(session, history);
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
let
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1020
|
+
const merged = [];
|
|
1021
|
+
let canonicalCursor = 0;
|
|
1022
|
+
let hasCanonicalAnchor = false;
|
|
1023
|
+
let canMatchAssistantContent = false;
|
|
1024
|
+
const appendCanonicalUntil = (endExclusive) => {
|
|
1025
|
+
while (canonicalCursor < endExclusive) {
|
|
1026
|
+
const message = canonicalEntries[canonicalCursor].message;
|
|
1027
|
+
merged.push({ message, retained: false });
|
|
1028
|
+
canonicalCursor += 1;
|
|
1029
|
+
hasCanonicalAnchor = true;
|
|
1030
|
+
if (message.type === "user_input") {
|
|
1031
|
+
canMatchAssistantContent = true;
|
|
1032
|
+
}
|
|
1010
1033
|
}
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
const
|
|
1014
|
-
const
|
|
1015
|
-
if (
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
const retainedMessages = [];
|
|
1022
|
-
const retainedEntries = [];
|
|
1023
|
-
for (const entry of liveEntries) {
|
|
1024
|
-
if (!this.shouldRetainCodexLiveHistoryMessage(entry.message))
|
|
1025
|
-
continue;
|
|
1026
|
-
const keys = this.codexHistoryMessageIdentityKeys(entry.message);
|
|
1027
|
-
const matchesCanonical = keys.some((key) => canonicalKeys.has(key));
|
|
1028
|
-
if (entry.message.type === "user_input") {
|
|
1029
|
-
const userKey = entry.message.userMessageUuid
|
|
1030
|
-
? `user:${entry.message.userMessageUuid}`
|
|
1031
|
-
: null;
|
|
1032
|
-
pendingCanonicalAssistantContents =
|
|
1033
|
-
matchesCanonical && userKey
|
|
1034
|
-
? [...(canonicalAssistantContentsByUser.get(userKey) ?? [])]
|
|
1035
|
-
: [];
|
|
1036
|
-
}
|
|
1037
|
-
if (entry.message.type === "assistant") {
|
|
1038
|
-
const contentKey = this.historyValueKey(entry.message.message.content);
|
|
1039
|
-
const contentIndex = pendingCanonicalAssistantContents.indexOf(contentKey);
|
|
1040
|
-
if (matchesCanonical) {
|
|
1041
|
-
if (contentIndex !== -1) {
|
|
1042
|
-
pendingCanonicalAssistantContents.splice(contentIndex, 1);
|
|
1043
|
-
}
|
|
1044
|
-
continue;
|
|
1034
|
+
};
|
|
1035
|
+
for (let liveIndex = 0; liveIndex < liveEntries.length; liveIndex++) {
|
|
1036
|
+
const liveEntry = liveEntries[liveIndex];
|
|
1037
|
+
const matchIndex = this.findCodexCanonicalMatchIndex(canonicalEntries, liveEntry.message, canonicalCursor, canMatchAssistantContent);
|
|
1038
|
+
if (matchIndex === -1) {
|
|
1039
|
+
let nextMatchIndex = -1;
|
|
1040
|
+
for (let nextLiveIndex = liveIndex + 1; nextLiveIndex < liveEntries.length; nextLiveIndex++) {
|
|
1041
|
+
nextMatchIndex = this.findCodexCanonicalMatchIndex(canonicalEntries, liveEntries[nextLiveIndex].message, canonicalCursor, canMatchAssistantContent);
|
|
1042
|
+
if (nextMatchIndex !== -1)
|
|
1043
|
+
break;
|
|
1045
1044
|
}
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1045
|
+
if (nextMatchIndex !== -1) {
|
|
1046
|
+
appendCanonicalUntil(nextMatchIndex);
|
|
1047
|
+
}
|
|
1048
|
+
else if (!hasCanonicalAnchor) {
|
|
1049
|
+
appendCanonicalUntil(canonicalEntries.length);
|
|
1050
|
+
}
|
|
1051
|
+
if (this.shouldRetainCodexLiveHistoryMessage(liveEntry.message)) {
|
|
1052
|
+
merged.push({ message: liveEntry.message, retained: true });
|
|
1049
1053
|
}
|
|
1050
|
-
if (matchesCanonicalTurnAssistant)
|
|
1051
|
-
continue;
|
|
1052
|
-
}
|
|
1053
|
-
if (matchesCanonical)
|
|
1054
1054
|
continue;
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
retainedMessages.push(entry.message);
|
|
1058
|
-
retainedEntries.push({ seq, message: entry.message });
|
|
1055
|
+
}
|
|
1056
|
+
appendCanonicalUntil(matchIndex + 1);
|
|
1059
1057
|
}
|
|
1058
|
+
appendCanonicalUntil(canonicalEntries.length);
|
|
1059
|
+
const retainedMessages = [];
|
|
1060
|
+
const retainedEntries = [];
|
|
1061
|
+
let canonicalRevision = 0;
|
|
1062
|
+
const previousRevision = session.historyRevision;
|
|
1063
|
+
const requiresNewBaselineRevision = previousRevision > 0;
|
|
1064
|
+
const targetRevision = requiresNewBaselineRevision
|
|
1065
|
+
? Math.max(merged.length, previousRevision + 1)
|
|
1066
|
+
: merged.length;
|
|
1067
|
+
const sequenceOffset = targetRevision - merged.length;
|
|
1068
|
+
const mergedEntries = merged.map(({ message, retained }, index) => {
|
|
1069
|
+
const seq = sequenceOffset + index + 1;
|
|
1070
|
+
if (retained) {
|
|
1071
|
+
message.historySeq = seq;
|
|
1072
|
+
retainedMessages.push(message);
|
|
1073
|
+
retainedEntries.push({ seq, message });
|
|
1074
|
+
}
|
|
1075
|
+
else {
|
|
1076
|
+
canonicalRevision = seq;
|
|
1077
|
+
}
|
|
1078
|
+
return { seq, message };
|
|
1079
|
+
});
|
|
1080
|
+
canonicalEntries.splice(0, canonicalEntries.length, ...mergedEntries);
|
|
1081
|
+
session.codexOrderedHistoryEntries =
|
|
1082
|
+
this.buildCodexOrderedHistoryWindow(mergedEntries);
|
|
1083
|
+
session.codexOrderedHistoryRevision = targetRevision;
|
|
1060
1084
|
session.pastMessages = history;
|
|
1061
1085
|
session.history = retainedMessages;
|
|
1062
1086
|
session.historyEntries = retainedEntries;
|
|
1063
|
-
session.historyRevision =
|
|
1064
|
-
session.codexCanonicalHistoryRevision =
|
|
1087
|
+
session.historyRevision = targetRevision;
|
|
1088
|
+
session.codexCanonicalHistoryRevision = canonicalRevision;
|
|
1089
|
+
session.codexHistoryResetRevision = targetRevision;
|
|
1065
1090
|
session.historyLowWatermark =
|
|
1066
1091
|
retainedEntries[0]?.seq ?? session.historyRevision + 1;
|
|
1067
1092
|
if (session.pendingCodexUserEchoUuids) {
|
|
@@ -1075,6 +1100,52 @@ export class BridgeWebSocketServer {
|
|
|
1075
1100
|
}
|
|
1076
1101
|
}
|
|
1077
1102
|
}
|
|
1103
|
+
buildCodexOrderedHistoryWindow(entries) {
|
|
1104
|
+
if (entries.length <= MAX_HISTORY_PER_SESSION) {
|
|
1105
|
+
return entries.map((entry) => ({
|
|
1106
|
+
seq: entry.seq,
|
|
1107
|
+
message: entry.message,
|
|
1108
|
+
}));
|
|
1109
|
+
}
|
|
1110
|
+
const tail = entries.slice(-MAX_HISTORY_PER_SESSION);
|
|
1111
|
+
if (tail.some((entry) => entry.message.type === "user_input")) {
|
|
1112
|
+
return tail;
|
|
1113
|
+
}
|
|
1114
|
+
let latestUser;
|
|
1115
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
1116
|
+
if (entries[index].message.type === "user_input") {
|
|
1117
|
+
latestUser = entries[index];
|
|
1118
|
+
break;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
return latestUser
|
|
1122
|
+
? [latestUser, ...entries.slice(-(MAX_HISTORY_PER_SESSION - 1))]
|
|
1123
|
+
: tail;
|
|
1124
|
+
}
|
|
1125
|
+
findCodexCanonicalMatchIndex(canonicalEntries, liveMessage, start, allowAssistantContentMatch) {
|
|
1126
|
+
const liveKeys = this.codexHistoryMessageIdentityKeys(liveMessage);
|
|
1127
|
+
if (liveKeys.length > 0) {
|
|
1128
|
+
for (let index = start; index < canonicalEntries.length; index++) {
|
|
1129
|
+
const canonicalKeys = this.codexHistoryMessageIdentityKeys(canonicalEntries[index].message);
|
|
1130
|
+
if (liveKeys.some((key) => canonicalKeys.includes(key)))
|
|
1131
|
+
return index;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
if (!allowAssistantContentMatch || liveMessage.type !== "assistant") {
|
|
1135
|
+
return -1;
|
|
1136
|
+
}
|
|
1137
|
+
const liveContent = this.historyValueKey(liveMessage.message.content);
|
|
1138
|
+
for (let index = start; index < canonicalEntries.length; index++) {
|
|
1139
|
+
const canonicalMessage = canonicalEntries[index].message;
|
|
1140
|
+
if (canonicalMessage.type === "user_input")
|
|
1141
|
+
break;
|
|
1142
|
+
if (canonicalMessage.type === "assistant" &&
|
|
1143
|
+
this.historyValueKey(canonicalMessage.message.content) === liveContent) {
|
|
1144
|
+
return index;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
return -1;
|
|
1148
|
+
}
|
|
1078
1149
|
seedCodexCanonicalUserTurnUuidMap(session, history) {
|
|
1079
1150
|
if (session.provider !== "codex")
|
|
1080
1151
|
return;
|
|
@@ -1151,8 +1222,8 @@ export class BridgeWebSocketServer {
|
|
|
1151
1222
|
this.send(ws, {
|
|
1152
1223
|
type: "history_snapshot",
|
|
1153
1224
|
sessionId,
|
|
1154
|
-
fromSeq: entries[0]?.seq ?? 1,
|
|
1155
|
-
toSeq:
|
|
1225
|
+
fromSeq: entries[0]?.seq ?? session.historyRevision + 1,
|
|
1226
|
+
toSeq: session.historyRevision,
|
|
1156
1227
|
messages: entries,
|
|
1157
1228
|
status: session.status,
|
|
1158
1229
|
reason: "reset",
|
|
@@ -1205,9 +1276,11 @@ export class BridgeWebSocketServer {
|
|
|
1205
1276
|
shouldResetCodexHistoryDelta(session, sinceSeq, resultKind) {
|
|
1206
1277
|
if (!this.codexThreadIdForSession(session))
|
|
1207
1278
|
return false;
|
|
1208
|
-
|
|
1279
|
+
const resetRevision = session.codexHistoryResetRevision ??
|
|
1280
|
+
session.codexCanonicalHistoryRevision;
|
|
1281
|
+
if (typeof resetRevision !== "number")
|
|
1209
1282
|
return true;
|
|
1210
|
-
if (sinceSeq <
|
|
1283
|
+
if (sinceSeq < resetRevision)
|
|
1211
1284
|
return true;
|
|
1212
1285
|
return resultKind === "snapshot";
|
|
1213
1286
|
}
|
|
@@ -5147,14 +5220,18 @@ export class BridgeWebSocketServer {
|
|
|
5147
5220
|
}
|
|
5148
5221
|
}
|
|
5149
5222
|
broadcastSessionMessageNow(sessionId, msg, exclude) {
|
|
5150
|
-
const data = JSON.stringify({ ...msg, sessionId });
|
|
5151
5223
|
for (const client of this.wss.clients) {
|
|
5152
5224
|
if (client === exclude)
|
|
5153
5225
|
continue;
|
|
5154
5226
|
if (client.readyState === WebSocket.OPEN) {
|
|
5155
|
-
|
|
5227
|
+
const outboundMsg = {
|
|
5228
|
+
...msg,
|
|
5229
|
+
sessionId,
|
|
5230
|
+
};
|
|
5231
|
+
const compatibleMsg = this.prepareServerMessageForClient(client, outboundMsg);
|
|
5232
|
+
if (!compatibleMsg)
|
|
5156
5233
|
continue;
|
|
5157
|
-
client.send(
|
|
5234
|
+
client.send(JSON.stringify(compatibleMsg));
|
|
5158
5235
|
}
|
|
5159
5236
|
}
|
|
5160
5237
|
}
|
|
@@ -5657,15 +5734,39 @@ export class BridgeWebSocketServer {
|
|
|
5657
5734
|
}
|
|
5658
5735
|
}
|
|
5659
5736
|
broadcast(msg) {
|
|
5660
|
-
const data = JSON.stringify(msg);
|
|
5661
5737
|
for (const client of this.wss.clients) {
|
|
5662
5738
|
if (client.readyState === WebSocket.OPEN) {
|
|
5663
|
-
|
|
5739
|
+
const compatibleMsg = this.prepareServerMessageForClient(client, msg);
|
|
5740
|
+
if (!compatibleMsg)
|
|
5664
5741
|
continue;
|
|
5665
|
-
client.send(
|
|
5742
|
+
client.send(JSON.stringify(compatibleMsg));
|
|
5666
5743
|
}
|
|
5667
5744
|
}
|
|
5668
5745
|
}
|
|
5746
|
+
prepareServerMessageForClient(ws, msg) {
|
|
5747
|
+
if (!this.shouldSendToClient(ws, msg))
|
|
5748
|
+
return null;
|
|
5749
|
+
if (!("messages" in msg) || !Array.isArray(msg.messages))
|
|
5750
|
+
return msg;
|
|
5751
|
+
const messages = msg.messages;
|
|
5752
|
+
if (msg.type === "history") {
|
|
5753
|
+
return {
|
|
5754
|
+
...msg,
|
|
5755
|
+
messages: messages.filter((message) => isRecord(message) && this.shouldSendToClient(ws, message)),
|
|
5756
|
+
};
|
|
5757
|
+
}
|
|
5758
|
+
if (msg.type === "history_delta" || msg.type === "history_snapshot") {
|
|
5759
|
+
return {
|
|
5760
|
+
...msg,
|
|
5761
|
+
messages: messages.filter((entry) => {
|
|
5762
|
+
if (!isRecord(entry) || !isRecord(entry.message))
|
|
5763
|
+
return true;
|
|
5764
|
+
return this.shouldSendToClient(ws, entry.message);
|
|
5765
|
+
}),
|
|
5766
|
+
};
|
|
5767
|
+
}
|
|
5768
|
+
return msg;
|
|
5769
|
+
}
|
|
5669
5770
|
shouldSendToClient(ws, msg) {
|
|
5670
5771
|
const type = typeof msg.type === "string" ? msg.type : "";
|
|
5671
5772
|
if (!OPT_IN_SERVER_MESSAGES.has(type))
|
|
@@ -5673,9 +5774,11 @@ export class BridgeWebSocketServer {
|
|
|
5673
5774
|
return (this.clientSupportedServerMessages.get(ws)?.has(type) ?? false);
|
|
5674
5775
|
}
|
|
5675
5776
|
hasInputConflictSince(session, baseSeq) {
|
|
5777
|
+
const codexResetRevision = session.codexHistoryResetRevision ??
|
|
5778
|
+
session.codexCanonicalHistoryRevision;
|
|
5676
5779
|
if (session.provider === "codex" &&
|
|
5677
|
-
typeof
|
|
5678
|
-
baseSeq <
|
|
5780
|
+
typeof codexResetRevision === "number" &&
|
|
5781
|
+
baseSeq < codexResetRevision) {
|
|
5679
5782
|
return true;
|
|
5680
5783
|
}
|
|
5681
5784
|
const delta = this.sessionManager.getHistorySince(session.id, baseSeq);
|
|
@@ -5757,19 +5860,20 @@ export class BridgeWebSocketServer {
|
|
|
5757
5860
|
this.send(ws, msg);
|
|
5758
5861
|
}
|
|
5759
5862
|
send(ws, msg) {
|
|
5760
|
-
|
|
5863
|
+
const compatibleMsg = this.prepareServerMessageForClient(ws, msg);
|
|
5864
|
+
if (!compatibleMsg)
|
|
5761
5865
|
return;
|
|
5762
|
-
const sessionId = this.extractSessionIdFromServerMessage(
|
|
5866
|
+
const sessionId = this.extractSessionIdFromServerMessage(compatibleMsg);
|
|
5763
5867
|
if (sessionId) {
|
|
5764
5868
|
this.recordDebugEvent(sessionId, {
|
|
5765
5869
|
direction: "outgoing",
|
|
5766
5870
|
channel: "ws",
|
|
5767
|
-
type: String(
|
|
5768
|
-
detail: this.summarizeOutboundMessage(
|
|
5871
|
+
type: String(compatibleMsg.type ?? "unknown"),
|
|
5872
|
+
detail: this.summarizeOutboundMessage(compatibleMsg),
|
|
5769
5873
|
});
|
|
5770
5874
|
}
|
|
5771
5875
|
if (ws.readyState === WebSocket.OPEN) {
|
|
5772
|
-
ws.send(JSON.stringify(
|
|
5876
|
+
ws.send(JSON.stringify(compatibleMsg));
|
|
5773
5877
|
}
|
|
5774
5878
|
}
|
|
5775
5879
|
/** Broadcast a gallery_new_image message to all connected clients. */
|