lody 0.52.0 → 0.52.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 +142 -27
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -36822,7 +36822,7 @@ Mongoose Error Code: ${error2.code}` : ""}`
36822
36822
  return client;
36823
36823
  }
36824
36824
  const name = "lody";
36825
- const version$4 = "0.52.0";
36825
+ const version$4 = "0.52.1";
36826
36826
  const description$1 = "Lody Agent CLI tool for managing remote command execution";
36827
36827
  const type$2 = "module";
36828
36828
  const main$3 = "dist/index.js";
@@ -85476,13 +85476,14 @@ ${tailedOutput}` : null;
85476
85476
  };
85477
85477
  function withSlowOperationWarning(promise, logger2, operationName, sessionId, intervalMs = 1e4) {
85478
85478
  let completed = false;
85479
- let elapsedMs = 0;
85479
+ const startMs = Date.now();
85480
85480
  const interval2 = setInterval(() => {
85481
- elapsedMs += intervalMs;
85482
85481
  if (!completed) {
85482
+ const elapsedMs = Date.now() - startMs;
85483
85483
  logger2.debug(`[${sessionId}] Operation "${operationName}" is still pending after ${Math.round(elapsedMs / 1e3)}s - possible hang`);
85484
85484
  }
85485
85485
  }, intervalMs);
85486
+ interval2.unref?.();
85486
85487
  return promise.finally(() => {
85487
85488
  completed = true;
85488
85489
  clearInterval(interval2);
@@ -98397,11 +98398,13 @@ stream:${scope2.streamId}`;
98397
98398
  machineId = null;
98398
98399
  machineKey = null;
98399
98400
  sessionKeys = /* @__PURE__ */ new Map();
98401
+ machineHeartbeatSeq = 0;
98400
98402
  started = false;
98401
98403
  stopped = false;
