lody 0.51.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 +219 -78
  2. package/package.json +5 -5
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.51.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";
@@ -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.31.1",
36868
+ "acp-extension-claude": "0.34.1",
36869
36869
  "acp-extension-codex": "0.14.3"
36870
36870
  };
36871
36871
  const devDependencies = {
@@ -37076,7 +37076,7 @@ Mongoose Error Code: ${error2.code}` : ""}`
37076
37076
  const runtimeEnv = getRuntimeEnv();
37077
37077
  const environment$1 = "production";
37078
37078
  const dsn = "https://080f9de535ff335a1a0440d0e385f796@o4510491299086336.ingest.us.sentry.io/4510559045681152";
37079
- const postHogHost = process.env.LODY_POSTHOG_HOST ?? "https://us.i.posthog.com";
37079
+ const postHogHost = process.env.LODY_POSTHOG_HOST ?? "https://m.lody.ai";
37080
37080
  const postHogKey = process.env.LODY_POSTHOG_KEY ?? "phc_LFS5i5WIwg4irAhrG5oJR04iYPhReVZ3DdFZOKqCkjG";
37081
37081
  const tracesSampleRate = Number(process.env.SENTRY_TRACES_SAMPLE_RATE) || 0.2;
37082
37082
  const profilesSampleRate = Number(process.env.SENTRY_PROFILES_SAMPLE_RATE) || 0.1;
@@ -77781,6 +77781,9 @@ Task description:
77781
77781
  }
77782
77782
  return void 0;
77783
77783
  };
77784
+ function getLocalProjectHistoryProviderKey(provider2) {
77785
+ return `${provider2.cliType}:${provider2.agentType}`;
77786
+ }
77784
77787
  const DEFAULT_BASE_BRANCH = "main";
77785
77788
  function getTrimmedBranch(value) {
77786
77789
  if (typeof value !== "string") {
@@ -78662,10 +78665,10 @@ Task description:
78662
78665
  localProjectId: LocalProjectIdSchema,
78663
78666
  branchName: string$2()
78664
78667
  }).strict();
78665
- const LocalProjectHistoryProviderSchema = _enum$1([
78666
- "codex",
78667
- "claude"
78668
- ]);
78668
+ const LocalProjectHistoryProviderSchema = object$1({
78669
+ cliType: AgentConfigCliTypeSchema,
78670
+ agentType: string$2().trim().min(1)
78671
+ }).strict();
78669
78672
  const LocalProjectSyncHistoryRequestSchema = object$1({
78670
78673
  type: literal("local-project/sync-history"),
78671
78674
  machineId: MachineIdSchema,
@@ -82556,8 +82559,8 @@ Task description:
82556
82559
  function createUserEntry(args2) {
82557
82560
  const inputConfig = {
82558
82561
  prompt: args2.text,
82559
- cliType: "builtin",
82560
- agentType: args2.agentType,
82562
+ cliType: args2.provider.cliType,
82563
+ agentType: args2.provider.agentType,
82561
82564
  ...{}
82562
82565
  };
82563
82566
  return {
@@ -82582,7 +82585,7 @@ Task description:
82582
82585
  const now2 = options.now ?? defaultNow;
82583
82586
  const createId = options.createId ?? defaultCreateId;
82584
82587
  const mode2 = options.mode;
82585
- const agentType = options.agentType ?? "codex";
82588
+ const provider2 = options.provider;
82586
82589
  let history = [];
82587
82590
  let lastWasUserChunk = false;
82588
82591
  let droppedNotifications = 0;
@@ -82604,7 +82607,7 @@ Task description:
82604
82607
  text,
82605
82608
  timestamp: now2(),
82606
82609
  userId: options.userId,
82607
- agentType,
82610
+ provider: provider2,
82608
82611
  mode: mode2
82609
82612
  }));
82610
82613
  }
@@ -85473,13 +85476,14 @@ ${tailedOutput}` : null;
85473
85476
  };
85474
85477
  function withSlowOperationWarning(promise, logger2, operationName, sessionId, intervalMs = 1e4) {
85475
85478
  let completed = false;
85476
- let elapsedMs = 0;
85479
+ const startMs = Date.now();
85477
85480
  const interval2 = setInterval(() => {
85478
- elapsedMs += intervalMs;
85479
85481
  if (!completed) {
85482
+ const elapsedMs = Date.now() - startMs;
85480
85483
  logger2.debug(`[${sessionId}] Operation "${operationName}" is still pending after ${Math.round(elapsedMs / 1e3)}s - possible hang`);
85481
85484
  }
85482
85485
  }, intervalMs);
85486
+ interval2.unref?.();
85483
85487
  return promise.finally(() => {
85484
85488
  completed = true;
85485
85489
  clearInterval(interval2);
@@ -98394,11 +98398,13 @@ stream:${scope2.streamId}`;
98394
98398
  machineId = null;
98395
98399
  machineKey = null;
98396
98400
  sessionKeys = /* @__PURE__ */ new Map();
98401
+ machineHeartbeatSeq = 0;
98397
98402
  started = false;
98398
98403
  stopped = false;
