lody 0.45.0 → 0.45.1
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/index.js +198 -164
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -36820,7 +36820,7 @@ Mongoose Error Code: ${error2.code}` : ""}`
|
|
|
36820
36820
|
return client;
|
|
36821
36821
|
}
|
|
36822
36822
|
const name = "lody";
|
|
36823
|
-
const version$4 = "0.45.
|
|
36823
|
+
const version$4 = "0.45.1";
|
|
36824
36824
|
const description = "Lody Agent CLI tool for managing remote command execution";
|
|
36825
36825
|
const type = "module";
|
|
36826
36826
|
const main$3 = "dist/index.js";
|
|
@@ -113879,6 +113879,34 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
|
|
|
113879
113879
|
async waitForRemoteSync() {
|
|
113880
113880
|
await this.remoteSyncReady;
|
|
113881
113881
|
}
|
|
113882
|
+
async ensureDocRoomJoined() {
|
|
113883
|
+
if (this.destroyed) {
|
|
113884
|
+
return;
|
|
113885
|
+
}
|
|
113886
|
+
if (!this.docSub) {
|
|
113887
|
+
this.remoteSyncReady = this.startDocRoomSync();
|
|
113888
|
+
}
|
|
113889
|
+
await this.remoteSyncReady;
|
|
113890
|
+
}
|
|
113891
|
+
getDocRoomStatus() {
|
|
113892
|
+
return this.docSub?.status;
|
|
113893
|
+
}
|
|
113894
|
+
onDocRoomStatusChange(listener) {
|
|
113895
|
+
return this.docSub?.onStatusChange(listener) ?? (() => {
|
|
113896
|
+
});
|
|
113897
|
+
}
|
|
113898
|
+
async rejoinDocRoom() {
|
|
113899
|
+
if (this.destroyed) {
|
|
113900
|
+
return;
|
|
113901
|
+
}
|
|
113902
|
+
if (!this.docSub) {
|
|
113903
|
+
await this.ensureDocRoomJoined();
|
|
113904
|
+
return;
|
|
113905
|
+
}
|
|
113906
|
+
if (this.docSub.rejoin) {
|
|
113907
|
+
await this.docSub.rejoin();
|
|
113908
|
+
}
|
|
113909
|
+
}
|
|
113882
113910
|
async initOffline(initialState) {
|
|
113883
113911
|
this.destroyed = false;
|
|
113884
113912
|
this.handle = await this.repo.openPersistedDoc(this.roomId);
|
|
@@ -121853,9 +121881,6 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
121853
121881
|
watchedSessions = /* @__PURE__ */ new Map();
|
|
121854
121882
|
sessionCheckChains = /* @__PURE__ */ new Map();
|
|
121855
121883
|
cancelCheckChains = /* @__PURE__ */ new Map();
|
|
121856
|
-
dispatchRetryTimers = /* @__PURE__ */ new Map();
|
|
121857
|
-
dispatchRetryAttempts = /* @__PURE__ */ new Map();
|
|
121858
|
-
dispatchRetryStartedAt = /* @__PURE__ */ new Map();
|
|
121859
121884
|
cancelSeenTurn = /* @__PURE__ */ new Map();
|
|
121860
121885
|
metadataWatchHandle = null;
|
|
121861
121886
|
userResolver;
|
|
@@ -121899,15 +121924,9 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
121899
121924
|
for (const watched of this.watchedSessions.values()) {
|
|
121900
121925
|
watched.unsubscribe();
|
|
121901
121926
|
}
|
|
121902
|
-
for (const timer2 of this.dispatchRetryTimers.values()) {
|
|
121903
|
-
clearTimeout(timer2);
|
|
121904
|
-
}
|
|
121905
121927
|
this.watchedSessions.clear();
|
|
121906
121928
|
this.sessionCheckChains.clear();
|
|
121907
121929
|
this.cancelCheckChains.clear();
|
|
121908
|
-
this.dispatchRetryTimers.clear();
|
|
121909
|
-
this.dispatchRetryAttempts.clear();
|
|
121910
|
-
this.dispatchRetryStartedAt.clear();
|
|
121911
121930
|
this.cancelSeenTurn.clear();
|
|
121912
121931
|
this.userResolver.clear();
|
|
121913
121932
|
this.started = false;
|
|
@@ -121983,14 +122002,15 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
121983
122002
|
}));
|
|
121984
122003
|
}
|
|
121985
122004
|
async reconcileSessionWatch(sessionId) {
|
|
121986
|
-
const
|
|
122005
|
+
const roomId = getSessionRoomId(sessionId);
|
|
122006
|
+
const record2 = await this.deps.workspaceDocument.repo.getDocMeta(roomId);
|
|
122007
|
+
const meta = isLoroRepoDocDeleted(record2) ? void 0 : record2?.meta;
|
|
121987
122008
|
const isOwned = meta?.machineId === this.deps.machineId && !meta?.isArchived;
|
|
121988
122009
|
if (!isOwned) {
|
|
121989
122010
|
const watched = this.watchedSessions.get(sessionId);
|
|
121990
122011
|
watched?.unsubscribe();
|
|
121991
122012
|
this.watchedSessions.delete(sessionId);
|
|
121992
122013
|
this.cancelCheckChains.delete(sessionId);
|
|
121993
|
-
this.clearDispatchRetry(sessionId);
|
|
121994
122014
|
this.cancelSeenTurn.delete(sessionId);
|
|
121995
122015
|
return;
|
|
121996
122016
|
}
|
|
@@ -122001,7 +122021,6 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
122001
122021
|
this.watchedSessions.delete(sessionId);
|
|
122002
122022
|
this.deps.logger.debug(`[${sessionId}] Unwatching idle session (no pending work)`);
|
|
122003
122023
|
}
|
|
122004
|
-
this.clearDispatchRetry(sessionId);
|
|
122005
122024
|
return;
|
|
122006
122025
|
}
|
|
122007
122026
|
if (!this.watchedSessions.has(sessionId)) {
|
|
@@ -122018,11 +122037,6 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
122018
122037
|
this.enqueueCancelCheck(sessionId);
|
|
122019
122038
|
this.enqueueSessionCheck(sessionId);
|
|
122020
122039
|
}
|
|
122021
|
-
async readSessionMeta(sessionId) {
|
|
122022
|
-
const roomId = getSessionRoomId(sessionId);
|
|
122023
|
-
const record2 = await this.deps.workspaceDocument.repo.getDocMeta(roomId);
|
|
122024
|
-
return isLoroRepoDocDeleted(record2) ? void 0 : record2?.meta;
|
|
122025
|
-
}
|
|
122026
122040
|
sessionNeedsActiveWatch(meta) {
|
|
122027
122041
|
const statusType = meta.status?.type;
|
|
122028
122042
|
if (statusType === "running" || statusType === "initializing" || statusType === "requestPermission") {
|
|
@@ -122037,7 +122051,7 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
122037
122051
|
if (meta.lastCanceledTurn && meta.lastCanceledTurn !== this.cancelSeenTurn.get(meta.id)) {
|
|
122038
122052
|
return true;
|
|
122039
122053
|
}
|
|
122040
|
-
if (
|
|
122054
|
+
if (meta.dispatchError?.code === SessionDispatchWatcher.DISPATCH_HISTORY_SYNC_TIMEOUT_CODE) {
|
|
122041
122055
|
return false;
|
|
122042
122056
|
}
|
|
122043
122057
|
if (!meta.lastHandledUserMsgId) {
|
|
@@ -122045,147 +122059,6 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
122045
122059
|
}
|
|
122046
122060
|
return false;
|
|
122047
122061
|
}
|
|
122048
|
-
static RECOVERABLE_DISPATCH_RETRY_INITIAL_DELAY_MS = 1e3;
|
|
122049
|
-
static RECOVERABLE_DISPATCH_RETRY_MAX_DELAY_MS = 6e4;
|
|
122050
|
-
static RECOVERABLE_DISPATCH_RETRY_MAX_ELAPSED_MS = 5 * 6e4;
|
|
122051
|
-
static DISPATCH_RECOVERY_UNHEALTHY_CODE = "dispatch_recovery_unhealthy";
|
|
122052
|
-
hasRecoverableDispatchSignal(meta) {
|
|
122053
|
-
const statusType = meta.status?.type;
|
|
122054
|
-
if (statusType === "running" || statusType === "initializing" || statusType === "requestPermission") {
|
|
122055
|
-
return true;
|
|
122056
|
-
}
|
|
122057
|
-
if (meta.processingUserMsgId) {
|
|
122058
|
-
return true;
|
|
122059
|
-
}
|
|
122060
|
-
return !!meta.latestUserMsgId && meta.latestUserMsgId !== meta.lastHandledUserMsgId;
|
|
122061
|
-
}
|
|
122062
|
-
isDispatchRecoveryUnhealthy(meta) {
|
|
122063
|
-
return meta.dispatchError?.code === SessionDispatchWatcher.DISPATCH_RECOVERY_UNHEALTHY_CODE;
|
|
122064
|
-
}
|
|
122065
|
-
clearDispatchRetry(sessionId) {
|
|
122066
|
-
const timer2 = this.dispatchRetryTimers.get(sessionId);
|
|
122067
|
-
if (timer2) {
|
|
122068
|
-
clearTimeout(timer2);
|
|
122069
|
-
this.dispatchRetryTimers.delete(sessionId);
|
|
122070
|
-
}
|
|
122071
|
-
this.dispatchRetryAttempts.delete(sessionId);
|
|
122072
|
-
this.dispatchRetryStartedAt.delete(sessionId);
|
|
122073
|
-
}
|
|
122074
|
-
scheduleRecoverableDispatchRetry(sessionId, meta) {
|
|
122075
|
-
if (!this.hasRecoverableDispatchSignal(meta)) {
|
|
122076
|
-
this.clearDispatchRetry(sessionId);
|
|
122077
|
-
return;
|
|
122078
|
-
}
|
|
122079
|
-
if (this.dispatchRetryTimers.has(sessionId)) {
|
|
122080
|
-
return;
|
|
122081
|
-
}
|
|
122082
|
-
const startedAt = this.dispatchRetryStartedAt.get(sessionId) ?? Date.now();
|
|
122083
|
-
this.dispatchRetryStartedAt.set(sessionId, startedAt);
|
|
122084
|
-
const elapsedMs = Date.now() - startedAt;
|
|
122085
|
-
if (elapsedMs >= SessionDispatchWatcher.RECOVERABLE_DISPATCH_RETRY_MAX_ELAPSED_MS) {
|
|
122086
|
-
void this.markDispatchRecoveryUnhealthy(sessionId, meta, elapsedMs).catch((error2) => {
|
|
122087
|
-
this.deps.logger.error(`[${sessionId}] Failed to mark dispatch recovery unhealthy: ${formatErrorMessage(error2)}`);
|
|
122088
|
-
});
|
|
122089
|
-
return;
|
|
122090
|
-
}
|
|
122091
|
-
const attempt = this.dispatchRetryAttempts.get(sessionId) ?? 0;
|
|
122092
|
-
const delayMs = this.calculateRecoverableDispatchRetryDelay(attempt);
|
|
122093
|
-
const attemptNumber = attempt + 1;
|
|
122094
|
-
this.dispatchRetryAttempts.set(sessionId, attemptNumber);
|
|
122095
|
-
this.deps.logger.debug(`[${sessionId}] Dispatch metadata is present but no user turn is visible; rebuilding session doc watch in ${delayMs}ms (attempt=${attemptNumber}, elapsedMs=${elapsedMs})`);
|
|
122096
|
-
const timer2 = setTimeout(() => {
|
|
122097
|
-
this.dispatchRetryTimers.delete(sessionId);
|
|
122098
|
-
if (!this.started) {
|
|
122099
|
-
return;
|
|
122100
|
-
}
|
|
122101
|
-
this.refreshSessionDocWatch(sessionId, `recoverable-dispatch-missing-turn:${attemptNumber}`).catch((error2) => {
|
|
122102
|
-
void this.handleDispatchRetryRefreshFailure(sessionId, meta, error2);
|
|
122103
|
-
});
|
|
122104
|
-
}, delayMs);
|
|
122105
|
-
const maybeUnrefTimer = timer2;
|
|
122106
|
-
maybeUnrefTimer.unref?.();
|
|
122107
|
-
this.dispatchRetryTimers.set(sessionId, timer2);
|
|
122108
|
-
}
|
|
122109
|
-
calculateRecoverableDispatchRetryDelay(attempt) {
|
|
122110
|
-
const exponentialDelayMs = Math.min(SessionDispatchWatcher.RECOVERABLE_DISPATCH_RETRY_INITIAL_DELAY_MS * 2 ** attempt, SessionDispatchWatcher.RECOVERABLE_DISPATCH_RETRY_MAX_DELAY_MS);
|
|
122111
|
-
const halfDelayMs = exponentialDelayMs / 2;
|
|
122112
|
-
return Math.floor(halfDelayMs + Math.random() * halfDelayMs);
|
|
122113
|
-
}
|
|
122114
|
-
async handleDispatchRetryRefreshFailure(sessionId, previousMeta, error2) {
|
|
122115
|
-
this.deps.logger.error(`[${sessionId}] Failed to refresh session doc watch for dispatch retry: ${formatErrorMessage(error2)}`);
|
|
122116
|
-
if (!this.started) {
|
|
122117
|
-
return;
|
|
122118
|
-
}
|
|
122119
|
-
let meta = previousMeta;
|
|
122120
|
-
try {
|
|
122121
|
-
const freshMeta = await this.readSessionMeta(sessionId);
|
|
122122
|
-
if (!freshMeta) {
|
|
122123
|
-
this.clearDispatchRetry(sessionId);
|
|
122124
|
-
return;
|
|
122125
|
-
}
|
|
122126
|
-
meta = freshMeta;
|
|
122127
|
-
} catch (metaError) {
|
|
122128
|
-
this.deps.logger.debug(`[${sessionId}] Failed to read fresh metadata after dispatch retry refresh failure; using previous retry signal: ${formatErrorMessage(metaError)}`);
|
|
122129
|
-
}
|
|
122130
|
-
if (!this.started) {
|
|
122131
|
-
return;
|
|
122132
|
-
}
|
|
122133
|
-
if (meta.machineId !== this.deps.machineId || meta.isArchived) {
|
|
122134
|
-
this.clearDispatchRetry(sessionId);
|
|
122135
|
-
return;
|
|
122136
|
-
}
|
|
122137
|
-
this.scheduleRecoverableDispatchRetry(sessionId, meta);
|
|
122138
|
-
}
|
|
122139
|
-
async markDispatchRecoveryUnhealthy(sessionId, previousMeta, elapsedMs) {
|
|
122140
|
-
this.clearDispatchRetry(sessionId);
|
|
122141
|
-
if (!this.started) {
|
|
122142
|
-
return;
|
|
122143
|
-
}
|
|
122144
|
-
let meta = previousMeta;
|
|
122145
|
-
try {
|
|
122146
|
-
const freshMeta = await this.readSessionMeta(sessionId);
|
|
122147
|
-
if (!freshMeta) {
|
|
122148
|
-
return;
|
|
122149
|
-
}
|
|
122150
|
-
meta = freshMeta;
|
|
122151
|
-
} catch (error2) {
|
|
122152
|
-
this.deps.logger.debug(`[${sessionId}] Failed to read fresh metadata before marking dispatch recovery unhealthy; using previous retry signal: ${formatErrorMessage(error2)}`);
|
|
122153
|
-
}
|
|
122154
|
-
if (!this.hasRecoverableDispatchSignal(meta) || meta.machineId !== this.deps.machineId || meta.isArchived) {
|
|
122155
|
-
return;
|
|
122156
|
-
}
|
|
122157
|
-
this.deps.logger.warn(`[${sessionId}] Dispatch recovery could not find a user turn after ${elapsedMs}ms; marking recovery unhealthy`);
|
|
122158
|
-
await this.deps.workspaceDocument.repo.upsertDocMeta?.(getSessionRoomId(sessionId), {
|
|
122159
|
-
status: SessionStatusFactory.idle(),
|
|
122160
|
-
latestUserMsgId: void 0,
|
|
122161
|
-
processingUserMsgId: void 0,
|
|
122162
|
-
dispatchError: {
|
|
122163
|
-
code: SessionDispatchWatcher.DISPATCH_RECOVERY_UNHEALTHY_CODE,
|
|
122164
|
-
message: "Dispatch recovery could not reconnect to this session after 5 minutes. Send a new message to retry.",
|
|
122165
|
-
at: getServerNow()
|
|
122166
|
-
}
|
|
122167
|
-
});
|
|
122168
|
-
const watched = this.watchedSessions.get(sessionId);
|
|
122169
|
-
watched?.unsubscribe();
|
|
122170
|
-
this.watchedSessions.delete(sessionId);
|
|
122171
|
-
this.sessionCheckChains.delete(sessionId);
|
|
122172
|
-
await this.deps.workspaceDocument.cleanSessionDoc(sessionId, {
|
|
122173
|
-
preserveStatus: true
|
|
122174
|
-
});
|
|
122175
|
-
}
|
|
122176
|
-
async refreshSessionDocWatch(sessionId, reason) {
|
|
122177
|
-
const watched = this.watchedSessions.get(sessionId);
|
|
122178
|
-
watched?.unsubscribe();
|
|
122179
|
-
this.watchedSessions.delete(sessionId);
|
|
122180
|
-
this.deps.logger.debug(`[${sessionId}] Refreshing session doc watch (${reason})`);
|
|
122181
|
-
await this.deps.workspaceDocument.cleanSessionDoc(sessionId, {
|
|
122182
|
-
preserveStatus: true
|
|
122183
|
-
});
|
|
122184
|
-
if (!this.started) {
|
|
122185
|
-
return;
|
|
122186
|
-
}
|
|
122187
|
-
await this.reconcileSessionWatch(sessionId);
|
|
122188
|
-
}
|
|
122189
122062
|
async maybeHandleSession(sessionId) {
|
|
122190
122063
|
const sessionDoc = await this.deps.workspaceDocument.getOrCreateSessionDoc(sessionId);
|
|
122191
122064
|
const meta = await sessionDoc.getMetaState();
|
|
@@ -122208,10 +122081,11 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
122208
122081
|
}
|
|
122209
122082
|
const nextUserTurn = await this.findOrAwaitDispatchableTurn(sessionId, sessionDoc, meta);
|
|
122210
122083
|
if (!nextUserTurn) {
|
|
122211
|
-
this.
|
|
122084
|
+
if (this.hasPendingUserTurnSignal(meta)) {
|
|
122085
|
+
await this.markMissingUserTurnRecovery(sessionId, meta);
|
|
122086
|
+
}
|
|
122212
122087
|
return;
|
|
122213
122088
|
}
|
|
122214
|
-
this.clearDispatchRetry(sessionId);
|
|
122215
122089
|
const freshMeta = await sessionDoc.getMetaState();
|
|
122216
122090
|
if (!freshMeta) {
|
|
122217
122091
|
return;
|
|
@@ -122407,6 +122281,27 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
122407
122281
|
}
|
|
122408
122282
|
static REALTIME_WAIT_TIMEOUT_MS = 3e4;
|
|
122409
122283
|
static REMOTE_SYNC_TIMEOUT_MS = 15e3;
|
|
122284
|
+
static HISTORY_SYNC_WAIT_TIMEOUT_MS = 5 * 6e4;
|
|
122285
|
+
static HISTORY_RECONNECT_JITTER_MIN_MS = 500;
|
|
122286
|
+
static HISTORY_RECONNECT_JITTER_MAX_MS = 1500;
|
|
122287
|
+
static DISPATCH_HISTORY_SYNC_TIMEOUT_CODE = "dispatch_recovery_unhealthy";
|
|
122288
|
+
static setUnrefTimeout(callback, delayMs) {
|
|
122289
|
+
const timer2 = setTimeout(callback, delayMs);
|
|
122290
|
+
if (typeof timer2 === "object" && "unref" in timer2) {
|
|
122291
|
+
timer2.unref();
|
|
122292
|
+
}
|
|
122293
|
+
return timer2;
|
|
122294
|
+
}
|
|
122295
|
+
static sleep(delayMs) {
|
|
122296
|
+
return new Promise((resolve2) => {
|
|
122297
|
+
SessionDispatchWatcher.setUnrefTimeout(resolve2, delayMs);
|
|
122298
|
+
});
|
|
122299
|
+
}
|
|
122300
|
+
static getReconnectJitterMs() {
|
|
122301
|
+
const min2 = SessionDispatchWatcher.HISTORY_RECONNECT_JITTER_MIN_MS;
|
|
122302
|
+
const max2 = SessionDispatchWatcher.HISTORY_RECONNECT_JITTER_MAX_MS;
|
|
122303
|
+
return min2 + Math.floor(Math.random() * (max2 - min2 + 1));
|
|
122304
|
+
}
|
|
122410
122305
|
async findOrAwaitDispatchableTurn(sessionId, sessionDoc, meta) {
|
|
122411
122306
|
const history = await sessionDoc.getHistory();
|
|
122412
122307
|
let turn = findNextDispatchableUserTurn(history, meta);
|
|
@@ -122417,9 +122312,12 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
122417
122312
|
if (turn) {
|
|
122418
122313
|
return turn;
|
|
122419
122314
|
}
|
|
122315
|
+
if (this.hasPendingUserTurnSignal(meta)) {
|
|
122316
|
+
return await this.waitForPendingUserTurnHistorySync(sessionId, sessionDoc, meta);
|
|
122317
|
+
}
|
|
122420
122318
|
await Promise.race([
|
|
122421
122319
|
sessionDoc.waitForRemoteSync(),
|
|
122422
|
-
new Promise((resolve2) =>
|
|
122320
|
+
new Promise((resolve2) => SessionDispatchWatcher.setUnrefTimeout(resolve2, SessionDispatchWatcher.REMOTE_SYNC_TIMEOUT_MS))
|
|
122423
122321
|
]);
|
|
122424
122322
|
turn = await this.checkHistoryAndQueue(sessionDoc, meta);
|
|
122425
122323
|
if (turn) {
|
|
@@ -122429,6 +122327,142 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
122429
122327
|
turn = await this.waitForDispatchableTurnFromMirror(sessionId, sessionDoc, meta);
|
|
122430
122328
|
return turn;
|
|
122431
122329
|
}
|
|
122330
|
+
hasPendingUserTurnSignal(meta) {
|
|
122331
|
+
if (meta.processingUserMsgId) {
|
|
122332
|
+
return true;
|
|
122333
|
+
}
|
|
122334
|
+
return Boolean(meta.latestUserMsgId && meta.latestUserMsgId !== meta.lastHandledUserMsgId);
|
|
122335
|
+
}
|
|
122336
|
+
async waitForPendingUserTurnHistorySync(sessionId, sessionDoc, meta) {
|
|
122337
|
+
this.deps.logger.debug(`[${sessionId}] Pending user turn metadata is visible but history is missing it; waiting up to ${SessionDispatchWatcher.HISTORY_SYNC_WAIT_TIMEOUT_MS / 1e3}s for history CRDT sync`);
|
|
122338
|
+
try {
|
|
122339
|
+
await sessionDoc.ensureDocRoomJoined();
|
|
122340
|
+
} catch (error2) {
|
|
122341
|
+
this.deps.logger.debug(`[${sessionId}] Failed to ensure session history room is joined before waiting: ${formatErrorMessage(error2)}`);
|
|
122342
|
+
}
|
|
122343
|
+
await sessionDoc.waitUntilSynced();
|
|
122344
|
+
const freshMeta = await sessionDoc.getMetaState() ?? meta;
|
|
122345
|
+
if (!this.hasPendingUserTurnSignal(freshMeta)) {
|
|
122346
|
+
this.deps.logger.debug(`[${sessionId}] Pending user turn pointer cleared during pre-wait sync; exiting wait`);
|
|
122347
|
+
return null;
|
|
122348
|
+
}
|
|
122349
|
+
const turnAfterJoin = await this.checkHistoryAndQueue(sessionDoc, freshMeta);
|
|
122350
|
+
if (turnAfterJoin) {
|
|
122351
|
+
return turnAfterJoin;
|
|
122352
|
+
}
|
|
122353
|
+
return await new Promise((resolve2) => {
|
|
122354
|
+
let settled = false;
|
|
122355
|
+
let reconnectAttempted = false;
|
|
122356
|
+
let unsubscribeMirror;
|
|
122357
|
+
let unsubscribeStatus;
|
|
122358
|
+
const cleanup = () => {
|
|
122359
|
+
if (settled) {
|
|
122360
|
+
return;
|
|
122361
|
+
}
|
|
122362
|
+
settled = true;
|
|
122363
|
+
clearTimeout(timer2);
|
|
122364
|
+
unsubscribeMirror?.();
|
|
122365
|
+
unsubscribeStatus?.();
|
|
122366
|
+
};
|
|
122367
|
+
const checkForTurn = () => {
|
|
122368
|
+
if (settled) {
|
|
122369
|
+
return;
|
|
122370
|
+
}
|
|
122371
|
+
void this.checkHistoryAndQueue(sessionDoc, freshMeta).then((turn) => {
|
|
122372
|
+
if (settled || !turn) {
|
|
122373
|
+
return;
|
|
122374
|
+
}
|
|
122375
|
+
cleanup();
|
|
122376
|
+
this.deps.logger.debug(`[${sessionId}] Found dispatchable turn after waiting for history CRDT sync`);
|
|
122377
|
+
resolve2(turn);
|
|
122378
|
+
}).catch((error2) => {
|
|
122379
|
+
if (settled) {
|
|
122380
|
+
return;
|
|
122381
|
+
}
|
|
122382
|
+
this.deps.logger.debug(`[${sessionId}] Error checking history during history sync wait: ${formatErrorMessage(error2)}`);
|
|
122383
|
+
});
|
|
122384
|
+
};
|
|
122385
|
+
const attemptReconnectOnce = (reason) => {
|
|
122386
|
+
if (settled || reconnectAttempted) {
|
|
122387
|
+
return;
|
|
122388
|
+
}
|
|
122389
|
+
reconnectAttempted = true;
|
|
122390
|
+
void (async () => {
|
|
122391
|
+
const jitterMs = SessionDispatchWatcher.getReconnectJitterMs();
|
|
122392
|
+
this.deps.logger.debug(`[${sessionId}] Session history room ${reason}; attempting one rejoin in ${jitterMs}ms`);
|
|
122393
|
+
await SessionDispatchWatcher.sleep(jitterMs);
|
|
122394
|
+
if (settled) {
|
|
122395
|
+
return;
|
|
122396
|
+
}
|
|
122397
|
+
try {
|
|
122398
|
+
await sessionDoc.rejoinDocRoom();
|
|
122399
|
+
} catch (error2) {
|
|
122400
|
+
this.deps.logger.debug(`[${sessionId}] Session history room rejoin failed: ${formatErrorMessage(error2)}`);
|
|
122401
|
+
}
|
|
122402
|
+
checkForTurn();
|
|
122403
|
+
})();
|
|
122404
|
+
};
|
|
122405
|
+
const handleRoomStatus = (status) => {
|
|
122406
|
+
if (settled || !status) {
|
|
122407
|
+
return;
|
|
122408
|
+
}
|
|
122409
|
+
if (status === "disconnected" || status === "error") {
|
|
122410
|
+
attemptReconnectOnce(status);
|
|
122411
|
+
}
|
|
122412
|
+
};
|
|
122413
|
+
const timer2 = SessionDispatchWatcher.setUnrefTimeout(() => {
|
|
122414
|
+
cleanup();
|
|
122415
|
+
this.deps.logger.warn(`[${sessionId}] User turn did not arrive in history after ${SessionDispatchWatcher.HISTORY_SYNC_WAIT_TIMEOUT_MS / 1e3}s; entering dispatch recovery`);
|
|
122416
|
+
resolve2(null);
|
|
122417
|
+
}, SessionDispatchWatcher.HISTORY_SYNC_WAIT_TIMEOUT_MS);
|
|
122418
|
+
unsubscribeMirror = sessionDoc.mirror?.subscribe(checkForTurn);
|
|
122419
|
+
if (!unsubscribeMirror) {
|
|
122420
|
+
this.deps.logger.debug(`[${sessionId}] Session mirror is unavailable during history sync wait`);
|
|
122421
|
+
}
|
|
122422
|
+
unsubscribeStatus = sessionDoc.onDocRoomStatusChange(handleRoomStatus);
|
|
122423
|
+
const currentStatus = sessionDoc.getDocRoomStatus();
|
|
122424
|
+
if (currentStatus) {
|
|
122425
|
+
handleRoomStatus(currentStatus);
|
|
122426
|
+
} else {
|
|
122427
|
+
attemptReconnectOnce("has no active subscription");
|
|
122428
|
+
}
|
|
122429
|
+
});
|
|
122430
|
+
}
|
|
122431
|
+
async markMissingUserTurnRecovery(sessionId, previousMeta) {
|
|
122432
|
+
const roomId = getSessionRoomId(sessionId);
|
|
122433
|
+
let meta = previousMeta;
|
|
122434
|
+
try {
|
|
122435
|
+
const record2 = await this.deps.workspaceDocument.repo.getDocMeta(roomId);
|
|
122436
|
+
if (!isLoroRepoDocDeleted(record2) && record2?.meta) {
|
|
122437
|
+
meta = record2.meta;
|
|
122438
|
+
}
|
|
122439
|
+
} catch (error2) {
|
|
122440
|
+
this.deps.logger.debug(`[${sessionId}] Failed to refresh metadata before dispatch recovery: ${formatErrorMessage(error2)}`);
|
|
122441
|
+
}
|
|
122442
|
+
if (meta.machineId !== this.deps.machineId || meta.isArchived || !this.hasPendingUserTurnSignal(meta)) {
|
|
122443
|
+
return;
|
|
122444
|
+
}
|
|
122445
|
+
const pendingUserMsgId = meta.processingUserMsgId ?? meta.latestUserMsgId ?? meta.lastHandledUserMsgId;
|
|
122446
|
+
const recoveryPatch = {
|
|
122447
|
+
status: SessionStatusFactory.idle(),
|
|
122448
|
+
dispatchError: {
|
|
122449
|
+
code: SessionDispatchWatcher.DISPATCH_HISTORY_SYNC_TIMEOUT_CODE,
|
|
122450
|
+
message: "Dispatch recovery could not reconnect to this session after 5 minutes. Send a new message to retry.",
|
|
122451
|
+
at: getServerNow()
|
|
122452
|
+
}
|
|
122453
|
+
};
|
|
122454
|
+
if (pendingUserMsgId) {
|
|
122455
|
+
recoveryPatch.lastHandledUserMsgId = pendingUserMsgId;
|
|
122456
|
+
recoveryPatch.latestUserMsgId = pendingUserMsgId;
|
|
122457
|
+
}
|
|
122458
|
+
await this.deps.workspaceDocument.repo.upsertDocMeta?.(roomId, recoveryPatch);
|
|
122459
|
+
const watched = this.watchedSessions.get(sessionId);
|
|
122460
|
+
watched?.unsubscribe();
|
|
122461
|
+
this.watchedSessions.delete(sessionId);
|
|
122462
|
+
await this.deps.workspaceDocument.cleanSessionDoc(sessionId, {
|
|
122463
|
+
preserveStatus: true
|
|
122464
|
+
});
|
|
122465
|
+
}
|
|
122432
122466
|
async waitForDispatchableTurnFromMirror(sessionId, sessionDoc, meta) {
|
|
122433
122467
|
return new Promise((resolve2) => {
|
|
122434
122468
|
let settled = false;
|
|
@@ -122437,7 +122471,7 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
|
|
|
122437
122471
|
clearTimeout(timer2);
|
|
122438
122472
|
unsubscribe2?.();
|
|
122439
122473
|
};
|
|
122440
|
-
const timer2 =
|
|
122474
|
+
const timer2 = SessionDispatchWatcher.setUnrefTimeout(() => {
|
|
122441
122475
|
if (settled) return;
|
|
122442
122476
|
cleanup();
|
|
122443
122477
|
this.deps.logger.debug(`[${sessionId}] No dispatchable turn found after ${SessionDispatchWatcher.REALTIME_WAIT_TIMEOUT_MS / 1e3}s timeout`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lody",
|
|
3
|
-
"version": "0.45.
|
|
3
|
+
"version": "0.45.1",
|
|
4
4
|
"description": "Lody Agent CLI tool for managing remote command execution",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -73,9 +73,9 @@
|
|
|
73
73
|
"ws": "^8.18.3",
|
|
74
74
|
"zod": "^4.1.5",
|
|
75
75
|
"@lody/cli-supervisor": "0.0.1",
|
|
76
|
+
"@lody/shared": "0.0.1",
|
|
76
77
|
"@lody/convex": "0.0.1",
|
|
77
78
|
"@lody/loro-streams-rpc": "0.0.1",
|
|
78
|
-
"@lody/shared": "0.0.1",
|
|
79
79
|
"loro-code": "0.0.1"
|
|
80
80
|
},
|
|
81
81
|
"files": [
|