lunel-cli 0.1.64 → 0.1.66
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/ai/codex.d.ts +11 -0
- package/dist/ai/codex.js +158 -10
- package/dist/index.js +71 -9
- package/package.json +1 -1
package/dist/ai/codex.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export declare class CodexProvider implements AIProvider {
|
|
|
6
6
|
private nextId;
|
|
7
7
|
private pending;
|
|
8
8
|
private sessions;
|
|
9
|
+
private resumedThreadIds;
|
|
9
10
|
private pendingPermissionRequestIds;
|
|
10
11
|
private assistantMessageIdByTurnId;
|
|
11
12
|
private partTextById;
|
|
@@ -71,6 +72,13 @@ export declare class CodexProvider implements AIProvider {
|
|
|
71
72
|
private mergeSession;
|
|
72
73
|
private upsertSession;
|
|
73
74
|
private ensureLocalSession;
|
|
75
|
+
private getTransportThreadId;
|
|
76
|
+
private findSessionByThreadId;
|
|
77
|
+
private ensureThreadResumed;
|
|
78
|
+
private shouldTreatAsThreadNotFound;
|
|
79
|
+
private ensurePromptSessionReady;
|
|
80
|
+
private createContinuationSession;
|
|
81
|
+
private handleMissingThread;
|
|
74
82
|
private resolveSessionFromPayload;
|
|
75
83
|
private resolveInFlightTurnId;
|
|
76
84
|
private decodeMessagesFromThreadRead;
|
|
@@ -94,6 +102,7 @@ export declare class CodexProvider implements AIProvider {
|
|
|
94
102
|
private extractItemId;
|
|
95
103
|
private extractTextPayload;
|
|
96
104
|
private extractThreadId;
|
|
105
|
+
private extractThreadCwd;
|
|
97
106
|
private extractCreatedAt;
|
|
98
107
|
private extractUpdatedAt;
|
|
99
108
|
private readTimestamp;
|
|
@@ -115,6 +124,8 @@ export declare class CodexProvider implements AIProvider {
|
|
|
115
124
|
private renderUnifiedDiffBody;
|
|
116
125
|
private extractCanonicalPatch;
|
|
117
126
|
private normalizeDisplayPath;
|
|
127
|
+
private normalizeDirectoryPath;
|
|
128
|
+
private belongsToCurrentRoot;
|
|
118
129
|
private normalizedFileChangeStatus;
|
|
119
130
|
private extractToolInput;
|
|
120
131
|
private extractCommandExecutionInput;
|
package/dist/ai/codex.js
CHANGED
|
@@ -13,6 +13,7 @@ export class CodexProvider {
|
|
|
13
13
|
nextId = 1;
|
|
14
14
|
pending = new Map();
|
|
15
15
|
sessions = new Map();
|
|
16
|
+
resumedThreadIds = new Set();
|
|
16
17
|
pendingPermissionRequestIds = new Map();
|
|
17
18
|
assistantMessageIdByTurnId = new Map();
|
|
18
19
|
partTextById = new Map();
|
|
@@ -51,7 +52,7 @@ export class CodexProvider {
|
|
|
51
52
|
};
|
|
52
53
|
}
|
|
53
54
|
async createSession(title) {
|
|
54
|
-
const result = await this.call("thread/start", {});
|
|
55
|
+
const result = await this.call("thread/start", { cwd: process.cwd() });
|
|
55
56
|
const threadId = this.extractThreadId(result);
|
|
56
57
|
if (!threadId) {
|
|
57
58
|
throw new Error("thread/start response missing threadId");
|
|
@@ -63,6 +64,7 @@ export class CodexProvider {
|
|
|
63
64
|
createdAt: this.extractCreatedAt(result) ?? Date.now(),
|
|
64
65
|
updatedAt: this.extractUpdatedAt(result) ?? Date.now(),
|
|
65
66
|
archived: false,
|
|
67
|
+
cwd: this.extractThreadCwd(result) ?? process.cwd(),
|
|
66
68
|
});
|
|
67
69
|
return { session: this.toSessionInfo(session) };
|
|
68
70
|
}
|
|
@@ -77,6 +79,7 @@ export class CodexProvider {
|
|
|
77
79
|
}
|
|
78
80
|
this.reconcileSessionsWithServer(activeThreads, archivedThreads);
|
|
79
81
|
const sessions = Array.from(this.sessions.values())
|
|
82
|
+
.filter((session) => this.belongsToCurrentRoot(session))
|
|
80
83
|
.sort((a, b) => a.updatedAt - b.updatedAt)
|
|
81
84
|
.map((session) => this.toSessionInfo(session));
|
|
82
85
|
return { sessions };
|
|
@@ -99,8 +102,9 @@ export class CodexProvider {
|
|
|
99
102
|
}
|
|
100
103
|
async getMessages(sessionId) {
|
|
101
104
|
const session = this.ensureLocalSession(sessionId);
|
|
105
|
+
const transportThreadId = this.getTransportThreadId(session);
|
|
102
106
|
const result = await this.call("thread/read", {
|
|
103
|
-
threadId:
|
|
107
|
+
threadId: transportThreadId,
|
|
104
108
|
includeTurns: true,
|
|
105
109
|
});
|
|
106
110
|
const threadObject = this.extractThreadObject(result);
|
|
@@ -117,6 +121,8 @@ export class CodexProvider {
|
|
|
117
121
|
createdAt: this.extractCreatedAt(threadObject) ?? session.createdAt,
|
|
118
122
|
updatedAt: this.extractUpdatedAt(threadObject) ?? session.updatedAt,
|
|
119
123
|
archived: false,
|
|
124
|
+
transportThreadId,
|
|
125
|
+
cwd: this.extractThreadCwd(threadObject) ?? session.cwd,
|
|
120
126
|
});
|
|
121
127
|
return { messages: session.messages };
|
|
122
128
|
}
|
|
@@ -125,11 +131,12 @@ export class CodexProvider {
|
|
|
125
131
|
session.updatedAt = Date.now();
|
|
126
132
|
(async () => {
|
|
127
133
|
try {
|
|
134
|
+
let targetSession = await this.ensurePromptSessionReady(session);
|
|
128
135
|
let imageUrlKey = "url";
|
|
129
136
|
while (true) {
|
|
130
137
|
try {
|
|
131
138
|
await this.call("turn/start", {
|
|
132
|
-
threadId:
|
|
139
|
+
threadId: this.getTransportThreadId(targetSession),
|
|
133
140
|
input: this.makeTurnInputPayload(text, files, imageUrlKey),
|
|
134
141
|
...(model ? { model: model.providerID === "codex" ? model.modelID : `${model.providerID}/${model.modelID}` } : {}),
|
|
135
142
|
...(agent ? { agent } : {}),
|
|
@@ -137,6 +144,11 @@ export class CodexProvider {
|
|
|
137
144
|
break;
|
|
138
145
|
}
|
|
139
146
|
catch (err) {
|
|
147
|
+
if (this.shouldTreatAsThreadNotFound(err)) {
|
|
148
|
+
targetSession = await this.createContinuationSession(targetSession);
|
|
149
|
+
imageUrlKey = "url";
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
140
152
|
if (imageUrlKey === "url"
|
|
141
153
|
&& files.length > 0
|
|
142
154
|
&& this.shouldRetryTurnStartWithImageURLField(err)) {
|
|
@@ -157,12 +169,13 @@ export class CodexProvider {
|
|
|
157
169
|
}
|
|
158
170
|
async abort(sessionId) {
|
|
159
171
|
const session = this.ensureLocalSession(sessionId);
|
|
160
|
-
const
|
|
172
|
+
const transportThreadId = this.getTransportThreadId(session);
|
|
173
|
+
const turnId = session.activeTurnId ?? await this.resolveInFlightTurnId(transportThreadId);
|
|
161
174
|
if (!turnId) {
|
|
162
175
|
throw new Error(`Session ${sessionId} has no active interruptible Codex turn`);
|
|
163
176
|
}
|
|
164
177
|
session.activeTurnId = turnId;
|
|
165
|
-
await this.call("turn/interrupt", { threadId:
|
|
178
|
+
await this.call("turn/interrupt", { threadId: transportThreadId, turnId });
|
|
166
179
|
return {};
|
|
167
180
|
}
|
|
168
181
|
async agents() {
|
|
@@ -660,8 +673,9 @@ export class CodexProvider {
|
|
|
660
673
|
}
|
|
661
674
|
async refreshSessionMetadata(sessionId) {
|
|
662
675
|
const session = this.sessions.get(sessionId);
|
|
676
|
+
const transportThreadId = session ? this.getTransportThreadId(session) : sessionId;
|
|
663
677
|
const result = await this.call("thread/read", {
|
|
664
|
-
threadId:
|
|
678
|
+
threadId: transportThreadId,
|
|
665
679
|
includeTurns: false,
|
|
666
680
|
});
|
|
667
681
|
const threadObject = this.extractThreadObject(result);
|
|
@@ -674,6 +688,8 @@ export class CodexProvider {
|
|
|
674
688
|
createdAt: this.extractCreatedAt(threadObject) ?? session?.createdAt ?? Date.now(),
|
|
675
689
|
updatedAt: this.extractUpdatedAt(threadObject) ?? session?.updatedAt ?? Date.now(),
|
|
676
690
|
archived: false,
|
|
691
|
+
transportThreadId,
|
|
692
|
+
cwd: this.extractThreadCwd(threadObject) ?? session?.cwd,
|
|
677
693
|
}, true);
|
|
678
694
|
}
|
|
679
695
|
async fetchServerThreadsByArchiveState(archived) {
|
|
@@ -748,6 +764,7 @@ export class CodexProvider {
|
|
|
748
764
|
createdAt: this.extractCreatedAt(obj),
|
|
749
765
|
updatedAt: this.extractUpdatedAt(obj),
|
|
750
766
|
archived,
|
|
767
|
+
cwd: this.extractThreadCwd(obj),
|
|
751
768
|
};
|
|
752
769
|
}
|
|
753
770
|
hasNextCursor(value) {
|
|
@@ -767,6 +784,7 @@ export class CodexProvider {
|
|
|
767
784
|
title,
|
|
768
785
|
createdAt: this.extractCreatedAt(payload) ?? Date.now(),
|
|
769
786
|
updatedAt: this.extractUpdatedAt(payload) ?? Date.now(),
|
|
787
|
+
cwd: this.extractThreadCwd(payload),
|
|
770
788
|
});
|
|
771
789
|
}
|
|
772
790
|
reconcileSessionsWithServer(activeThreads, archivedThreads = []) {
|
|
@@ -797,6 +815,8 @@ export class CodexProvider {
|
|
|
797
815
|
createdAt: input.createdAt ?? Date.now(),
|
|
798
816
|
updatedAt: input.updatedAt ?? Date.now(),
|
|
799
817
|
archived: input.archived ?? false,
|
|
818
|
+
transportThreadId: input.transportThreadId ?? input.id,
|
|
819
|
+
cwd: input.cwd,
|
|
800
820
|
messages: [],
|
|
801
821
|
};
|
|
802
822
|
}
|
|
@@ -806,25 +826,31 @@ export class CodexProvider {
|
|
|
806
826
|
? Math.max(existing.updatedAt, input.updatedAt)
|
|
807
827
|
: existing.updatedAt;
|
|
808
828
|
existing.archived = input.archived ?? existing.archived ?? false;
|
|
829
|
+
existing.transportThreadId = input.transportThreadId ?? input.id ?? existing.transportThreadId ?? existing.id;
|
|
830
|
+
existing.cwd = input.cwd ?? existing.cwd;
|
|
809
831
|
return existing;
|
|
810
832
|
}
|
|
811
833
|
upsertSession(input, emitUpdated = false) {
|
|
812
|
-
const existing = this.
|
|
834
|
+
const existing = this.findSessionByThreadId(input.id);
|
|
813
835
|
const before = existing
|
|
814
836
|
? {
|
|
815
837
|
title: existing.title,
|
|
816
838
|
createdAt: existing.createdAt,
|
|
817
839
|
updatedAt: existing.updatedAt,
|
|
818
840
|
archived: existing.archived ?? false,
|
|
841
|
+
transportThreadId: existing.transportThreadId ?? existing.id,
|
|
842
|
+
cwd: existing.cwd,
|
|
819
843
|
}
|
|
820
844
|
: null;
|
|
821
845
|
const session = this.mergeSession(existing, input);
|
|
822
|
-
this.sessions.set(
|
|
846
|
+
this.sessions.set(session.id, session);
|
|
823
847
|
const changed = !before
|
|
824
848
|
|| before.title !== session.title
|
|
825
849
|
|| before.createdAt !== session.createdAt
|
|
826
850
|
|| before.updatedAt !== session.updatedAt
|
|
827
|
-
|| before.archived !== (session.archived ?? false)
|
|
851
|
+
|| before.archived !== (session.archived ?? false)
|
|
852
|
+
|| before.transportThreadId !== (session.transportThreadId ?? session.id)
|
|
853
|
+
|| before.cwd !== session.cwd;
|
|
828
854
|
if (emitUpdated && changed) {
|
|
829
855
|
this.emitter?.({ type: "session.updated", properties: { info: this.toSessionInfo(session) } });
|
|
830
856
|
}
|
|
@@ -836,9 +862,102 @@ export class CodexProvider {
|
|
|
836
862
|
return existing;
|
|
837
863
|
return this.upsertSession({ id: sessionId, title: "Conversation", createdAt: Date.now(), updatedAt: Date.now() });
|
|
838
864
|
}
|
|
865
|
+
getTransportThreadId(session) {
|
|
866
|
+
return session.transportThreadId ?? session.id;
|
|
867
|
+
}
|
|
868
|
+
findSessionByThreadId(threadId) {
|
|
869
|
+
const direct = this.sessions.get(threadId);
|
|
870
|
+
if (direct)
|
|
871
|
+
return direct;
|
|
872
|
+
for (const session of this.sessions.values()) {
|
|
873
|
+
if ((session.transportThreadId ?? session.id) === threadId) {
|
|
874
|
+
return session;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
return undefined;
|
|
878
|
+
}
|
|
879
|
+
async ensureThreadResumed(threadId) {
|
|
880
|
+
if (!threadId || this.resumedThreadIds.has(threadId)) {
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
await this.call("thread/resume", { threadId });
|
|
884
|
+
this.resumedThreadIds.add(threadId);
|
|
885
|
+
}
|
|
886
|
+
shouldTreatAsThreadNotFound(error) {
|
|
887
|
+
const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
|
|
888
|
+
if (message.includes("not materialized") || message.includes("not yet materialized")) {
|
|
889
|
+
return false;
|
|
890
|
+
}
|
|
891
|
+
return message.includes("thread not found") || message.includes("unknown thread");
|
|
892
|
+
}
|
|
893
|
+
async ensurePromptSessionReady(session) {
|
|
894
|
+
const threadId = this.getTransportThreadId(session);
|
|
895
|
+
try {
|
|
896
|
+
await this.ensureThreadResumed(threadId);
|
|
897
|
+
return session;
|
|
898
|
+
}
|
|
899
|
+
catch (error) {
|
|
900
|
+
if (!this.shouldTreatAsThreadNotFound(error)) {
|
|
901
|
+
throw error;
|
|
902
|
+
}
|
|
903
|
+
return this.createContinuationSession(session);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
async createContinuationSession(session) {
|
|
907
|
+
const previousThreadId = this.getTransportThreadId(session);
|
|
908
|
+
this.handleMissingThread(session);
|
|
909
|
+
const result = await this.call("thread/start", {
|
|
910
|
+
...(session.cwd ? { cwd: session.cwd } : { cwd: process.cwd() }),
|
|
911
|
+
});
|
|
912
|
+
const threadId = this.extractThreadId(result);
|
|
913
|
+
if (!threadId) {
|
|
914
|
+
throw new Error("thread/start response missing threadId");
|
|
915
|
+
}
|
|
916
|
+
session.transportThreadId = threadId;
|
|
917
|
+
session.updatedAt = Date.now();
|
|
918
|
+
session.archived = false;
|
|
919
|
+
this.upsertSession({
|
|
920
|
+
id: session.id,
|
|
921
|
+
title: this.extractThreadTitleFromUnknown(result) ?? session.title,
|
|
922
|
+
createdAt: session.createdAt,
|
|
923
|
+
updatedAt: this.extractUpdatedAt(result) ?? Date.now(),
|
|
924
|
+
archived: false,
|
|
925
|
+
transportThreadId: threadId,
|
|
926
|
+
cwd: this.extractThreadCwd(result) ?? session.cwd ?? process.cwd(),
|
|
927
|
+
}, true);
|
|
928
|
+
this.resumedThreadIds.add(threadId);
|
|
929
|
+
const systemMessageId = `system:${crypto.randomUUID()}`;
|
|
930
|
+
session.messages.push({
|
|
931
|
+
id: systemMessageId,
|
|
932
|
+
role: "assistant",
|
|
933
|
+
parts: [{
|
|
934
|
+
id: `${systemMessageId}:text`,
|
|
935
|
+
type: "text",
|
|
936
|
+
text: `Continued from archived thread \`${previousThreadId}\``,
|
|
937
|
+
sessionID: session.id,
|
|
938
|
+
messageID: systemMessageId,
|
|
939
|
+
}],
|
|
940
|
+
time: Date.now(),
|
|
941
|
+
});
|
|
942
|
+
this.emitMessagePartEvent(session.id, systemMessageId, "assistant", {
|
|
943
|
+
id: `${systemMessageId}:text`,
|
|
944
|
+
type: "text",
|
|
945
|
+
text: `Continued from archived thread \`${previousThreadId}\``,
|
|
946
|
+
sessionID: session.id,
|
|
947
|
+
messageID: systemMessageId,
|
|
948
|
+
});
|
|
949
|
+
return session;
|
|
950
|
+
}
|
|
951
|
+
handleMissingThread(session) {
|
|
952
|
+
const threadId = this.getTransportThreadId(session);
|
|
953
|
+
this.resumedThreadIds.delete(threadId);
|
|
954
|
+
session.activeTurnId = undefined;
|
|
955
|
+
}
|
|
839
956
|
resolveSessionFromPayload(payload) {
|
|
840
957
|
const threadId = this.extractThreadId(payload);
|
|
841
|
-
|
|
958
|
+
if (!threadId)
|
|
959
|
+
return undefined;
|
|
960
|
+
return this.findSessionByThreadId(threadId) ?? this.ensureLocalSession(threadId);
|
|
842
961
|
}
|
|
843
962
|
async resolveInFlightTurnId(threadId) {
|
|
844
963
|
const result = await this.call("thread/read", { threadId, includeTurns: true });
|
|
@@ -1211,6 +1330,19 @@ export class CodexProvider {
|
|
|
1211
1330
|
const thread = this.asRecord(obj.thread);
|
|
1212
1331
|
return this.readString(thread.id) ?? this.readString(thread.threadId) ?? this.readString(thread.thread_id);
|
|
1213
1332
|
}
|
|
1333
|
+
extractThreadCwd(payload) {
|
|
1334
|
+
const obj = this.extractThreadObject(payload);
|
|
1335
|
+
const cwd = this.firstString(obj, [
|
|
1336
|
+
"cwd",
|
|
1337
|
+
"projectPath",
|
|
1338
|
+
"project_path",
|
|
1339
|
+
"gitWorkingDirectory",
|
|
1340
|
+
"git_working_directory",
|
|
1341
|
+
"workingDirectory",
|
|
1342
|
+
"working_directory",
|
|
1343
|
+
]);
|
|
1344
|
+
return cwd ? this.normalizeDirectoryPath(cwd) : undefined;
|
|
1345
|
+
}
|
|
1214
1346
|
extractCreatedAt(payload) {
|
|
1215
1347
|
const obj = this.extractThreadObject(payload);
|
|
1216
1348
|
return this.readTimestamp(obj.createdAt) ?? this.readTimestamp(obj.created_at);
|
|
@@ -1441,6 +1573,22 @@ export class CodexProvider {
|
|
|
1441
1573
|
}
|
|
1442
1574
|
return relative;
|
|
1443
1575
|
}
|
|
1576
|
+
normalizeDirectoryPath(rawPath) {
|
|
1577
|
+
if (!rawPath)
|
|
1578
|
+
return undefined;
|
|
1579
|
+
const trimmed = rawPath.trim();
|
|
1580
|
+
if (!trimmed)
|
|
1581
|
+
return undefined;
|
|
1582
|
+
return path.resolve(trimmed);
|
|
1583
|
+
}
|
|
1584
|
+
belongsToCurrentRoot(session) {
|
|
1585
|
+
const sessionCwd = this.normalizeDirectoryPath(session.cwd);
|
|
1586
|
+
const currentRoot = this.normalizeDirectoryPath(process.cwd());
|
|
1587
|
+
if (!sessionCwd || !currentRoot) {
|
|
1588
|
+
return false;
|
|
1589
|
+
}
|
|
1590
|
+
return sessionCwd === currentRoot;
|
|
1591
|
+
}
|
|
1444
1592
|
normalizedFileChangeStatus(itemObject, isCompleted) {
|
|
1445
1593
|
const status = this.readString(itemObject.status)
|
|
1446
1594
|
?? this.readString(this.asRecord(itemObject.status).type)
|
package/dist/index.js
CHANGED
|
@@ -2494,11 +2494,56 @@ async function createSessionFromManager() {
|
|
|
2494
2494
|
return (await response.json());
|
|
2495
2495
|
}
|
|
2496
2496
|
async function connectToCloudSession(sessionCode) {
|
|
2497
|
-
const response = await fetch(`${MANAGER_URL}/v1/session
|
|
2497
|
+
const response = await fetch(`${MANAGER_URL}/v1/session/resolve`, {
|
|
2498
|
+
method: "POST",
|
|
2499
|
+
headers: { "Content-Type": "application/json" },
|
|
2500
|
+
body: JSON.stringify({ code: sessionCode }),
|
|
2501
|
+
});
|
|
2498
2502
|
if (!response.ok) {
|
|
2499
2503
|
throw new Error(`Failed to look up cloud session ${sessionCode}: ${response.status}`);
|
|
2500
2504
|
}
|
|
2501
|
-
|
|
2505
|
+
const snapshot = await response.json();
|
|
2506
|
+
if (!snapshot.exists || !snapshot.valid || !snapshot.primary || !snapshot.resumeToken || !snapshot.code) {
|
|
2507
|
+
throw new Error(`Failed to look up cloud session ${sessionCode}: invalid or expired session`);
|
|
2508
|
+
}
|
|
2509
|
+
return {
|
|
2510
|
+
sessionId: snapshot.sessionId || "",
|
|
2511
|
+
code: snapshot.code,
|
|
2512
|
+
password: snapshot.resumeToken,
|
|
2513
|
+
primary: snapshot.primary,
|
|
2514
|
+
backup: snapshot.backup ?? null,
|
|
2515
|
+
state: snapshot.state,
|
|
2516
|
+
expiresAt: snapshot.expiresAt,
|
|
2517
|
+
};
|
|
2518
|
+
}
|
|
2519
|
+
async function resolveSessionByResumeToken(resumeToken) {
|
|
2520
|
+
const response = await fetch(`${MANAGER_URL}/v1/session/resolve`, {
|
|
2521
|
+
method: "POST",
|
|
2522
|
+
headers: { "Content-Type": "application/json" },
|
|
2523
|
+
body: JSON.stringify({ resumeToken }),
|
|
2524
|
+
});
|
|
2525
|
+
if (!response.ok) {
|
|
2526
|
+
throw new Error(`Failed to resolve session from manager: ${response.status}`);
|
|
2527
|
+
}
|
|
2528
|
+
const snapshot = await response.json();
|
|
2529
|
+
if (!snapshot.exists || !snapshot.valid || !snapshot.primary || !snapshot.resumeToken || !snapshot.code) {
|
|
2530
|
+
if (snapshot.reason === "session_finalized" || snapshot.reason === "not_found") {
|
|
2531
|
+
return null;
|
|
2532
|
+
}
|
|
2533
|
+
if (snapshot.state === "ended" || snapshot.state === "expired") {
|
|
2534
|
+
return null;
|
|
2535
|
+
}
|
|
2536
|
+
throw new Error(`Session resolve returned invalid snapshot (${snapshot.reason || "unknown"})`);
|
|
2537
|
+
}
|
|
2538
|
+
return {
|
|
2539
|
+
sessionId: snapshot.sessionId || "",
|
|
2540
|
+
code: snapshot.code,
|
|
2541
|
+
password: snapshot.resumeToken,
|
|
2542
|
+
primary: snapshot.primary,
|
|
2543
|
+
backup: snapshot.backup ?? null,
|
|
2544
|
+
state: snapshot.state,
|
|
2545
|
+
expiresAt: snapshot.expiresAt,
|
|
2546
|
+
};
|
|
2502
2547
|
}
|
|
2503
2548
|
function displayQR(primaryGateway, backupGateway, code) {
|
|
2504
2549
|
console.log("\n");
|
|
@@ -2776,13 +2821,30 @@ async function handleConnectionDrop(reason) {
|
|
|
2776
2821
|
gracefulShutdown();
|
|
2777
2822
|
return;
|
|
2778
2823
|
}
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2824
|
+
let attempt = 0;
|
|
2825
|
+
while (!shuttingDown) {
|
|
2826
|
+
attempt += 1;
|
|
2827
|
+
const base = Math.min(250 * 2 ** (attempt - 1), 30_000);
|
|
2828
|
+
const delayMs = Math.round(base * (0.8 + Math.random() * 0.4));
|
|
2829
|
+
try {
|
|
2830
|
+
const resolved = await resolveSessionByResumeToken(currentSessionPassword);
|
|
2831
|
+
if (!resolved) {
|
|
2832
|
+
console.error("[reconnect] session no longer exists or is finalized");
|
|
2833
|
+
gracefulShutdown();
|
|
2834
|
+
return;
|
|
2835
|
+
}
|
|
2836
|
+
currentSessionCode = resolved.code;
|
|
2837
|
+
currentSessionPassword = resolved.password;
|
|
2838
|
+
currentPrimaryGateway = normalizeGatewayUrl(resolved.primary);
|
|
2839
|
+
currentBackupGateway = resolved.backup ? normalizeGatewayUrl(resolved.backup) : null;
|
|
2840
|
+
await connectWebSocket();
|
|
2841
|
+
console.log(`[reconnect] connected via ${activeGatewayUrl}`);
|
|
2842
|
+
return;
|
|
2843
|
+
}
|
|
2844
|
+
catch (err) {
|
|
2845
|
+
console.error(`[reconnect] attempt ${attempt} failed: ${err.message}`);
|
|
2846
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
2847
|
+
}
|
|
2786
2848
|
}
|
|
2787
2849
|
}
|
|
2788
2850
|
async function main() {
|