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.
Files changed (2) hide show
  1. package/dist/index.js +198 -164
  2. 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.0";
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 meta = await this.readSessionMeta(sessionId);
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 (this.isDispatchRecoveryUnhealthy(meta)) {
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.scheduleRecoverableDispatchRetry(sessionId, meta);
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) => setTimeout(resolve2, SessionDispatchWatcher.REMOTE_SYNC_TIMEOUT_MS))
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 = setTimeout(() => {
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.0",
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": [