lody 0.52.0 → 0.52.2

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 +154 -47
  2. package/package.json +4 -4
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.2";
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";
@@ -36865,7 +36865,7 @@ Mongoose Error Code: ${error2.code}` : ""}`
36865
36865
  "node": ">=18.0.0"
36866
36866
  };
36867
36867
  const optionalDependencies = {
36868
- "acp-extension-claude": "0.34.1",
36868
+ "acp-extension-claude": "0.34.3",
36869
36869
  "acp-extension-codex": "0.14.3"
36870
36870
  };
36871
36871
  const devDependencies = {
@@ -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);
@@ -122002,11 +122023,19 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122002
122023
  return {};
122003
122024
  }
122004
122025
  async extMethod(method, params) {
122005
- this.handleExtensionMessage(method, params);
122026
+ try {
122027
+ this.handleExtensionMessage(method, params);
122028
+ } catch (error2) {
122029
+ this.logger.warn(`Error handling extension method ${method}: ${error2}`);
122030
+ }
122006
122031
  return {};
122007
122032
  }
122008
122033
  async extNotification(method, params) {
122009
- this.handleExtensionMessage(method, params);
122034
+ try {
122035
+ this.handleExtensionMessage(method, params);
122036
+ } catch (error2) {
122037
+ this.logger.warn(`Error handling extension notification ${method}: ${error2}`);
122038
+ }
122010
122039
  }