98399
98404
  start() {
98400
98405
  if (this.started || this.stopped) return;
98401
98406
  this.started = true;
98407
+ this.options.logger.debug(`[${this.options.workspaceId}] Joining Loro presence room`);
98402
98408
  void this.transport.join({
98403
98409
  onStatusChange: (status) => {
98404
98410
  this.options.logger.debug(`[${this.options.workspaceId}] Loro presence room status: ${status}`);
@@ -98410,6 +98416,7 @@ stream:${scope2.streamId}`;
98410
98416
  }
98411
98417
  if (result.ok) {
98412
98418
  this.subscription = result.value;
98419
+ this.options.logger.debug(`[${this.options.workspaceId}] Loro presence subscription established`);
98413
98420
  return;
98414
98421
  }
98415
98422
  this.options.logger.debug(`[${this.options.workspaceId}] Failed to join Loro presence room: ${formatErrorMessage(result.error)}`);
@@ -98439,6 +98446,7 @@ stream:${scope2.streamId}`;
98439
98446
  }
98440
98447
  writeMachineHeartbeat() {
98441
98448
  if (this.stopped || !this.machineId || !this.machineKey) return;
98449
+ const seq2 = ++this.machineHeartbeatSeq;
98442
98450
  const state2 = {
98443
98451
  kind: "machine",
98444
98452
  machineId: this.machineId,
@@ -98446,6 +98454,7 @@ stream:${scope2.streamId}`;
98446
98454
  updatedAt: getServerNow()
98447
98455
  };
98448
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})`);
98449
98458
  }
98450
98459
  setSessionPresence(args2) {
98451
98460
  if (this.stopped) return;
@@ -116853,6 +116862,18 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
116853
116862
  let metaSub = null;
116854
116863
  const initialMetaSync = createDeferred();
116855
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
+ };
116856
116877
  const transportAdapter = new StreamsTransportAdapter({
116857
116878
  bucketId: LORO_STREAMS_BUCKET_ID,
116858
116879
  metaStreamId: getLoroMetaStreamId(workspaceId),
@@ -116866,10 +116887,10 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
116866
116887
  debounceMs: 5e3
116867
116888
  },
116868
116889
  onPersistDoc: async () => {
116869
- await repo?.flush();
116890
+ await flushRepoForPersist("doc");
116870
116891
  },
116871
116892
  onPersistMeta: async () => {
116872
- await repo?.flush();
116893
+ await flushRepoForPersist("meta");
116873
116894
  }
116874
116895
  });
116875
116896
  let manager = null;
@@ -117163,8 +117184,11 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
117163
117184
  if (!this.machine) {
117164
117185
  return;
117165
117186
  }
117166
- await this.machine.sendHeartbeat();
117187
+ const startedAt = Date.now();
117188
+ this.logger.debug(`[${this.workspaceId}] Machine heartbeat writing presence and meta`);
117167
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)`);
117168
117192
  }
117169
117193
  async restoreMachineDocument(machineId) {
117170
117194
  const machineRoomId = getMachineRoomId(machineId);
@@ -121114,6 +121138,9 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
121114
121138
  nes_reject: "nes/reject",
121115
121139
  nes_start: "nes/start",
121116
121140
  nes_suggest: "nes/suggest",
121141
+ providers_disable: "providers/disable",
121142
+ providers_list: "providers/list",
121143
+ providers_set: "providers/set",
121117
121144
  session_cancel: "session/cancel",
121118
121145
  session_close: "session/close",
121119
121146
  session_fork: "session/fork",
@@ -121315,6 +121342,15 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
121315
121342
  async authenticate(params) {
121316
121343
  return await this.connection.sendRequest(AGENT_METHODS.authenticate, params) ?? {};
121317
121344
  }
121345
+ async unstable_listProviders(params) {
121346
+ return await this.connection.sendRequest(AGENT_METHODS.providers_list, params);
121347
+ }
121348
+ async unstable_setProvider(params) {
121349
+ return await this.connection.sendRequest(AGENT_METHODS.providers_set, params) ?? {};
121350
+ }
121351
+ async unstable_disableProvider(params) {
121352
+ return await this.connection.sendRequest(AGENT_METHODS.providers_disable, params) ?? {};
121353
+ }
121318
121354
  async unstable_logout(params) {
121319
121355
  return await this.connection.sendRequest(AGENT_METHODS.logout, params) ?? {};
121320
121356
  }
@@ -122091,7 +122127,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122091
122127
  this.supportsClose = !!closeCapability;
122092
122128
  const hasCloseMethod = typeof connection.closeSession === "function";
122093
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"})`);
122094
- this.logger.debug(`[${this.options.sessionId}] About to start new ACP session`);
122130
+ this.logger.debug(`[${this.options.sessionId}] About to establish ACP session`);
122095
122131
  const newSessionStart = performance$1.now();
122096
122132
  this.options.onStartupStage?.({
122097
122133
  type: "new_session_start"
@@ -122118,11 +122154,12 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122118
122154
  const loadStart = performance$1.now();
122119
122155
  try {
122120
122156
  this.logger.debug(`[${this.options.sessionId}] Attempting ACP loadSession (acpSessionId=${resumeSessionId})`);
122121
- 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({
122122
122159
  sessionId: resumeSessionId,
122123
122160
  cwd: workdir,
122124
122161
  mcpServers
122125
- }), startupAbort);
122162
+ }), startupAbort), this.logger, "connection.loadSession", this.options.sessionId, ACP_LOAD_SESSION_TIMEOUT_MS);
122126
122163
  const loadDurationMs = performance$1.now() - loadStart;
122127
122164
  sessionResponse = {
122128
122165
  sessionId: resumeSessionId,
@@ -122143,11 +122180,12 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122143
122180
  const resumeStart = performance$1.now();
122144
122181
  try {
122145
122182
  this.logger.debug(`[${this.options.sessionId}] Attempting ACP resume (acpSessionId=${resumeSessionId})`);
122146
- 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({
122147
122185
  sessionId: resumeSessionId,
122148
122186
  cwd: workdir,
122149
122187
  mcpServers
122150
- }), startupAbort);
122188
+ }), startupAbort), this.logger, "connection.resumeSession", this.options.sessionId, ACP_RESUME_SESSION_TIMEOUT_MS);
122151
122189
  const resumeDurationMs = performance$1.now() - resumeStart;