98402
98404
  start() {
98403
98405
  if (this.started || this.stopped) return;
98404
98406
  this.started = true;
98407
+ this.options.logger.debug(`[${this.options.workspaceId}] Joining Loro presence room`);
98405
98408
  void this.transport.join({
98406
98409
  onStatusChange: (status) => {
98407
98410
  this.options.logger.debug(`[${this.options.workspaceId}] Loro presence room status: ${status}`);
@@ -98413,6 +98416,7 @@ stream:${scope2.streamId}`;
98413
98416
  }
98414
98417
  if (result.ok) {
98415
98418
  this.subscription = result.value;
98419
+ this.options.logger.debug(`[${this.options.workspaceId}] Loro presence subscription established`);
98416
98420
  return;
98417
98421
  }
98418
98422
  this.options.logger.debug(`[${this.options.workspaceId}] Failed to join Loro presence room: ${formatErrorMessage(result.error)}`);
@@ -98442,6 +98446,7 @@ stream:${scope2.streamId}`;
98442
98446
  }
98443
98447
  writeMachineHeartbeat() {
98444
98448
  if (this.stopped || !this.machineId || !this.machineKey) return;
98449
+ const seq2 = ++this.machineHeartbeatSeq;
98445
98450
  const state2 = {
98446
98451
  kind: "machine",
98447
98452
  machineId: this.machineId,
@@ -98449,6 +98454,7 @@ stream:${scope2.streamId}`;
98449
98454
  updatedAt: getServerNow()
98450
98455
  };
98451
98456
  this.store.set(this.machineKey, state2);
98457
+ this.options.logger.debug(`[${this.options.workspaceId}] Loro presence machine heartbeat written (seq=${seq2} updatedAt=${state2.updatedAt})`);
98452
98458
  }
98453
98459
  setSessionPresence(args2) {
98454
98460
  if (this.stopped) return;
@@ -116856,6 +116862,18 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
116856
116862
  let metaSub = null;
116857
116863
  const initialMetaSync = createDeferred();
116858
116864
  let initialMetaSyncCompleted = false;
116865
+ let persistFlushSeq = 0;
116866
+ const flushRepoForPersist = async (reason) => {
116867
+ const currentRepo = repo;
116868
+ if (!currentRepo) {
116869
+ return;
116870
+ }
116871
+ const seq2 = ++persistFlushSeq;
116872
+ const startedAt = Date.now();
116873
+ logger2.debug(`[${workspaceId}] Loro repo flush started (reason=${reason} seq=${seq2})`);
116874
+ await withSlowOperationWarning(currentRepo.flush(), logger2, `loro-repo.flush(${reason}, seq=${seq2})`, workspaceId);
116875
+ logger2.debug(`[${workspaceId}] Loro repo flush completed (reason=${reason} seq=${seq2} duration=${Date.now() - startedAt}ms)`);
116876
+ };
116859
116877
  const transportAdapter = new StreamsTransportAdapter({
116860
116878
  bucketId: LORO_STREAMS_BUCKET_ID,
116861
116879
  metaStreamId: getLoroMetaStreamId(workspaceId),
@@ -116869,10 +116887,10 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
116869
116887
  debounceMs: 5e3
116870
116888
  },
116871
116889
  onPersistDoc: async () => {
116872
- await repo?.flush();
116890
+ await flushRepoForPersist("doc");
116873
116891
  },
116874
116892
  onPersistMeta: async () => {
116875
- await repo?.flush();
116893
+ await flushRepoForPersist("meta");
116876
116894
  }
116877
116895
  });
116878
116896
  let manager = null;
@@ -117166,8 +117184,11 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
117166
117184
  if (!this.machine) {
117167
117185
  return;
117168
117186
  }
117169
- await this.machine.sendHeartbeat();
117187
+ const startedAt = Date.now();
117188
+ this.logger.debug(`[${this.workspaceId}] Machine heartbeat writing presence and meta`);
117170
117189
  this.presenceRuntime?.writeMachineHeartbeat();
117190
+ await withSlowOperationWarning(this.machine.sendHeartbeat(), this.logger, "machine.sendHeartbeat repo.upsertDocMeta(lastSeen)", this.workspaceId);
117191
+ this.logger.debug(`[${this.workspaceId}] Machine heartbeat meta write completed (duration=${Date.now() - startedAt}ms)`);
117171
117192
  }
117172
117193
  async restoreMachineDocument(machineId) {
117173
117194
  const machineRoomId = getMachineRoomId(machineId);
@@ -122106,7 +122127,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122106
122127
  this.supportsClose = !!closeCapability;
122107
122128
  const hasCloseMethod = typeof connection.closeSession === "function";
122108
122129
  this.logger.debug(`[${this.options.sessionId}] ACP capabilities (loadSession=${this.supportsLoadSession ? "yes" : "no"} loadSessionMethod=${hasLoadSessionMethod ? "yes" : "no"} resume=${this.supportsResume ? "yes" : "no"} resumeMethod=${hasResumeMethod ? "yes" : "no"} close=${this.supportsClose ? "yes" : "no"} closeMethod=${hasCloseMethod ? "yes" : "no"})`);
122109
- this.logger.debug(`[${this.options.sessionId}] About to start new ACP session`);
122130
+ this.logger.debug(`[${this.options.sessionId}] About to establish ACP session`);
122110
122131
  const newSessionStart = performance$1.now();
122111
122132
  this.options.onStartupStage?.({
122112
122133
  type: "new_session_start"
@@ -122133,11 +122154,12 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122133
122154
  const loadStart = performance$1.now();
122134
122155
  try {
122135
122156
  this.logger.debug(`[${this.options.sessionId}] Attempting ACP loadSession (acpSessionId=${resumeSessionId})`);
122136
- const loadResponse = await withAbort(connection.loadSession({
122157
+ const ACP_LOAD_SESSION_TIMEOUT_MS = Math.max(0, timeoutOptions.loadSessionTimeoutMs ?? timeoutOptions.newSessionTimeoutMs ?? 12e4);
122158
+ const loadResponse = await withTimeout$2(withAbort(connection.loadSession({
122137
122159
  sessionId: resumeSessionId,
122138
122160
  cwd: workdir,
122139
122161
  mcpServers
122140
- }), startupAbort);
122162
+ }), startupAbort), this.logger, "connection.loadSession", this.options.sessionId, ACP_LOAD_SESSION_TIMEOUT_MS);
122141
122163
  const loadDurationMs = performance$1.now() - loadStart;
122142
122164
  sessionResponse = {
122143
122165
  sessionId: resumeSessionId,
@@ -122158,11 +122180,12 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122158
122180
  const resumeStart = performance$1.now();
122159
122181
  try {
122160
122182
  this.logger.debug(`[${this.options.sessionId}] Attempting ACP resume (acpSessionId=${resumeSessionId})`);
122161
- const resumeResponse = await withAbort(connection.resumeSession({
122183
+ const ACP_RESUME_SESSION_TIMEOUT_MS = Math.max(0, timeoutOptions.resumeSessionTimeoutMs ?? timeoutOptions.newSessionTimeoutMs ?? 12e4);
122184
+ const resumeResponse = await withTimeout$2(withAbort(connection.resumeSession({
122162
122185
  sessionId: resumeSessionId,
122163
122186
  cwd: workdir,
122164
122187
  mcpServers
122165
- }), startupAbort);
122188
+ }), startupAbort), this.logger, "connection.resumeSession", this.options.sessionId, ACP_RESUME_SESSION_TIMEOUT_MS);
122166
122189
  const resumeDurationMs = performance$1.now() - resumeStart;
122167
122190
  sessionResponse = {
122168
122191
  sessionId: resumeSessionId,
@@ -122919,6 +122942,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122919
122942
  args: args2,
122920
122943
  spawnImpl: options.spawnImpl
122921
122944
  });
122945
+ options.logger.debug(`[acp-startup] spawned ACP process (cliType=${options.cliType} agentType=${options.agentType} workdir=${options.workdir})`);
122922
122946
  const stderrStream = agentProcess.stderr;
122923
122947
  let stderrTail = "";
122924
122948
  if (stderrStream) {
@@ -122980,6 +123004,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122980
123004
  const input2 = createStdinWritableStream(agentProcess.stdin);
122981
123005
  const stream2 = ndJsonStream(input2, output);
122982
123006
  try {
123007
+ options.logger.debug("[acp-startup] creating ACP client");
122983
123008
  const started = await createAcpClient({
122984
123009
  stream: stream2,
122985
123010
  workdir: options.workdir,
@@ -122994,6 +123019,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122994
123019
  onRequestPermission: options.onRequestPermission,
122995
123020
  startupAbort: startupMonitor.abortPromise
122996
123021
  });
123022
+ options.logger.debug(`[acp-startup] ACP client ready (acpSessionId=${started.acpSessionId})`);
122997
123023
  return {
122998
123024
  agentProcess,
122999
123025
  client: started.client,
@@ -123137,6 +123163,8 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
123137
123163
  const sleep2 = (ms2) => new Promise((resolve2) => setTimeout(resolve2, ms2));
123138
123164
  const tryGenerateWithArgs = async (extraArgs) => {
123139
123165
  let collectedText = "";
123166
+ const startupStartedAt = Date.now();
123167
+ options.logger.debug(`[title-generator] Starting isolated title ACP agent (cliType=${options.cliType} agentType=${options.agentType})`);
123140
123168
  const { agentProcess, client, acpSessionId } = await startLocalAcpAgent({
123141
123169
  cliType: options.cliType,
123142
123170
  agentType: options.agentType,
@@ -123162,6 +123190,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
123162
123190
  }),
123163
123191
  extraArgs
123164
123192
  });
123193
+ options.logger.debug(`[title-generator] Isolated title ACP agent ready (acpSessionId=${acpSessionId} startupDuration=${Date.now() - startupStartedAt}ms)`);
123165
123194
  try {
123166
123195
  const prompt2 = buildTitlePrompt(options.taskPrompt);
123167
123196
  const configOptionValues = options.titleConfig?.configOptionValues;
@@ -123170,7 +123199,9 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
123170
123199
  await client?.setSessionConfigOption(acpSessionId, key2, value);
123171
123200
  }
123172
123201
  }
123202
+ options.logger.debug(`[title-generator] Sending title prompt (acpSessionId=${acpSessionId})`);
123173
123203
  const response = await client?.prompt(acpSessionId, prompt2);
123204
+ options.logger.debug(`[title-generator] Title prompt returned (acpSessionId=${acpSessionId})`);
123174
123205
  const deadline = Date.now() + 1e4;
123175
123206
  while (Date.now() < deadline && collectedText.trim() === "") {
123176
123207
  await sleep2(100);
@@ -127168,6 +127199,7 @@ $mem | ConvertTo-Json -Compress
127168
127199
  const resumeSource = requestedResumeSessionId ? "request" : storedResumeSessionId ? "meta" : "none";
127169
127200
  self2.deps.logger.debug(`[${sessionId}] Session not found in memory; restoring (project=${project?.kind === "github" ? project.repoFullName : project?.kind === "local" ? `local:${project.localProjectId}` : "none"} resume=${resumeSessionId ? "yes" : "no"} resumeSource=${resumeSource} resumeSessionId=${resumeSessionId ?? "none"})`);
127170
127201
  yield* self2.tryPromise(() => sessionDoc.setStatus(SessionStatusFactory.initializing("resuming")));
127202
+ self2.deps.logger.debug(`[${sessionId}] Resuming status published; preparing session restore (resumeSource=${resumeSource} resumeSessionId=${resumeSessionId ?? "none"})`);
127171
127203
  const restoreConfig = {
127172
127204
  sessionId,
127173
127205
  workspaceId: message.workspaceId,
@@ -127191,11 +127223,13 @@ $mem | ConvertTo-Json -Compress
127191
127223
  if (resumeSessionId) {
127192
127224
  yield* acpReplaySuppression.acquire;
127193
127225
  }
127226
+ self2.deps.logger.debug(`[${sessionId}] Session restore createSession started (resumeSessionId=${resumeSessionId ?? "none"})`);
127194
127227
  const restoredSession = yield* ctx.trackPendingSession(() => self2.deps.sessionManager.createSession(restoreConfig, {
127195
127228
  resumeSessionId
127196
127229
  }), {
127197
127230
  terminateOnCancel: true
127198
127231
  });
127232
+ self2.deps.logger.debug(`[${sessionId}] Session restore createSession returned (acpSessionId=${restoredSession.acpSessionId ?? "null"})`);
127199
127233
  ctx.bindSession(restoredSession);
127200
127234
  yield* ctx.abortIfCancelled({
127201
127235
  terminateSession: true
@@ -127221,9 +127255,11 @@ $mem | ConvertTo-Json -Compress
127221
127255
  ...restoreConfig
127222
127256
  };
127223
127257
  const fallbackAttempt = gen(function* () {
127258
+ self2.deps.logger.debug(`[${sessionId}] Fallback restore createSession started`);
127224
127259
  const fallbackSession = yield* ctx.trackPendingSession(() => self2.deps.sessionManager.createSession(fallbackConfig), {
127225
127260
  terminateOnCancel: true
127226
127261
  });
127262
+ self2.deps.logger.debug(`[${sessionId}] Fallback restore createSession returned (acpSessionId=${fallbackSession.acpSessionId ?? "null"})`);
127227
127263
  ctx.bindSession(fallbackSession);
127228
127264
  yield* ctx.abortIfCancelled({
127229
127265
  terminateSession: true
@@ -128343,6 +128379,7 @@ $mem | ConvertTo-Json -Compress
128343
128379
  static REALTIME_WAIT_TIMEOUT_MS = 3e4;
128344
128380
  static REMOTE_SYNC_TIMEOUT_MS = 15e3;
128345
128381
  static HISTORY_SYNC_WAIT_TIMEOUT_MS = 5 * 6e4;
128382
+ static HISTORY_SYNC_PROGRESS_LOG_MS = 3e4;
128346
128383
  static HISTORY_RECONNECT_JITTER_MIN_MS = 500;
128347
128384
  static HISTORY_RECONNECT_JITTER_MAX_MS = 1500;
128348
128385
  static setUnrefTimeout(callback, delayMs) {
@@ -128415,12 +128452,17 @@ $mem | ConvertTo-Json -Compress
128415
128452
  let reconnectAttempted = false;
128416
128453
  let unsubscribeMirror;
128417
128454
  let unsubscribeStatus;
128455
+ let progressTimer = null;
128456
+ const waitStartedAt = Date.now();
128418
128457
  const cleanup = () => {
128419
128458
  if (settled) {
128420
128459
  return;
128421
128460
  }
128422
128461
  settled = true;
128423
128462
  clearTimeout(timer2);
128463
+ if (progressTimer) {
128464
+ clearInterval(progressTimer);
128465
+ }
128424
128466
  unsubscribeMirror?.();
128425
128467
  unsubscribeStatus?.();
128426
128468
  };
@@ -128475,6 +128517,13 @@ $mem | ConvertTo-Json -Compress
128475
128517
  this.deps.logger.warn(`[${sessionId}] User turn did not arrive in history after ${SessionDispatchWatcher.HISTORY_SYNC_WAIT_TIMEOUT_MS / 1e3}s; entering dispatch recovery`);
128476
128518
  resolve2(null);
128477
128519
  }, SessionDispatchWatcher.HISTORY_SYNC_WAIT_TIMEOUT_MS);
128520
+ progressTimer = setInterval(() => {
128521
+ if (settled) {
128522
+ return;
128523
+ }
128524
+ this.deps.logger.warn(`[${sessionId}] Still waiting for pending user turn history sync (elapsed=${Date.now() - waitStartedAt}ms docRoom=${sessionDoc.getDocRoomStatus() ?? "unknown"})`);
128525
+ }, SessionDispatchWatcher.HISTORY_SYNC_PROGRESS_LOG_MS);
128526
+ progressTimer.unref?.();
128478
128527
  unsubscribeMirror = sessionDoc.mirror?.subscribe(checkForTurn);
128479
128528
  if (!unsubscribeMirror) {
128480
128529
  this.deps.logger.debug(`[${sessionId}] Session mirror is unavailable during history sync wait`);
@@ -132059,6 +132108,8 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
132059
132108
  static CONTEXT_WINDOW_USAGE_THROTTLE_MS = 400;
132060
132109
  permissionRequestStartTimes = /* @__PURE__ */ new Map();
132061
132110
  machineHeartbeatTimer = null;
132111
+ machineHeartbeatInFlightStartedAt = null;
132112
+ machineHeartbeatSeq = 0;
132062
132113
  static MACHINE_HEARTBEAT_INTERVAL_MS = 2e4;
132063
132114
  evictForMemoryPressureFn = async () => ({
132064
132115
  availableMemoryBytes: 0,
@@ -132089,24 +132140,35 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
132089
132140
  startMachineHeartbeat() {
132090
132141
  this.stopMachineHeartbeat();
132091
132142
  this.logger.debug(`Machine heartbeat started (${Math.round(MessageHandler.MACHINE_HEARTBEAT_INTERVAL_MS / 1e3)}s interval)`);
132092
- void this.workspaceDocument.sendMachineHeartbeat().then(() => {
132093
- this.logger.debug("Machine heartbeat sent (initial)");
132094
- }).catch((error2) => {
132095
- this.logger.debug(`Initial machine heartbeat failed: ${formatErrorMessage(error2)}`);
132096
- });
132143
+ this.sendMachineHeartbeatWithDiagnostics("initial");
132097
132144
  this.machineHeartbeatTimer = setInterval(() => {
132098
- void this.workspaceDocument.sendMachineHeartbeat().then(() => {
132099
- this.logger.debug("Machine heartbeat sent");
132100
- }).catch((error2) => {
132101
- this.logger.debug(`Machine heartbeat failed: ${formatErrorMessage(error2)}`);
132102
- });
132145
+ this.sendMachineHeartbeatWithDiagnostics("periodic");
132103
132146
  }, MessageHandler.MACHINE_HEARTBEAT_INTERVAL_MS);
132147
+ this.machineHeartbeatTimer.unref?.();
132148
+ }
132149
+ sendMachineHeartbeatWithDiagnostics(reason) {
132150
+ const now2 = Date.now();
132151
+ if (this.machineHeartbeatInFlightStartedAt !== null) {
132152
+ this.logger.warn(`Machine heartbeat skipped (reason=${reason}); previous heartbeat still pending for ${now2 - this.machineHeartbeatInFlightStartedAt}ms`);
132153
+ return;
132154
+ }
132155
+ const seq2 = ++this.machineHeartbeatSeq;
132156
+ this.machineHeartbeatInFlightStartedAt = now2;
132157
+ this.logger.debug(`Machine heartbeat tick started (reason=${reason} seq=${seq2})`);
132158
+ void withSlowOperationWarning(this.workspaceDocument.sendMachineHeartbeat(), this.logger, `workspaceDocument.sendMachineHeartbeat(reason=${reason}, seq=${seq2})`, this.machineId).then(() => {
132159
+ this.logger.debug(`Machine heartbeat sent (reason=${reason} seq=${seq2} duration=${Date.now() - now2}ms)`);
132160
+ }).catch((error2) => {
132161
+ this.logger.debug(`Machine heartbeat failed (reason=${reason} seq=${seq2} duration=${Date.now() - now2}ms): ${formatErrorMessage(error2)}`);
132162
+ }).finally(() => {
132163
+ this.machineHeartbeatInFlightStartedAt = null;
132164
+ });
132104
132165
  }
132105
132166
  stopMachineHeartbeat() {
132106
132167
  if (this.machineHeartbeatTimer) {
132107
132168
  clearInterval(this.machineHeartbeatTimer);
132108
132169
  this.machineHeartbeatTimer = null;
132109
132170
  }
132171
+ this.machineHeartbeatInFlightStartedAt = null;
132110
132172
  }
132111
132173
  preferredBaseBranch = (process.env.LODY_BASE_BRANCH || "main").trim() || "main";
132112
132174
  resolveGitHubProjectBranch(meta, preferredBranch) {
@@ -134850,6 +134912,8 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
134850
134912
  }
134851
134913
  queue;
134852
134914
  isStopped = false;
134915
+ static QUEUE_WAIT_WARNING_MS = 1e4;
134916
+ static PROCESSING_WARNING_MS = 3e4;
134853
134917
  enqueue(message, handler) {
134854
134918
  if (this.isStopped) {
134855
134919
  this.logger.debug("MessageProcessor is stopped, ignoring new message");
@@ -134857,11 +134921,30 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
134857
134921
  }
134858
134922
  const sessionId = this.extractSessionId(message);
134859
134923
  const queueKey = this.extractQueueKey(message);
134860
- this.logger.debug(`Enqueued message type=${message.type} sessionId=${sessionId || "N/A"}`);
134924
+ const queuedAt = Date.now();
134925
+ let started = false;
134926
+ const waitWarning = setInterval(() => {
134927
+ if (started) {
134928
+ return;
134929
+ }
134930
+ this.logger.warn(`Message still waiting in queue type=${message.type} sessionId=${sessionId || "N/A"} queuedFor=${Date.now() - queuedAt}ms active=${this.queue.active} waiting=${this.queue.waiting}`);
134931
+ }, MessageProcessor.QUEUE_WAIT_WARNING_MS);
134932
+ waitWarning.unref?.();
134933
+ this.logger.debug(`Enqueued message type=${message.type} sessionId=${sessionId || "N/A"} active=${this.queue.active} waiting=${this.queue.waiting}`);
134861
134934
  void this.queue.enqueue(queueKey, async () => {
134862
134935
  const startTime = Date.now();
134936
+ started = true;
134937
+ clearInterval(waitWarning);
134938
+ let processingCompleted = false;
134939
+ const processingWarning = setInterval(() => {
134940
+ if (processingCompleted) {
134941
+ return;
134942
+ }
134943
+ this.logger.warn(`Message still processing type=${message.type} sessionId=${sessionId || "N/A"} runningFor=${Date.now() - startTime}ms active=${this.queue.active} waiting=${this.queue.waiting}`);
134944
+ }, MessageProcessor.PROCESSING_WARNING_MS);
134945
+ processingWarning.unref?.();
134863
134946
  try {
134864
- this.logger.debug(`Processing message type=${message.type} sessionId=${sessionId || "N/A"}`);
134947
+ this.logger.debug(`Processing message type=${message.type} sessionId=${sessionId || "N/A"} queueWait=${startTime - queuedAt}ms active=${this.queue.active} waiting=${this.queue.waiting}`);
134865
134948
  await handler(message);
134866
134949
  const duration2 = Date.now() - startTime;
134867
134950
  this.logger.debug(`Processed message type=${message.type} sessionId=${sessionId || "N/A"} duration=${duration2}ms`);
@@ -134871,6 +134954,9 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
134871
134954
  const err2 = error2 instanceof Error ? error2 : new Error(String(error2));
134872
134955
  this.logger.error(`Failed to process message type=${message.type} sessionId=${sessionId || "N/A"} duration=${duration2}ms: ${err2.message}`);
134873
134956
  this.emit("message:error", err2, message);
134957
+ } finally {
134958
+ processingCompleted = true;
134959
+ clearInterval(processingWarning);
134874
134960
  }
134875
134961
  });
134876
134962
  }
@@ -135235,6 +135321,8 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
135235
135321
  }
135236
135322
  async dispatchLocalMessageForResponse(message) {
135237
135323
  const handler = this.requireHandler();
135324
+ const dispatchStartedAt = Date.now();
135325
+ this.options.logger.debug(`Local control dispatch started type=${message.type} sessionId=${"sessionId" in message ? message.sessionId : "N/A"} active=${this.messageProcessor.getActiveSessions()} waiting=${this.messageProcessor.getQueueSize()}`);
135238
135326
  return await new Promise((resolve2, reject) => {
135239
135327
  this.messageProcessor.enqueue(message, async (nextMessage) => {
135240
135328
  const responses = [];
@@ -135244,6 +135332,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
135244
135332
  return;
135245
135333
  }
135246
135334
  settled = true;
135335
+ this.options.logger.debug(`Local control dispatch resolved type=${message.type} sessionId=${"sessionId" in message ? message.sessionId : "N/A"} duration=${Date.now() - dispatchStartedAt}ms responses=${responses.length} active=${this.messageProcessor.getActiveSessions()} waiting=${this.messageProcessor.getQueueSize()}`);
135247
135336
  resolve2([
135248
135337
  ...responses
135249
135338
  ]);
@@ -135253,6 +135342,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
135253
135342
  return;
135254
135343
  }
135255
135344
  settled = true;
135345
+ this.options.logger.debug(`Local control dispatch rejected type=${message.type} sessionId=${"sessionId" in message ? message.sessionId : "N/A"} duration=${Date.now() - dispatchStartedAt}ms: ${error2 instanceof Error ? error2.message : String(error2)}`);
135256
135346
  reject(error2);
135257
135347
  };
135258
135348
  const context2 = {
@@ -142420,7 +142510,7 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
142420
142510
  await this.workspaceDocument.getOrCreateSessionDoc(config2.sessionId);
142421
142511
  this.logger.debug(`[${config2.sessionId}] Session will enter create inner`);
142422
142512
  const createInnerStart = performance$1.now();
142423
- const session = await this.createSessionInner(config2);
142513
+ const session = await withSlowOperationWarning(this.createSessionInner(config2), this.logger, "session.createSessionInner", config2.sessionId);
142424
142514
  session.ghTokenInjected = ghTokenInjected;
142425
142515
  const createInnerDurationMs = performance$1.now() - createInnerStart;
142426
142516
  this.logger.debug(`[${config2.sessionId}] Session create inner finished in ${Math.round(createInnerDurationMs)}ms`);
@@ -142441,7 +142531,7 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
142441
142531
  this.logger.debug(`[${sessionId}] About to call session.createAgent`);
142442
142532
  try {
142443
142533
  const createAgentStart = performance$1.now();
142444
- acpSessionId = await session.createAgent({
142534
+ acpSessionId = await withSlowOperationWarning(session.createAgent({
142445
142535
  cliType: config2.agentCliType,
142446
142536
  agentType: config2.agentType,
142447
142537
  command: setting.exec.command,
@@ -142501,7 +142591,7 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
142501
142591
  onCodexImageGenerationEnd: (event) => {
142502
142592
  this.emit("onCodexImageGenerationEnd", sessionId, event);
142503
142593
  }
142504
- });
142594
+ }), this.logger, `session.createAgent(resume=${requestedResumeSessionId ? "yes" : "no"})`, sessionId);
142505
142595
  const createAgentDurationMs = performance$1.now() - createAgentStart;
142506
142596
  this.logger.debug(`[${sessionId}] Session createAgent finished in ${Math.round(createAgentDurationMs)}ms (acpSessionId=${acpSessionId})`);
142507
142597
  this.logger.debug(`[${sessionId}] createAgent returned successfully`);
@@ -145144,6 +145234,23 @@ Received ${signal}, shutting down gracefully...` : "\nShutting down gracefully..
145144
145234
  shutdown: shutdown2
145145
145235
  };
145146
145236
  }
145237
+ function startEventLoopLagMonitor(logger2, options) {
145238
+ const intervalMs = Math.max(100, options.intervalMs ?? 1e3);
145239
+ const warnThresholdMs = Math.max(intervalMs, options.warnThresholdMs ?? 5e3);
145240
+ let expectedAt = Date.now() + intervalMs;
145241
+ const timer2 = setInterval(() => {
145242
+ const now2 = Date.now();
145243
+ const lagMs = now2 - expectedAt;
145244
+ expectedAt = now2 + intervalMs;
145245
+ if (lagMs >= warnThresholdMs) {
145246
+ logger2.warn(`[event-loop] ${options.label} timer lag detected: fired ${Math.round(lagMs)}ms late (threshold=${warnThresholdMs}ms interval=${intervalMs}ms)`);
145247
+ }
145248
+ }, intervalMs);
145249
+ timer2.unref?.();
145250
+ return {
145251
+ stop: () => clearInterval(timer2)
145252
+ };
145253
+ }
145147
145254
  const ELECTRON_BOOTSTRAP_ENV = "LODY_ELECTRON_BOOTSTRAP";
145148
145255
  const ELECTRON_SESSION_TOKEN_ENV = "LODY_ELECTRON_SESSION_TOKEN";
145149
145256
  const ELECTRON_SESSION_USER_ID_ENV = "LODY_ELECTRON_SESSION_USER_ID";
@@ -145371,7 +145478,13 @@ Received ${signal}, shutting down gracefully...` : "\nShutting down gracefully..
145371
145478
  runtimeStateReporter,
145372
145479
  onFatalAuthFailure: (error2) => triggerFatalAuthShutdown?.(error2)
145373
145480
  });
145374
- registerProcessCleanup(() => fleet.shutdown());
145481
+ const eventLoopLagMonitor = startEventLoopLagMonitor(logger2, {
145482
+ label: "lody start"
145483
+ });
145484
+ registerProcessCleanup(async () => {
145485
+ eventLoopLagMonitor.stop();
145486
+ await fleet.shutdown();
145487
+ });
145375
145488
  const shutdownSignals = process.platform === "win32" ? [
145376
145489
  "SIGINT",
145377
145490
  "SIGTERM",
@@ -145387,6 +145500,7 @@ Received ${signal}, shutting down gracefully...` : "\nShutting down gracefully..
145387
145500
  logger: logger2,
145388
145501
  shutdown: async () => {
145389
145502
  unregisterProcessCleanup();
145503
+ eventLoopLagMonitor.stop();
145390
145504
  await fleet.shutdown();
145391
145505
  },
145392
145506
  flushTelemetry: () => shutdownPostHog(),
@@ -145425,6 +145539,7 @@ Received ${signal}, shutting down gracefully...` : "\nShutting down gracefully..
145425
145539
  });
145426
145540
  shutdownController.unregister();
145427
145541
  unregisterProcessCleanup();
145542
+ eventLoopLagMonitor.stop();
145428
145543
  await fleet.shutdown().catch((err2) => {
145429
145544
  logger2.error(`Cleanup failed: ${err2 instanceof Error ? err2.message : "Unknown error"}`);
145430
145545
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lody",
3
- "version": "0.52.0",
3
+ "version": "0.52.1",
4
4
  "description": "Lody Agent CLI tool for managing remote command execution",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -73,11 +73,11 @@
73
73
  "winston-transport": "^4.7.1",
74
74
  "ws": "^8.18.3",
75
75
  "zod": "^4.1.5",
76
- "@lody/cli-supervisor": "0.0.1",
77
76
  "@lody/convex": "0.0.1",
78
77
  "@lody/loro-streams-rpc": "0.0.1",
78
+ "@lody/shared": "0.0.1",
79
79
  "loro-code": "0.0.1",
80
- "@lody/shared": "0.0.1"
80
+ "@lody/cli-supervisor": "0.0.1"
81
81
  },
82
82
  "files": [
83
83
  "dist",