122011
122040
  handleExtensionMessage(method, params) {
122012
122041
  const resolvedMethod = method.startsWith("_") ? method.slice(1) : method;
@@ -122106,7 +122135,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122106
122135
  this.supportsClose = !!closeCapability;
122107
122136
  const hasCloseMethod = typeof connection.closeSession === "function";
122108
122137
  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`);
122138
+ this.logger.debug(`[${this.options.sessionId}] About to establish ACP session`);
122110
122139
  const newSessionStart = performance$1.now();
122111
122140
  this.options.onStartupStage?.({
122112
122141
  type: "new_session_start"
@@ -122133,11 +122162,12 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122133
122162
  const loadStart = performance$1.now();
122134
122163
  try {
122135
122164
  this.logger.debug(`[${this.options.sessionId}] Attempting ACP loadSession (acpSessionId=${resumeSessionId})`);
122136
- const loadResponse = await withAbort(connection.loadSession({
122165
+ const ACP_LOAD_SESSION_TIMEOUT_MS = Math.max(0, timeoutOptions.loadSessionTimeoutMs ?? timeoutOptions.newSessionTimeoutMs ?? 12e4);
122166
+ const loadResponse = await withTimeout$2(withAbort(connection.loadSession({
122137
122167
  sessionId: resumeSessionId,
122138
122168
  cwd: workdir,
122139
122169
  mcpServers
122140
- }), startupAbort);
122170
+ }), startupAbort), this.logger, "connection.loadSession", this.options.sessionId, ACP_LOAD_SESSION_TIMEOUT_MS);
122141
122171
  const loadDurationMs = performance$1.now() - loadStart;
122142
122172
  sessionResponse = {
122143
122173
  sessionId: resumeSessionId,
@@ -122158,11 +122188,12 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122158
122188
  const resumeStart = performance$1.now();
122159
122189
  try {
122160
122190
  this.logger.debug(`[${this.options.sessionId}] Attempting ACP resume (acpSessionId=${resumeSessionId})`);
122161
- const resumeResponse = await withAbort(connection.resumeSession({
122191
+ const ACP_RESUME_SESSION_TIMEOUT_MS = Math.max(0, timeoutOptions.resumeSessionTimeoutMs ?? timeoutOptions.newSessionTimeoutMs ?? 12e4);
122192
+ const resumeResponse = await withTimeout$2(withAbort(connection.resumeSession({
122162
122193
  sessionId: resumeSessionId,
122163
122194
  cwd: workdir,
122164
122195
  mcpServers
122165
- }), startupAbort);
122196
+ }), startupAbort), this.logger, "connection.resumeSession", this.options.sessionId, ACP_RESUME_SESSION_TIMEOUT_MS);
122166
122197
  const resumeDurationMs = performance$1.now() - resumeStart;
122167
122198
  sessionResponse = {
122168
122199
  sessionId: resumeSessionId,
@@ -122218,21 +122249,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122218
122249
  this.logger.error(`[${this.options.sessionId}] connection.prompt returned undefined - connection may be closed`);
122219
122250
  return void 0;
122220
122251
  }
122221
- const PROMPT_WARN_AFTER_MS = 6e5;
122222
- const PROMPT_NO_UPDATES_MS = 12e4;
122223
- const PROMPT_WARN_INTERVAL_MS = 3e5;
122224
- const startMs = Date.now();
122225
- let completed = false;
122226
122252
  let abortListener;
122227
- const warningInterval = setInterval(() => {
122228
- if (completed) return;
122229
- const nowMs = Date.now();
122230
- const elapsedMs = nowMs - startMs;
122231
- if (elapsedMs < PROMPT_WARN_AFTER_MS) return;
122232
- const noUpdatesMs = nowMs - this.lastSessionUpdateAtMs;
122233
- if (noUpdatesMs < PROMPT_NO_UPDATES_MS) return;
122234
- this.logger.debug(`[${this.options.sessionId}] Operation "connection.prompt" is still pending after ${Math.round(elapsedMs / 1e3)}s (no session updates for ${Math.round(noUpdatesMs / 1e3)}s)`);
122235
- }, PROMPT_WARN_INTERVAL_MS);
122236
122253
  try {
122237
122254
  const abortPromise = abortSignal ? new Promise((_2, reject) => {
122238
122255
  abortListener = () => {
@@ -122253,11 +122270,9 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122253
122270
  this.logger.debug(`[${this.options.sessionId}] connection.prompt returned`);
122254
122271
  return result;
122255
122272
  } finally {
122256
- completed = true;
122257
122273
  if (abortSignal && abortListener) {
122258
122274
  abortSignal.removeEventListener("abort", abortListener);
122259
122275
  }
122260
- clearInterval(warningInterval);
122261
122276
  }
122262
122277
  }
122263
122278
  async cancel(sessionId) {
@@ -122428,7 +122443,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122428
122443
  const BuiltinACPSetting = {
122429
122444
  claude: {
122430
122445
  packageName: "acp-extension-claude",
122431
- version: "0.34.1",
122446
+ version: "0.34.3",
122432
122447
  binName: "acp-extension-claude"
122433
122448
  },
122434
122449
  codex: {
@@ -122919,6 +122934,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122919
122934
  args: args2,
122920
122935
  spawnImpl: options.spawnImpl
122921
122936
  });
122937
+ options.logger.debug(`[acp-startup] spawned ACP process (cliType=${options.cliType} agentType=${options.agentType} workdir=${options.workdir})`);
122922
122938
  const stderrStream = agentProcess.stderr;
122923
122939
  let stderrTail = "";
122924
122940
  if (stderrStream) {
@@ -122980,6 +122996,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122980
122996
  const input2 = createStdinWritableStream(agentProcess.stdin);
122981
122997
  const stream2 = ndJsonStream(input2, output);
122982
122998
  try {
122999
+ options.logger.debug("[acp-startup] creating ACP client");
122983
123000
  const started = await createAcpClient({
122984
123001
  stream: stream2,
122985
123002
  workdir: options.workdir,
@@ -122994,6 +123011,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122994
123011
  onRequestPermission: options.onRequestPermission,
122995
123012
  startupAbort: startupMonitor.abortPromise
122996
123013
  });
123014
+ options.logger.debug(`[acp-startup] ACP client ready (acpSessionId=${started.acpSessionId})`);
122997
123015
  return {
122998
123016
  agentProcess,
122999
123017
  client: started.client,
@@ -123137,6 +123155,8 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
123137
123155
  const sleep2 = (ms2) => new Promise((resolve2) => setTimeout(resolve2, ms2));
123138
123156
  const tryGenerateWithArgs = async (extraArgs) => {
123139
123157
  let collectedText = "";
123158
+ const startupStartedAt = Date.now();
123159
+ options.logger.debug(`[title-generator] Starting isolated title ACP agent (cliType=${options.cliType} agentType=${options.agentType})`);
123140
123160
  const { agentProcess, client, acpSessionId } = await startLocalAcpAgent({
123141
123161
  cliType: options.cliType,
123142
123162
  agentType: options.agentType,
@@ -123162,6 +123182,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
123162
123182
  }),
123163
123183
  extraArgs
123164
123184
  });
123185
+ options.logger.debug(`[title-generator] Isolated title ACP agent ready (acpSessionId=${acpSessionId} startupDuration=${Date.now() - startupStartedAt}ms)`);
123165
123186
  try {
123166
123187
  const prompt2 = buildTitlePrompt(options.taskPrompt);
123167
123188
  const configOptionValues = options.titleConfig?.configOptionValues;
@@ -123170,7 +123191,9 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
123170
123191
  await client?.setSessionConfigOption(acpSessionId, key2, value);
123171
123192
  }
123172
123193
  }
123194
+ options.logger.debug(`[title-generator] Sending title prompt (acpSessionId=${acpSessionId})`);
123173
123195
  const response = await client?.prompt(acpSessionId, prompt2);
123196
+ options.logger.debug(`[title-generator] Title prompt returned (acpSessionId=${acpSessionId})`);
123174
123197
  const deadline = Date.now() + 1e4;
123175
123198
  while (Date.now() < deadline && collectedText.trim() === "") {
123176
123199
  await sleep2(100);
@@ -127168,6 +127191,7 @@ $mem | ConvertTo-Json -Compress
127168
127191
  const resumeSource = requestedResumeSessionId ? "request" : storedResumeSessionId ? "meta" : "none";
127169
127192
  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
127193
  yield* self2.tryPromise(() => sessionDoc.setStatus(SessionStatusFactory.initializing("resuming")));
127194
+ self2.deps.logger.debug(`[${sessionId}] Resuming status published; preparing session restore (resumeSource=${resumeSource} resumeSessionId=${resumeSessionId ?? "none"})`);
127171
127195
  const restoreConfig = {
127172
127196
  sessionId,
127173
127197
  workspaceId: message.workspaceId,
@@ -127191,11 +127215,13 @@ $mem | ConvertTo-Json -Compress
127191
127215
  if (resumeSessionId) {
127192
127216
  yield* acpReplaySuppression.acquire;
127193
127217
  }
127218
+ self2.deps.logger.debug(`[${sessionId}] Session restore createSession started (resumeSessionId=${resumeSessionId ?? "none"})`);
127194
127219
  const restoredSession = yield* ctx.trackPendingSession(() => self2.deps.sessionManager.createSession(restoreConfig, {
127195
127220
  resumeSessionId
127196
127221
  }), {
127197
127222
  terminateOnCancel: true
127198
127223
  });
127224
+ self2.deps.logger.debug(`[${sessionId}] Session restore createSession returned (acpSessionId=${restoredSession.acpSessionId ?? "null"})`);
127199
127225
  ctx.bindSession(restoredSession);
127200
127226
  yield* ctx.abortIfCancelled({
127201
127227
  terminateSession: true
@@ -127221,9 +127247,11 @@ $mem | ConvertTo-Json -Compress
127221
127247
  ...restoreConfig
127222
127248
  };
127223
127249
  const fallbackAttempt = gen(function* () {
127250
+ self2.deps.logger.debug(`[${sessionId}] Fallback restore createSession started`);
127224
127251
  const fallbackSession = yield* ctx.trackPendingSession(() => self2.deps.sessionManager.createSession(fallbackConfig), {
127225
127252
  terminateOnCancel: true
127226
127253
  });
127254
+ self2.deps.logger.debug(`[${sessionId}] Fallback restore createSession returned (acpSessionId=${fallbackSession.acpSessionId ?? "null"})`);
127227
127255
  ctx.bindSession(fallbackSession);
127228
127256
  yield* ctx.abortIfCancelled({
127229
127257
  terminateSession: true
@@ -128343,6 +128371,7 @@ $mem | ConvertTo-Json -Compress
128343
128371
  static REALTIME_WAIT_TIMEOUT_MS = 3e4;
128344
128372
  static REMOTE_SYNC_TIMEOUT_MS = 15e3;
128345
128373
  static HISTORY_SYNC_WAIT_TIMEOUT_MS = 5 * 6e4;
128374
+ static HISTORY_SYNC_PROGRESS_LOG_MS = 3e4;
128346
128375
  static HISTORY_RECONNECT_JITTER_MIN_MS = 500;
128347
128376
  static HISTORY_RECONNECT_JITTER_MAX_MS = 1500;
128348
128377
  static setUnrefTimeout(callback, delayMs) {
@@ -128415,12 +128444,17 @@ $mem | ConvertTo-Json -Compress
128415
128444
  let reconnectAttempted = false;
128416
128445
  let unsubscribeMirror;
128417
128446
  let unsubscribeStatus;
128447
+ let progressTimer = null;
128448
+ const waitStartedAt = Date.now();
128418
128449
  const cleanup = () => {
128419
128450
  if (settled) {
128420
128451
  return;
128421
128452
  }
128422
128453
  settled = true;
128423
128454
  clearTimeout(timer2);
128455
+ if (progressTimer) {
128456
+ clearInterval(progressTimer);
128457
+ }
128424
128458
  unsubscribeMirror?.();
128425
128459
  unsubscribeStatus?.();
128426
128460
  };
@@ -128475,6 +128509,13 @@ $mem | ConvertTo-Json -Compress
128475
128509
  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
128510
  resolve2(null);
128477
128511
  }, SessionDispatchWatcher.HISTORY_SYNC_WAIT_TIMEOUT_MS);
128512
+ progressTimer = setInterval(() => {
128513
+ if (settled) {
128514
+ return;
128515
+ }
128516
+ this.deps.logger.warn(`[${sessionId}] Still waiting for pending user turn history sync (elapsed=${Date.now() - waitStartedAt}ms docRoom=${sessionDoc.getDocRoomStatus() ?? "unknown"})`);
128517
+ }, SessionDispatchWatcher.HISTORY_SYNC_PROGRESS_LOG_MS);
128518
+ progressTimer.unref?.();
128478
128519
  unsubscribeMirror = sessionDoc.mirror?.subscribe(checkForTurn);
128479
128520
  if (!unsubscribeMirror) {
128480
128521
  this.deps.logger.debug(`[${sessionId}] Session mirror is unavailable during history sync wait`);
@@ -132059,6 +132100,8 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
132059
132100
  static CONTEXT_WINDOW_USAGE_THROTTLE_MS = 400;
132060
132101
  permissionRequestStartTimes = /* @__PURE__ */ new Map();
132061
132102
  machineHeartbeatTimer = null;
132103
+ machineHeartbeatInFlightStartedAt = null;
132104
+ machineHeartbeatSeq = 0;
132062
132105
  static MACHINE_HEARTBEAT_INTERVAL_MS = 2e4;
132063
132106
  evictForMemoryPressureFn = async () => ({
132064
132107
  availableMemoryBytes: 0,
@@ -132089,24 +132132,35 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
132089
132132
  startMachineHeartbeat() {
132090
132133
  this.stopMachineHeartbeat();
132091
132134
  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
- });
132135
+ this.sendMachineHeartbeatWithDiagnostics("initial");
132097
132136
  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
- });
132137
+ this.sendMachineHeartbeatWithDiagnostics("periodic");
132103
132138
  }, MessageHandler.MACHINE_HEARTBEAT_INTERVAL_MS);
132139
+ this.machineHeartbeatTimer.unref?.();
132140
+ }
132141
+ sendMachineHeartbeatWithDiagnostics(reason) {
132142
+ const now2 = Date.now();
132143
+ if (this.machineHeartbeatInFlightStartedAt !== null) {
132144
+ this.logger.warn(`Machine heartbeat skipped (reason=${reason}); previous heartbeat still pending for ${now2 - this.machineHeartbeatInFlightStartedAt}ms`);
132145
+ return;
132146
+ }
132147
+ const seq2 = ++this.machineHeartbeatSeq;
132148
+ this.machineHeartbeatInFlightStartedAt = now2;
132149
+ this.logger.debug(`Machine heartbeat tick started (reason=${reason} seq=${seq2})`);
132150
+ void withSlowOperationWarning(this.workspaceDocument.sendMachineHeartbeat(), this.logger, `workspaceDocument.sendMachineHeartbeat(reason=${reason}, seq=${seq2})`, this.machineId).then(() => {
132151
+ this.logger.debug(`Machine heartbeat sent (reason=${reason} seq=${seq2} duration=${Date.now() - now2}ms)`);
132152
+ }).catch((error2) => {
132153
+ this.logger.debug(`Machine heartbeat failed (reason=${reason} seq=${seq2} duration=${Date.now() - now2}ms): ${formatErrorMessage(error2)}`);
132154
+ }).finally(() => {
132155
+ this.machineHeartbeatInFlightStartedAt = null;
132156
+ });
132104
132157
  }
132105
132158
  stopMachineHeartbeat() {
132106
132159
  if (this.machineHeartbeatTimer) {
132107
132160
  clearInterval(this.machineHeartbeatTimer);
132108
132161
  this.machineHeartbeatTimer = null;
132109
132162
  }
132163
+ this.machineHeartbeatInFlightStartedAt = null;
132110
132164
  }
132111
132165
  preferredBaseBranch = (process.env.LODY_BASE_BRANCH || "main").trim() || "main";
132112
132166
  resolveGitHubProjectBranch(meta, preferredBranch) {
@@ -134850,6 +134904,8 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
134850
134904
  }
134851
134905
  queue;
134852
134906
  isStopped = false;
134907
+ static QUEUE_WAIT_WARNING_MS = 1e4;
134908
+ static PROCESSING_WARNING_MS = 3e4;
134853
134909
  enqueue(message, handler) {
134854
134910
  if (this.isStopped) {
134855
134911
  this.logger.debug("MessageProcessor is stopped, ignoring new message");
@@ -134857,11 +134913,30 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
134857
134913
  }
134858
134914
  const sessionId = this.extractSessionId(message);
134859
134915
  const queueKey = this.extractQueueKey(message);
134860
- this.logger.debug(`Enqueued message type=${message.type} sessionId=${sessionId || "N/A"}`);
134916
+ const queuedAt = Date.now();
134917
+ let started = false;
134918
+ const waitWarning = setInterval(() => {
134919
+ if (started) {
134920
+ return;
134921
+ }
134922
+ 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}`);
134923
+ }, MessageProcessor.QUEUE_WAIT_WARNING_MS);
134924
+ waitWarning.unref?.();
134925
+ this.logger.debug(`Enqueued message type=${message.type} sessionId=${sessionId || "N/A"} active=${this.queue.active} waiting=${this.queue.waiting}`);
134861
134926
  void this.queue.enqueue(queueKey, async () => {
134862
134927
  const startTime = Date.now();
134928
+ started = true;
134929
+ clearInterval(waitWarning);
134930
+ let processingCompleted = false;
134931
+ const processingWarning = setInterval(() => {
134932
+ if (processingCompleted) {
134933
+ return;
134934
+ }
134935
+ 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}`);
134936
+ }, MessageProcessor.PROCESSING_WARNING_MS);
134937
+ processingWarning.unref?.();
134863
134938
  try {
134864
- this.logger.debug(`Processing message type=${message.type} sessionId=${sessionId || "N/A"}`);
134939
+ 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
134940
  await handler(message);
134866
134941
  const duration2 = Date.now() - startTime;
134867
134942
  this.logger.debug(`Processed message type=${message.type} sessionId=${sessionId || "N/A"} duration=${duration2}ms`);
@@ -134871,6 +134946,9 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
134871
134946
  const err2 = error2 instanceof Error ? error2 : new Error(String(error2));
134872
134947
  this.logger.error(`Failed to process message type=${message.type} sessionId=${sessionId || "N/A"} duration=${duration2}ms: ${err2.message}`);
134873
134948
  this.emit("message:error", err2, message);
134949
+ } finally {
134950
+ processingCompleted = true;
134951
+ clearInterval(processingWarning);
134874
134952
  }
134875
134953
  });
134876
134954
  }
@@ -135235,6 +135313,8 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
135235
135313
  }
135236
135314
  async dispatchLocalMessageForResponse(message) {
135237
135315
  const handler = this.requireHandler();
135316
+ const dispatchStartedAt = Date.now();
135317
+ 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
135318
  return await new Promise((resolve2, reject) => {
135239
135319
  this.messageProcessor.enqueue(message, async (nextMessage) => {
135240
135320
  const responses = [];
@@ -135244,6 +135324,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
135244
135324
  return;
135245
135325
  }
135246
135326
  settled = true;
135327
+ 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
135328
  resolve2([
135248
135329
  ...responses
135249
135330
  ]);
@@ -135253,6 +135334,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
135253
135334
  return;
135254
135335
  }
135255
135336
  settled = true;
135337
+ 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
135338
  reject(error2);
135257
135339
  };
135258
135340
  const context2 = {
@@ -142420,7 +142502,7 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
142420
142502
  await this.workspaceDocument.getOrCreateSessionDoc(config2.sessionId);
142421
142503
  this.logger.debug(`[${config2.sessionId}] Session will enter create inner`);
142422
142504
  const createInnerStart = performance$1.now();
142423
- const session = await this.createSessionInner(config2);
142505
+ const session = await withSlowOperationWarning(this.createSessionInner(config2), this.logger, "session.createSessionInner", config2.sessionId);
142424
142506
  session.ghTokenInjected = ghTokenInjected;
142425
142507
  const createInnerDurationMs = performance$1.now() - createInnerStart;
142426
142508
  this.logger.debug(`[${config2.sessionId}] Session create inner finished in ${Math.round(createInnerDurationMs)}ms`);
@@ -142441,7 +142523,7 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
142441
142523
  this.logger.debug(`[${sessionId}] About to call session.createAgent`);
142442
142524
  try {
142443
142525
  const createAgentStart = performance$1.now();
142444
- acpSessionId = await session.createAgent({
142526
+ acpSessionId = await withSlowOperationWarning(session.createAgent({
142445
142527
  cliType: config2.agentCliType,
142446
142528
  agentType: config2.agentType,
142447
142529
  command: setting.exec.command,
@@ -142501,7 +142583,7 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
142501
142583
  onCodexImageGenerationEnd: (event) => {
142502
142584
  this.emit("onCodexImageGenerationEnd", sessionId, event);
142503
142585
  }
142504
- });
142586
+ }), this.logger, `session.createAgent(resume=${requestedResumeSessionId ? "yes" : "no"})`, sessionId);
142505
142587
  const createAgentDurationMs = performance$1.now() - createAgentStart;
142506
142588
  this.logger.debug(`[${sessionId}] Session createAgent finished in ${Math.round(createAgentDurationMs)}ms (acpSessionId=${acpSessionId})`);
142507
142589
  this.logger.debug(`[${sessionId}] createAgent returned successfully`);
@@ -145144,6 +145226,23 @@ Received ${signal}, shutting down gracefully...` : "\nShutting down gracefully..
145144
145226
  shutdown: shutdown2
145145
145227
  };
145146
145228
  }
145229
+ function startEventLoopLagMonitor(logger2, options) {
145230
+ const intervalMs = Math.max(100, options.intervalMs ?? 1e3);
145231
+ const warnThresholdMs = Math.max(intervalMs, options.warnThresholdMs ?? 5e3);
145232
+ let expectedAt = Date.now() + intervalMs;
145233
+ const timer2 = setInterval(() => {
145234
+ const now2 = Date.now();
145235
+ const lagMs = now2 - expectedAt;
145236
+ expectedAt = now2 + intervalMs;
145237
+ if (lagMs >= warnThresholdMs) {
145238
+ logger2.warn(`[event-loop] ${options.label} timer lag detected: fired ${Math.round(lagMs)}ms late (threshold=${warnThresholdMs}ms interval=${intervalMs}ms)`);
145239
+ }
145240
+ }, intervalMs);
145241
+ timer2.unref?.();
145242
+ return {
145243
+ stop: () => clearInterval(timer2)
145244
+ };
145245
+ }
145147
145246
  const ELECTRON_BOOTSTRAP_ENV = "LODY_ELECTRON_BOOTSTRAP";
145148
145247
  const ELECTRON_SESSION_TOKEN_ENV = "LODY_ELECTRON_SESSION_TOKEN";
145149
145248
  const ELECTRON_SESSION_USER_ID_ENV = "LODY_ELECTRON_SESSION_USER_ID";
@@ -145371,7 +145470,13 @@ Received ${signal}, shutting down gracefully...` : "\nShutting down gracefully..
145371
145470
  runtimeStateReporter,
145372
145471
  onFatalAuthFailure: (error2) => triggerFatalAuthShutdown?.(error2)
145373
145472
  });
145374
- registerProcessCleanup(() => fleet.shutdown());
145473
+ const eventLoopLagMonitor = startEventLoopLagMonitor(logger2, {
145474
+ label: "lody start"
145475
+ });
145476
+ registerProcessCleanup(async () => {
145477
+ eventLoopLagMonitor.stop();
145478
+ await fleet.shutdown();
145479
+ });
145375
145480
  const shutdownSignals = process.platform === "win32" ? [
145376
145481
  "SIGINT",
145377
145482
  "SIGTERM",
@@ -145387,6 +145492,7 @@ Received ${signal}, shutting down gracefully...` : "\nShutting down gracefully..
145387
145492
  logger: logger2,
145388
145493
  shutdown: async () => {
145389
145494
  unregisterProcessCleanup();
145495
+ eventLoopLagMonitor.stop();
145390
145496
  await fleet.shutdown();
145391
145497
  },
145392
145498
  flushTelemetry: () => shutdownPostHog(),
@@ -145425,6 +145531,7 @@ Received ${signal}, shutting down gracefully...` : "\nShutting down gracefully..
145425
145531
  });
145426
145532
  shutdownController.unregister();
145427
145533
  unregisterProcessCleanup();
145534
+ eventLoopLagMonitor.stop();
145428
145535
  await fleet.shutdown().catch((err2) => {
145429
145536
  logger2.error(`Cleanup failed: ${err2 instanceof Error ? err2.message : "Unknown error"}`);
145430
145537
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lody",
3
- "version": "0.52.0",
3
+ "version": "0.52.2",
4
4
  "description": "Lody Agent CLI tool for managing remote command execution",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -20,7 +20,7 @@
20
20
  "node": ">=18.0.0"
21
21
  },
22
22
  "optionalDependencies": {
23
- "acp-extension-claude": "0.34.1",
23
+ "acp-extension-claude": "0.34.3",
24
24
  "acp-extension-codex": "0.14.3"
25
25
  },
26
26
  "devDependencies": {
@@ -74,10 +74,10 @@
74
74
  "ws": "^8.18.3",
75
75
  "zod": "^4.1.5",
76
76
  "@lody/cli-supervisor": "0.0.1",
77
- "@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/convex": "0.0.1"
81
81
  },
82
82
  "files": [
83
83
  "dist",