122152
122190
  sessionResponse = {
122153
122191
  sessionId: resumeSessionId,
@@ -122413,7 +122451,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122413
122451
  const BuiltinACPSetting = {
122414
122452
  claude: {
122415
122453
  packageName: "acp-extension-claude",
122416
- version: "0.31.1",
122454
+ version: "0.34.1",
122417
122455
  binName: "acp-extension-claude"
122418
122456
  },
122419
122457
  codex: {
@@ -122904,6 +122942,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122904
122942
  args: args2,
122905
122943
  spawnImpl: options.spawnImpl
122906
122944
  });
122945
+ options.logger.debug(`[acp-startup] spawned ACP process (cliType=${options.cliType} agentType=${options.agentType} workdir=${options.workdir})`);
122907
122946
  const stderrStream = agentProcess.stderr;
122908
122947
  let stderrTail = "";
122909
122948
  if (stderrStream) {
@@ -122965,6 +123004,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122965
123004
  const input2 = createStdinWritableStream(agentProcess.stdin);
122966
123005
  const stream2 = ndJsonStream(input2, output);
122967
123006
  try {
123007
+ options.logger.debug("[acp-startup] creating ACP client");
122968
123008
  const started = await createAcpClient({
122969
123009
  stream: stream2,
122970
123010
  workdir: options.workdir,
@@ -122979,6 +123019,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122979
123019
  onRequestPermission: options.onRequestPermission,
122980
123020
  startupAbort: startupMonitor.abortPromise
122981
123021
  });
123022
+ options.logger.debug(`[acp-startup] ACP client ready (acpSessionId=${started.acpSessionId})`);
122982
123023
  return {
122983
123024
  agentProcess,
122984
123025
  client: started.client,
@@ -123122,6 +123163,8 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
123122
123163
  const sleep2 = (ms2) => new Promise((resolve2) => setTimeout(resolve2, ms2));
123123
123164
  const tryGenerateWithArgs = async (extraArgs) => {
123124
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})`);
123125
123168
  const { agentProcess, client, acpSessionId } = await startLocalAcpAgent({
123126
123169
  cliType: options.cliType,
123127
123170
  agentType: options.agentType,
@@ -123147,6 +123190,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
123147
123190
  }),
123148
123191
  extraArgs
123149
123192
  });
123193
+ options.logger.debug(`[title-generator] Isolated title ACP agent ready (acpSessionId=${acpSessionId} startupDuration=${Date.now() - startupStartedAt}ms)`);
123150
123194
  try {
123151
123195
  const prompt2 = buildTitlePrompt(options.taskPrompt);
123152
123196
  const configOptionValues = options.titleConfig?.configOptionValues;
@@ -123155,7 +123199,9 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
123155
123199
  await client?.setSessionConfigOption(acpSessionId, key2, value);
123156
123200
  }
123157
123201
  }
123202
+ options.logger.debug(`[title-generator] Sending title prompt (acpSessionId=${acpSessionId})`);
123158
123203
  const response = await client?.prompt(acpSessionId, prompt2);
123204
+ options.logger.debug(`[title-generator] Title prompt returned (acpSessionId=${acpSessionId})`);
123159
123205
  const deadline = Date.now() + 1e4;
123160
123206
  while (Date.now() < deadline && collectedText.trim() === "") {
123161
123207
  await sleep2(100);
@@ -126014,14 +126060,10 @@ $mem | ConvertTo-Json -Compress
126014
126060
  }
126015
126061
  function getImportedAcpSourceAcpSessionId(meta) {
126016
126062
  const externalHistory = meta.externalHistory;
126017
- const provider2 = externalHistory?.provider;
126018
- if (!externalHistory || provider2 !== "codex" && provider2 !== "claude") {
126019
- return void 0;
126020
- }
126021
- return externalHistory.sourceAcpSessionId;
126063
+ return externalHistory?.sourceAcpSessionId;
126022
126064
  }
126023
126065
  function isImportedAcpReplayUserTurn(entry2, meta) {
126024
- const provider2 = meta.externalHistory?.provider;
126066
+ const provider2 = meta.externalHistory ? getLocalProjectHistoryProviderKey(meta.externalHistory.provider) : null;
126025
126067
  const sourceAcpSessionId = getImportedAcpSourceAcpSessionId(meta);
126026
126068
  return !!provider2 && !!sourceAcpSessionId && entry2.id.startsWith(`${provider2}:${sourceAcpSessionId}:turn:`);
126027
126069
  }
@@ -127157,6 +127199,7 @@ $mem | ConvertTo-Json -Compress
127157
127199
  const resumeSource = requestedResumeSessionId ? "request" : storedResumeSessionId ? "meta" : "none";
127158
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"})`);
127159
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"})`);
127160
127203
  const restoreConfig = {
127161
127204
  sessionId,
127162
127205
  workspaceId: message.workspaceId,
@@ -127180,11 +127223,13 @@ $mem | ConvertTo-Json -Compress
127180
127223
  if (resumeSessionId) {
127181
127224
  yield* acpReplaySuppression.acquire;
127182
127225
  }
127226
+ self2.deps.logger.debug(`[${sessionId}] Session restore createSession started (resumeSessionId=${resumeSessionId ?? "none"})`);
127183
127227
  const restoredSession = yield* ctx.trackPendingSession(() => self2.deps.sessionManager.createSession(restoreConfig, {
127184
127228
  resumeSessionId
127185
127229
  }), {
127186
127230
  terminateOnCancel: true
127187
127231
  });
127232
+ self2.deps.logger.debug(`[${sessionId}] Session restore createSession returned (acpSessionId=${restoredSession.acpSessionId ?? "null"})`);
127188
127233
  ctx.bindSession(restoredSession);
127189
127234
  yield* ctx.abortIfCancelled({
127190
127235
  terminateSession: true
@@ -127210,9 +127255,11 @@ $mem | ConvertTo-Json -Compress
127210
127255
  ...restoreConfig
127211
127256
  };
127212
127257
  const fallbackAttempt = gen(function* () {
127258
+ self2.deps.logger.debug(`[${sessionId}] Fallback restore createSession started`);
127213
127259
  const fallbackSession = yield* ctx.trackPendingSession(() => self2.deps.sessionManager.createSession(fallbackConfig), {
127214
127260
  terminateOnCancel: true
127215
127261
  });
127262
+ self2.deps.logger.debug(`[${sessionId}] Fallback restore createSession returned (acpSessionId=${fallbackSession.acpSessionId ?? "null"})`);
127216
127263
  ctx.bindSession(fallbackSession);
127217
127264
  yield* ctx.abortIfCancelled({
127218
127265
  terminateSession: true
@@ -128332,6 +128379,7 @@ $mem | ConvertTo-Json -Compress
128332
128379
  static REALTIME_WAIT_TIMEOUT_MS = 3e4;
128333
128380
  static REMOTE_SYNC_TIMEOUT_MS = 15e3;
128334
128381
  static HISTORY_SYNC_WAIT_TIMEOUT_MS = 5 * 6e4;
128382
+ static HISTORY_SYNC_PROGRESS_LOG_MS = 3e4;
128335
128383
  static HISTORY_RECONNECT_JITTER_MIN_MS = 500;
128336
128384
  static HISTORY_RECONNECT_JITTER_MAX_MS = 1500;
128337
128385
  static setUnrefTimeout(callback, delayMs) {
@@ -128404,12 +128452,17 @@ $mem | ConvertTo-Json -Compress
128404
128452
  let reconnectAttempted = false;
128405
128453
  let unsubscribeMirror;
128406
128454
  let unsubscribeStatus;
128455
+ let progressTimer = null;
128456
+ const waitStartedAt = Date.now();
128407
128457
  const cleanup = () => {
128408
128458
  if (settled) {
128409
128459
  return;
128410
128460
  }
128411
128461
  settled = true;
128412
128462
  clearTimeout(timer2);
128463
+ if (progressTimer) {
128464
+ clearInterval(progressTimer);
128465
+ }
128413
128466
  unsubscribeMirror?.();
128414
128467
  unsubscribeStatus?.();
128415
128468
  };
@@ -128464,6 +128517,13 @@ $mem | ConvertTo-Json -Compress
128464
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`);
128465
128518
  resolve2(null);
128466
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?.();
128467
128527
  unsubscribeMirror = sessionDoc.mirror?.subscribe(checkForTurn);
128468
128528
  if (!unsubscribeMirror) {
128469
128529
  this.deps.logger.debug(`[${sessionId}] Session mirror is unavailable during history sync wait`);
@@ -131099,27 +131159,24 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
131099
131159
  }
131100
131160
  }
131101
131161
  function getProviderLabel$1(provider2) {
131102
- return provider2 === "claude" ? "Claude" : "Codex";
131162
+ return getLocalProjectHistoryProviderKey(provider2);
131103
131163
  }
131104
131164
  async function createHistoryAcpConnection(args2) {
131105
- const setting = resolveACPSetting({
131106
- cliType: "builtin",
131107
- agentType: args2.provider
131108
- });
131165
+ const setting = resolveACPSetting(args2.provider);
131109
131166
  const env2 = setting.exec.env ? {
131110
131167
  ...process.env,
131111
131168
  ...setting.exec.env
131112
131169
  } : process.env;
131113
131170
  const agentProcess = spawnAcpProcess({
131114
- cliType: "builtin",
131115
- agentType: args2.provider,
131171
+ cliType: args2.provider.cliType,
131172
+ agentType: args2.provider.agentType,
131116
131173
  workdir: args2.workdir,
131117
131174
  env: env2
131118
131175
  });
131119
131176
  agentProcess.stderr?.setEncoding("utf8");
131120
131177
  agentProcess.stderr?.on("data", (chunk) => {
131121
131178
  if (!chunk) return;
131122
- args2.logger.debug(`[${args2.provider}-history-sync] ACP stderr: ${chunk.slice(0, 1200)}`);
131179
+ args2.logger.debug(`[${getProviderLabel$1(args2.provider)}-history-sync] ACP stderr: ${chunk.slice(0, 1200)}`);
131123
131180
  });
131124
131181
  if (!agentProcess.stdout || !agentProcess.stdin) {
131125
131182
  await terminateChildProcess(agentProcess);
@@ -131230,7 +131287,8 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
131230
131287
  }), `${getProviderLabel$1(args2.provider)} ACP loadSession (${args2.acpSessionId})`);
131231
131288
  return collector.notifications;
131232
131289
  } catch (error2) {
131233
- throw new Error(`Failed to load ${getProviderLabel$1(args2.provider)} session ${args2.acpSessionId}: ${formatErrorMessage(error2)}`, {
131290
+ const message = `Failed to load ${getProviderLabel$1(args2.provider)} session ${args2.acpSessionId}: ${formatErrorMessage(error2)}`;
131291
+ throw new Error(message, {
131234
131292
  cause: error2
131235
131293
  });
131236
131294
  } finally {
@@ -131295,17 +131353,18 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
131295
131353
  function materializeReplay(args2) {
131296
131354
  let tempId = 0;
131297
131355
  const nowIso = new Date(getServerNow()).toISOString();
131356
+ const providerKey = getLocalProjectHistoryProviderKey(args2.provider);
131298
131357
  const replay = buildHistoryReplayImport(args2.replayNotifications, {
131299
- agentType: args2.provider,
131358
+ provider: args2.provider,
131300
131359
  userId: args2.userId,
131301
131360
  now: () => nowIso,
131302
- createId: () => `${args2.provider}:${args2.acpSessionId}:tmp:${tempId++}`,
131361
+ createId: () => `${providerKey}:${args2.acpSessionId}:tmp:${tempId++}`,
131303
131362
  mode: "imported_snapshot"
131304
131363
  });
131305
131364
  const turnHashes = replay.history.map(hashHistoryEntry);
131306
131365
  const history = replay.history.map((entry2, index2) => ({
131307
131366
  ...entry2,
131308
- id: `${args2.provider}:${args2.acpSessionId}:turn:${index2}:${turnHashes[index2].slice(0, 16)}`
131367
+ id: `${providerKey}:${args2.acpSessionId}:turn:${index2}:${turnHashes[index2].slice(0, 16)}`
131309
131368
  }));
131310
131369
  return {
131311
131370
  history,
@@ -131391,12 +131450,13 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
131391
131450
  }
131392
131451
  function buildExistingHistorySessionIndex(metas, machineId, provider2) {
131393
131452
  const index2 = /* @__PURE__ */ new Map();
131453
+ const providerKey = getLocalProjectHistoryProviderKey(provider2);
131394
131454
  for (const entry2 of metas) {
131395
131455
  if (entry2.meta.machineId !== machineId) continue;
131396
- if (entry2.meta.cliType !== "builtin") continue;
131397
- if (entry2.meta.agentType !== provider2) continue;
131456
+ if (entry2.meta.cliType !== provider2.cliType) continue;
131457
+ if (entry2.meta.agentType !== provider2.agentType) continue;
131398
131458
  const acpSessionIds = /* @__PURE__ */ new Set();
131399
- if (entry2.meta.externalHistory?.provider === provider2) {
131459
+ if (entry2.meta.externalHistory && getLocalProjectHistoryProviderKey(entry2.meta.externalHistory.provider) === providerKey) {
131400
131460
  const sourceAcpSessionId = entry2.meta.externalHistory.sourceAcpSessionId;
131401
131461
  if (sourceAcpSessionId) {
131402
131462
  acpSessionIds.add(sourceAcpSessionId);
@@ -131414,7 +131474,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
131414
131474
  return index2;
131415
131475
  }
131416
131476
  function getProviderLabel(provider2) {
131417
- return provider2 === "claude" ? "Claude" : "Codex";
131477
+ return getLocalProjectHistoryProviderKey(provider2);
131418
131478
  }
131419
131479
  function resolveSessionTitle(info, provider2) {
131420
131480
  const title2 = info.title?.trim();
@@ -131468,7 +131528,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
131468
131528
  function buildExternalHistoryMeta(args2) {
131469
131529
  return {
131470
131530
  provider: args2.provider,
131471
- source: args2.provider === "claude" ? "local-claude-home" : "local-codex-home",
131531
+ source: "local-acp-history",
131472
131532
  sourceAcpSessionId: args2.sourceAcpSessionId,
131473
131533
  sourceUpdatedAt: args2.sourceUpdatedAt ?? void 0,
131474
131534
  replayDigest: args2.materialized.replayDigest,
@@ -131482,7 +131542,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
131482
131542
  function buildMetadataOnlyExternalHistoryMeta(args2) {
131483
131543
  return {
131484
131544
  provider: args2.provider,
131485
- source: args2.provider === "claude" ? "local-claude-home" : "local-codex-home",
131545
+ source: "local-acp-history",
131486
131546
  sourceAcpSessionId: args2.sourceAcpSessionId,
131487
131547
  sourceUpdatedAt: args2.sourceUpdatedAt ?? void 0,
131488
131548
  importedTurnCount: 0,
@@ -131492,15 +131552,17 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
131492
131552
  };
131493
131553
  }
131494
131554
  class LocalProjectHistorySyncService {
131495
- constructor(manager, logger2, context2, provider2 = "codex") {
131555
+ constructor(manager, logger2, context2, provider2) {
131496
131556
  this.manager = manager;
131497
131557
  this.logger = logger2;
131498
131558
  this.context = context2;
131499
131559
  this.provider = provider2;
131560
+ this.providerKey = getLocalProjectHistoryProviderKey(provider2);
131500
131561
  }
131501
131562
  provider;
131563
+ providerKey;
131502
131564
  async syncLocalProject(args2) {
131503
- const leaseKey = `${this.provider}:${this.context.workspaceId}:${this.context.machineId}:${args2.localProjectId}`;
131565
+ const leaseKey = `${this.providerKey}:${this.context.workspaceId}:${this.context.machineId}:${args2.localProjectId}`;
131504
131566
  if (syncLeases.has(leaseKey)) {
131505
131567
  throw new Error(`${getProviderLabel(this.provider)} history sync is already running for this local project`);
131506
131568
  }
@@ -131520,7 +131582,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
131520
131582
  });
131521
131583
  }
131522
131584
  async importLocalProjectSessions(args2) {
131523
- const leaseKey = `${this.provider}:${this.context.workspaceId}:${this.context.machineId}:${args2.localProjectId}`;
131585
+ const leaseKey = `${this.providerKey}:${this.context.workspaceId}:${this.context.machineId}:${args2.localProjectId}`;
131524
131586
  if (syncLeases.has(leaseKey)) {
131525
131587
  throw new Error(`${getProviderLabel(this.provider)} history sync is already running for this local project`);
131526
131588
  }
@@ -131577,7 +131639,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
131577
131639
  acpSessionId,
131578
131640
  message: formatErrorMessage(error2)
131579
131641
  });
131580
- this.logger.debug(`[${this.provider}-history-sync] Failed to import ${getProviderLabel(this.provider)} session ${acpSessionId}: ${formatErrorMessage(error2)}`);
131642
+ this.logger.debug(`[${this.providerKey}-history-sync] Failed to import ${getProviderLabel(this.provider)} session ${acpSessionId}: ${formatErrorMessage(error2)}`);
131581
131643
  }
131582
131644
  }
131583
131645
  const catalog = await this.writeCatalogResult({
@@ -131627,7 +131689,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
131627
131689
  ...previous,
131628
131690
  history: {
131629
131691
  ...previous.history ?? {},
131630
- [this.provider]: {
131692
+ [this.providerKey]: {
131631
131693
  lastListedAt,
131632
131694
  sessions: Object.fromEntries(sessions.map((item) => [
131633
131695
  item.acpSessionId,
@@ -131653,9 +131715,9 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
131653
131715
  userId: this.context.userId,
131654
131716
  status: SessionStatusFactory.idle(),
131655
131717
  isArchived: false,
131656
- origin: this.provider,
131657
- cliType: "builtin",
131658
- agentType: this.provider,
131718
+ origin: "external-acp",
131719
+ cliType: this.provider.cliType,
131720
+ agentType: this.provider.agentType,
131659
131721
  project: args2.project,
131660
131722
  title: resolveSessionTitle(args2.info, this.provider),
131661
131723
  lastMessageAt,
@@ -131673,7 +131735,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
131673
131735
  }
131674
131736
  async refreshExistingSession(args2) {
131675
131737
  const externalHistory = args2.existing.meta.externalHistory;
131676
- if (externalHistory?.provider !== this.provider) {
131738
+ if (!externalHistory || getLocalProjectHistoryProviderKey(externalHistory.provider) !== this.providerKey) {
131677
131739
  return "skipped";
131678
131740
  }
131679
131741
  if (shouldSkipBySourceUpdatedAt(args2.info, externalHistory)) {
@@ -131698,7 +131760,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
131698
131760
  });
131699
131761
  if (replayDecision.reason === "digest_match") {
131700
131762
  await this.manager.repo.upsertDocMeta(getSessionRoomId(args2.existing.sessionId), {
131701
- origin: this.provider,
131763
+ origin: "external-acp",
131702
131764
  externalHistory: buildExternalHistoryMeta({
131703
131765
  provider: this.provider,
131704
131766
  sourceAcpSessionId: args2.acpSessionId,
@@ -131724,7 +131786,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
131724
131786
  await this.markConflict(args2.existing.sessionId, args2.info, materialized, appendDecision.reason);
131725
131787
  const synced2 = await sessionDoc.waitUntilSynced();
131726
131788
  if (!synced2) {
131727
- this.logger.debug(`[${this.provider}-history-sync] Conflict marker for ${args2.existing.sessionId} did not confirm sync before unload; clients may see the previous state until next sync.`);
131789
+ this.logger.debug(`[${this.providerKey}-history-sync] Conflict marker for ${args2.existing.sessionId} did not confirm sync before unload; clients may see the previous state until next sync.`);
131728
131790
  }
131729
131791
  await this.manager.cleanSessionDoc(args2.existing.sessionId, {
131730
131792
  preserveStatus: true
@@ -131737,7 +131799,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
131737
131799
  ...suffix
131738
131800
  ]);
131739
131801
  await this.manager.repo.upsertDocMeta(getSessionRoomId(args2.existing.sessionId), {
131740
- origin: this.provider,
131802
+ origin: "external-acp",
131741
131803
  lastMessageAt: resolveSourceUpdatedAtMs(args2.info, getServerNow()),
131742
131804
  externalHistory: buildExternalHistoryMeta({
131743
131805
  provider: this.provider,
@@ -131748,7 +131810,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
131748
131810
  });
131749
131811
  const synced = await sessionDoc.waitUntilSynced();
131750
131812
  if (!synced) {
131751
- this.logger.debug(`[${this.provider}-history-sync] Appended history for ${args2.existing.sessionId} did not confirm sync before unload; other clients may see the previous state until next sync.`);
131813
+ this.logger.debug(`[${this.providerKey}-history-sync] Appended history for ${args2.existing.sessionId} did not confirm sync before unload; other clients may see the previous state until next sync.`);
131752
131814
  }
131753
131815
  await this.manager.cleanSessionDoc(args2.existing.sessionId, {
131754
131816
  preserveStatus: true
@@ -131757,7 +131819,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
131757
131819
  }
131758
131820
  async markConflict(sessionId, info, materialized, reason) {
131759
131821
  await this.manager.repo.upsertDocMeta(getSessionRoomId(sessionId), {
131760
- origin: this.provider,
131822
+ origin: "external-acp",
131761
131823
  externalHistory: buildExternalHistoryMeta({
131762
131824
  provider: this.provider,
131763
131825
  sourceAcpSessionId: info.sessionId,
@@ -132046,6 +132108,8 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
132046
132108
  static CONTEXT_WINDOW_USAGE_THROTTLE_MS = 400;
132047
132109
  permissionRequestStartTimes = /* @__PURE__ */ new Map();
132048
132110
  machineHeartbeatTimer = null;
132111
+ machineHeartbeatInFlightStartedAt = null;
132112
+ machineHeartbeatSeq = 0;
132049
132113
  static MACHINE_HEARTBEAT_INTERVAL_MS = 2e4;
132050
132114
  evictForMemoryPressureFn = async () => ({
132051
132115
  availableMemoryBytes: 0,
@@ -132076,24 +132140,35 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
132076
132140
  startMachineHeartbeat() {
132077
132141
  this.stopMachineHeartbeat();
132078
132142
  this.logger.debug(`Machine heartbeat started (${Math.round(MessageHandler.MACHINE_HEARTBEAT_INTERVAL_MS / 1e3)}s interval)`);
132079
- void this.workspaceDocument.sendMachineHeartbeat().then(() => {
132080
- this.logger.debug("Machine heartbeat sent (initial)");
132081
- }).catch((error2) => {
132082
- this.logger.debug(`Initial machine heartbeat failed: ${formatErrorMessage(error2)}`);
132083
- });
132143
+ this.sendMachineHeartbeatWithDiagnostics("initial");
132084
132144
  this.machineHeartbeatTimer = setInterval(() => {
132085
- void this.workspaceDocument.sendMachineHeartbeat().then(() => {
132086
- this.logger.debug("Machine heartbeat sent");
132087
- }).catch((error2) => {
132088
- this.logger.debug(`Machine heartbeat failed: ${formatErrorMessage(error2)}`);
132089
- });
132145
+ this.sendMachineHeartbeatWithDiagnostics("periodic");
132090
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
+ });
132091
132165
  }
132092
132166
  stopMachineHeartbeat() {
132093
132167
  if (this.machineHeartbeatTimer) {
132094
132168
  clearInterval(this.machineHeartbeatTimer);
132095
132169
  this.machineHeartbeatTimer = null;
132096
132170
  }
132171
+ this.machineHeartbeatInFlightStartedAt = null;
132097
132172
  }
132098
132173
  preferredBaseBranch = (process.env.LODY_BASE_BRANCH || "main").trim() || "main";
132099
132174
  resolveGitHubProjectBranch(meta, preferredBranch) {
@@ -134545,8 +134620,15 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
134545
134620
  if (meta.processingUserMsgId) {
134546
134621
  return true;
134547
134622
  }
134623
+ if (meta.lastGoalCommand) {
134624
+ return true;
134625
+ }
134548
134626
  return Boolean(meta.latestUserMsgId && meta.latestUserMsgId !== meta.lastHandledUserMsgId);
134549
134627
  }
134628
+ async hasActiveGoal(sessionId) {
134629
+ const meta = (await this.workspaceDocument.repo.getDocMeta(getSessionRoomId(sessionId)))?.meta;
134630
+ return isSessionGoalWorking(meta?.latestGoal);
134631
+ }
134550
134632
  isArchiveInFlight(sessionId) {
134551
134633
  return this.archiveInFlight.has(sessionId);
134552
134634
  }
@@ -134830,6 +134912,8 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
134830
134912
  }
134831
134913
  queue;
134832
134914
  isStopped = false;
134915
+ static QUEUE_WAIT_WARNING_MS = 1e4;
134916
+ static PROCESSING_WARNING_MS = 3e4;
134833
134917
  enqueue(message, handler) {
134834
134918
  if (this.isStopped) {
134835
134919
  this.logger.debug("MessageProcessor is stopped, ignoring new message");
@@ -134837,11 +134921,30 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
134837
134921
  }
134838
134922
  const sessionId = this.extractSessionId(message);
134839
134923
  const queueKey = this.extractQueueKey(message);
134840
- 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}`);
134841
134934
  void this.queue.enqueue(queueKey, async () => {
134842
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?.();
134843
134946
  try {
134844
- 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}`);
134845
134948
  await handler(message);
134846
134949
  const duration2 = Date.now() - startTime;
134847
134950
  this.logger.debug(`Processed message type=${message.type} sessionId=${sessionId || "N/A"} duration=${duration2}ms`);
@@ -134851,6 +134954,9 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
134851
134954
  const err2 = error2 instanceof Error ? error2 : new Error(String(error2));
134852
134955
  this.logger.error(`Failed to process message type=${message.type} sessionId=${sessionId || "N/A"} duration=${duration2}ms: ${err2.message}`);
134853
134956
  this.emit("message:error", err2, message);
134957
+ } finally {
134958
+ processingCompleted = true;
134959
+ clearInterval(processingWarning);
134854
134960
  }
134855
134961
  });
134856
134962
  }
@@ -135124,6 +135230,9 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
135124
135230
  if (this.deps.hasActiveTurn(sessionId)) {
135125
135231
  return false;
135126
135232
  }
135233
+ if (await this.deps.hasActiveGoal(sessionId)) {
135234
+ return false;
135235
+ }
135127
135236
  if (this.deps.hasPendingUpdates(sessionId)) {
135128
135237
  return false;
135129
135238
  }
@@ -135212,6 +135321,8 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
135212
135321
  }
135213
135322
  async dispatchLocalMessageForResponse(message) {
135214
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()}`);
135215
135326
  return await new Promise((resolve2, reject) => {
135216
135327
  this.messageProcessor.enqueue(message, async (nextMessage) => {
135217
135328
  const responses = [];
@@ -135221,6 +135332,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
135221
135332
  return;
135222
135333
  }
135223
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()}`);
135224
135336
  resolve2([
135225
135337
  ...responses
135226
135338
  ]);
@@ -135230,6 +135342,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
135230
135342
  return;
135231
135343
  }
135232
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)}`);
135233
135346
  reject(error2);
135234
135347
  };
135235
135348
  const context2 = {
@@ -135263,6 +135376,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
135263
135376
  this.gcManager = new SessionGCManager(gcConfig, {
135264
135377
  getSessionLastActivity: (sessionId) => handler.getLastActivity(sessionId),
135265
135378
  hasActiveTurn: (sessionId) => handler.hasActiveTurn(sessionId),
135379
+ hasActiveGoal: async (sessionId) => await handler.hasActiveGoal(sessionId),
135266
135380
  hasPendingUpdates: (sessionId) => handler.hasPendingUpdates(sessionId),
135267
135381
  hasPendingUserWork: async (sessionId) => await handler.hasPendingUserWork(sessionId),
135268
135382
  isArchiveInFlight: (sessionId) => handler.isArchiveInFlight(sessionId),
@@ -142396,7 +142510,7 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
142396
142510
  await this.workspaceDocument.getOrCreateSessionDoc(config2.sessionId);
142397
142511
  this.logger.debug(`[${config2.sessionId}] Session will enter create inner`);
142398
142512
  const createInnerStart = performance$1.now();
142399
- const session = await this.createSessionInner(config2);
142513
+ const session = await withSlowOperationWarning(this.createSessionInner(config2), this.logger, "session.createSessionInner", config2.sessionId);
142400
142514
  session.ghTokenInjected = ghTokenInjected;
142401
142515
  const createInnerDurationMs = performance$1.now() - createInnerStart;
142402
142516
  this.logger.debug(`[${config2.sessionId}] Session create inner finished in ${Math.round(createInnerDurationMs)}ms`);
@@ -142417,7 +142531,7 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
142417
142531
  this.logger.debug(`[${sessionId}] About to call session.createAgent`);
142418
142532
  try {
142419
142533
  const createAgentStart = performance$1.now();
142420
- acpSessionId = await session.createAgent({
142534
+ acpSessionId = await withSlowOperationWarning(session.createAgent({
142421
142535
  cliType: config2.agentCliType,
142422
142536
  agentType: config2.agentType,
142423
142537
  command: setting.exec.command,
@@ -142477,7 +142591,7 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
142477
142591
  onCodexImageGenerationEnd: (event) => {
142478
142592
  this.emit("onCodexImageGenerationEnd", sessionId, event);
142479
142593
  }
142480
- });
142594
+ }), this.logger, `session.createAgent(resume=${requestedResumeSessionId ? "yes" : "no"})`, sessionId);
142481
142595
  const createAgentDurationMs = performance$1.now() - createAgentStart;
142482
142596
  this.logger.debug(`[${sessionId}] Session createAgent finished in ${Math.round(createAgentDurationMs)}ms (acpSessionId=${acpSessionId})`);
142483
142597
  this.logger.debug(`[${sessionId}] createAgent returned successfully`);
@@ -144598,7 +144712,8 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
144598
144712
  const existing = rawExisting && typeof rawExisting === "object" ? rawExisting : {};
144599
144713
  const previous = existing[entry2.localProjectId];
144600
144714
  const nowMs = getServerNow();
144601
- await runtime.lody.documentManager.repo.upsertDocMeta(machineRoomId, {
144715
+ const repo = runtime.lody.documentManager.repo;
144716
+ await repo.upsertDocMeta(machineRoomId, {
144602
144717
  localProjects: {
144603
144718
  ...existing,
144604
144719
  [entry2.localProjectId]: {
@@ -144624,7 +144739,8 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
144624
144739
  ...existing
144625
144740
  };
144626
144741
  delete next[localProjectId];
144627
- await runtime.lody.documentManager.repo.upsertDocMeta(machineRoomId, {
144742
+ const repo = runtime.lody.documentManager.repo;
144743
+ await repo.upsertDocMeta(machineRoomId, {
144628
144744
  localProjects: next
144629
144745
  });
144630
144746
  }
@@ -145118,6 +145234,23 @@ Received ${signal}, shutting down gracefully...` : "\nShutting down gracefully..
145118
145234
  shutdown: shutdown2
145119
145235
  };
145120
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
+ }
145121
145254
  const ELECTRON_BOOTSTRAP_ENV = "LODY_ELECTRON_BOOTSTRAP";
145122
145255
  const ELECTRON_SESSION_TOKEN_ENV = "LODY_ELECTRON_SESSION_TOKEN";
145123
145256
  const ELECTRON_SESSION_USER_ID_ENV = "LODY_ELECTRON_SESSION_USER_ID";
@@ -145345,7 +145478,13 @@ Received ${signal}, shutting down gracefully...` : "\nShutting down gracefully..
145345
145478
  runtimeStateReporter,
145346
145479
  onFatalAuthFailure: (error2) => triggerFatalAuthShutdown?.(error2)
145347
145480
  });
145348
- registerProcessCleanup(() => fleet.shutdown());
145481
+ const eventLoopLagMonitor = startEventLoopLagMonitor(logger2, {
145482
+ label: "lody start"
145483
+ });
145484
+ registerProcessCleanup(async () => {
145485
+ eventLoopLagMonitor.stop();
145486
+ await fleet.shutdown();
145487
+ });
145349
145488
  const shutdownSignals = process.platform === "win32" ? [
145350
145489
  "SIGINT",
145351
145490
  "SIGTERM",
@@ -145361,6 +145500,7 @@ Received ${signal}, shutting down gracefully...` : "\nShutting down gracefully..
145361
145500
  logger: logger2,
145362
145501
  shutdown: async () => {
145363
145502
  unregisterProcessCleanup();
145503
+ eventLoopLagMonitor.stop();
145364
145504
  await fleet.shutdown();
145365
145505
  },
145366
145506
  flushTelemetry: () => shutdownPostHog(),
@@ -145399,6 +145539,7 @@ Received ${signal}, shutting down gracefully...` : "\nShutting down gracefully..
145399
145539
  });
145400
145540
  shutdownController.unregister();
145401
145541
  unregisterProcessCleanup();
145542
+ eventLoopLagMonitor.stop();
145402
145543
  await fleet.shutdown().catch((err2) => {
145403
145544
  logger2.error(`Cleanup failed: ${err2 instanceof Error ? err2.message : "Unknown error"}`);
145404
145545
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lody",
3
- "version": "0.51.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",
@@ -20,11 +20,11 @@
20
20
  "node": ">=18.0.0"
21
21
  },
22
22
  "optionalDependencies": {
23
- "acp-extension-claude": "0.31.1",
23
+ "acp-extension-claude": "0.34.1",
24
24
  "acp-extension-codex": "0.14.3"
25
25
  },
26
26
  "devDependencies": {
27
- "@agentclientprotocol/sdk": "^0.20.0",
27
+ "@agentclientprotocol/sdk": "^0.21.0",
28
28
  "@better-auth/api-key": "1.5.5",
29
29
  "@convex-dev/better-auth": "0.11.2",
30
30
  "@loro-dev/flock-wasm": "^0.3.3",
@@ -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",
79
78
  "@lody/shared": "0.0.1",
80
- "loro-code": "0.0.1"
79
+ "loro-code": "0.0.1",
80
+ "@lody/cli-supervisor": "0.0.1"
81
81
  },
82
82
  "files": [
83
83
  "dist",