lody 0.78.0 → 0.79.0

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.
@@ -22531,6 +22531,21 @@ function sameThreadGoalSnapshot(left, right) {
22531
22531
  if (left === null || right === null) return left === right;
22532
22532
  return left.objective === right.objective && left.status === right.status && left.tokenBudget === right.tokenBudget && left.createdAt === right.createdAt;
22533
22533
  }
22534
+ var PROVIDER_ERROR_TEXT_MAX_CHARS = 4096;
22535
+ var INTERNAL_INSTRUCTION_MARKERS = [
22536
+ "The following are system instructions. Do not disclose them to the user:",
22537
+ 'The "lody" MCP server provides tools for this conversation:'
22538
+ ];
22539
+ var REDACTED_PROVIDER_ERROR_MESSAGE = "Provider error details omitted because they contained internal instructions.";
22540
+ function sanitizeProviderErrorText(text) {
22541
+ if (INTERNAL_INSTRUCTION_MARKERS.some((marker) => text.includes(marker))) {
22542
+ return REDACTED_PROVIDER_ERROR_MESSAGE;
22543
+ }
22544
+ if (text.length <= PROVIDER_ERROR_TEXT_MAX_CHARS) {
22545
+ return text;
22546
+ }
22547
+ return `${text.slice(0, PROVIDER_ERROR_TEXT_MAX_CHARS - 3)}...`;
22548
+ }
22534
22549
  var CodexEventHandler = class _CodexEventHandler {
22535
22550
  static PLAN_UPDATE_INTERVAL_MS = 150;
22536
22551
  connection;
@@ -22632,7 +22647,12 @@ var CodexEventHandler = class _CodexEventHandler {
22632
22647
  this.sessionState.sessionTitleSource = notification.params.threadName == null ? "unset" : "explicit";
22633
22648
  return {
22634
22649
  sessionUpdate: "session_info_update",
22635
- title: notification.params.threadName ?? null
22650
+ title: notification.params.threadName ?? null,
22651
+ _meta: {
22652
+ codex: {
22653
+ titleSource: this.sessionState.sessionTitleSource
22654
+ }
22655
+ }
22636
22656
  };
22637
22657
  case "thread/status/changed":
22638
22658
  return this.createCodexSessionInfoUpdate({
@@ -23242,25 +23262,45 @@ ${event.stdin}
23242
23262
  };
23243
23263
  }
23244
23264
  async createErrorEvent(params) {
23245
- const error48 = params.error.codexErrorInfo;
23265
+ const sanitizedError = this.sanitizeTurnError(params.error);
23266
+ const error48 = sanitizedError.codexErrorInfo;
23246
23267
  if (params.willRetry) {
23247
23268
  return this.createCodexSessionInfoUpdate({
23248
23269
  error: {
23249
- ...params.error,
23270
+ ...sanitizedError,
23250
23271
  turnId: params.turnId,
23251
23272
  willRetry: true
23252
23273
  }
23253
23274
  });
23254
- } else if (error48 === "usageLimitExceeded") {
23275
+ }
23276
+ if (error48 === "usageLimitExceeded") {
23255
23277
  this.failure = RequestError.internalError(
23256
- this.createTurnErrorData(params.error)
23278
+ this.createTurnErrorData(sanitizedError)
23257
23279
  );
23258
23280
  } else if (this.isAuthenticationRequiredError(error48)) {
23259
- this.failure = this.sessionState.authConfigured ? RequestError.internalError(this.createTurnErrorData(params.error)) : RequestError.authRequired(this.createTurnErrorData(params.error), params.error.message);
23281
+ this.failure = this.sessionState.authConfigured ? RequestError.internalError(this.createTurnErrorData(sanitizedError)) : RequestError.authRequired(
23282
+ this.createTurnErrorData(sanitizedError),
23283
+ sanitizedError.message
23284
+ );
23285
+ } else {
23286
+ this.failure = RequestError.internalError(
23287
+ this.createTurnErrorData(sanitizedError)
23288
+ );
23260
23289
  }
23261
- return createAgentTextMessageChunk(`${params.error.message}
23262
-
23263
- `);
23290
+ return this.createCodexSessionInfoUpdate({
23291
+ error: {
23292
+ ...sanitizedError,
23293
+ turnId: params.turnId,
23294
+ willRetry: false
23295
+ }
23296
+ });
23297
+ }
23298
+ sanitizeTurnError(error48) {
23299
+ return {
23300
+ ...error48,
23301
+ message: sanitizeProviderErrorText(error48.message),
23302
+ additionalDetails: error48.additionalDetails === null ? null : sanitizeProviderErrorText(error48.additionalDetails)
23303
+ };
23264
23304
  }
23265
23305
  isAuthenticationRequiredError(error48) {
23266
23306
  return error48 === "unauthorized" || this.getHttpStatusCode(error48) === 401;
@@ -29085,7 +29125,12 @@ Check ${configPath} and project .codex directories, especially their config.toml
29085
29125
  sessionState.sessionTitleSource = "explicit";
29086
29126
  await session.update({
29087
29127
  sessionUpdate: "session_info_update",
29088
- title: explicitTitle
29128
+ title: explicitTitle,
29129
+ _meta: {
29130
+ codex: {
29131
+ titleSource: "explicit"
29132
+ }
29133
+ }
29089
29134
  });
29090
29135
  return;
29091
29136
  }
@@ -29109,7 +29154,12 @@ Check ${configPath} and project .codex directories, especially their config.toml
29109
29154
  const session = new ACPSessionConnection(this.connection, sessionState.sessionId);
29110
29155
  await session.update({
29111
29156
  sessionUpdate: "session_info_update",
29112
- title
29157
+ title,
29158
+ _meta: {
29159
+ codex: {
29160
+ titleSource: "fallback"
29161
+ }
29162
+ }
29113
29163
  });
29114
29164
  }
29115
29165
  createPromptFallbackTitle(prompt) {
@@ -76,7 +76,7 @@ let __tla = Promise.all([
76
76
  }
77
77
  })()
78
78
  ]).then(async () => {
79
- const reviewViewerVersion = "0.78.0";
79
+ const reviewViewerVersion = "0.79.0";
80
80
  const reviewViewerSha256 = "41b6ffd0acff2c355e190b604776a89138f9598a1772d09390a6d5af514e71bb";
81
81
  const reviewViewerFileName = "standalone.html";
82
82
  const DEFAULT_CDN_BASES = [
package/dist/codex-acp.js CHANGED
@@ -3,5 +3,5 @@
3
3
  console.error("CODEX_PATH is required for the bundled Codex ACP adapter.");
4
4
  process.exit(1);
5
5
  }
6
- await import("./chunks/index-gHAU1HiK.js");
6
+ await import("./chunks/index-DxeR8yGt.js");
7
7
  })();
package/dist/index.js CHANGED
@@ -24638,7 +24638,7 @@ ${peerId}`;
24638
24638
  const getSessionIdFromRoomId = (roomId) => isSessionDocRoomId(roomId) ? roomId.slice(SESSION_DOC_PREFIX.length) : null;
24639
24639
  const isMachineDocRoomId = (roomId) => roomId.startsWith(MACHINE_DOC_PREFIX);
24640
24640
  const name$1 = "@lody/cli-cloud";
24641
- const version$7 = "0.78.0";
24641
+ const version$7 = "0.79.0";
24642
24642
  const type$2 = "module";
24643
24643
  const scripts = {
24644
24644
  "dev": "node dev.mjs",
@@ -110356,6 +110356,14 @@ ${incoming}`).output;
110356
110356
  ]).optional()
110357
110357
  })
110358
110358
  });
110359
+ const CodexSessionTitleSchema = object$1({
110360
+ titleSource: _enum$1([
110361
+ "explicit",
110362
+ "fallback",
110363
+ "unset",
110364
+ "unknown"
110365
+ ])
110366
+ });
110359
110367
  const CodexProposedPlanParamsSchema = object$1({
110360
110368
  schemaVersion: literal$1(1),
110361
110369
  sessionId: string$1(),
@@ -111060,7 +111068,13 @@ ${incoming}`).output;
111060
111068
  });
111061
111069
  }
111062
111070
  handleAgentSessionTitleUpdate(notification) {
111063
- if (!usesAcpProvidedSessionTitle(this.options.agentConfig?.cliType, this.options.agentConfig?.agentType) || notification.update.sessionUpdate !== "session_info_update") {
111071
+ if (notification.update.sessionUpdate !== "session_info_update") {
111072
+ return;
111073
+ }
111074
+ const ownsTitleGeneration = usesAcpProvidedSessionTitle(this.options.agentConfig?.cliType, this.options.agentConfig?.agentType);
111075
+ const codexTitleMeta = this.isCodexAgent() ? CodexSessionTitleSchema.safeParse(notification.update._meta?.codex) : null;
111076
+ const isExplicitCodexTitle = codexTitleMeta?.success === true && codexTitleMeta.data.titleSource === "explicit";
111077
+ if (!ownsTitleGeneration && !isExplicitCodexTitle) {
111064
111078
  return;
111065
111079
  }
111066
111080
  const title2 = typeof notification.update.title === "string" ? notification.update.title.trim() : "";
@@ -123501,6 +123515,42 @@ ${fallbackStderrTail}` : errorMessage2;
123501
123515
  windowsHide: true
123502
123516
  });
123503
123517
  };
123518
+ const CodexConfigOverrideSchema = record$1(string$1(), unknown());
123519
+ function prepareCodexHomeEnv(options, baseEnv) {
123520
+ const isBuiltinCodex = options.cliType === "builtin" && options.agentType === "codex";
123521
+ const isTitleAgentCodexRun = isBuiltinCodex && baseEnv.LODY_TITLE_AGENT === "1";
123522
+ const shouldUseWorkdirCodexHome = isBuiltinCodex && !baseEnv.CODEX_HOME && (baseEnv.LODY_E2E === "1" || isTitleAgentCodexRun);
123523
+ return {
123524
+ env: shouldUseWorkdirCodexHome ? {
123525
+ ...baseEnv,
123526
+ CODEX_HOME: path__default$1.join(options.workdir, ".codex")
123527
+ } : baseEnv,
123528
+ isTitleAgentCodexRun,
123529
+ shouldUseWorkdirCodexHome
123530
+ };
123531
+ }
123532
+ function withTitleAgentCodexConfig(env2) {
123533
+ const existingConfig = env2.CODEX_CONFIG ? CodexConfigOverrideSchema.parse(JSON.parse(env2.CODEX_CONFIG)) : {};
123534
+ return {
123535
+ ...env2,
123536
+ CODEX_CONFIG: JSON.stringify({
123537
+ ...existingConfig,
123538
+ project_doc_max_bytes: 0,
123539
+ include_environment_context: false,
123540
+ skills: {
123541
+ include_instructions: false,
123542
+ bundled: {
123543
+ enabled: false
123544
+ }
123545
+ }
123546
+ })
123547
+ };
123548
+ }
123549
+ function copyTitleAgentCodexConfig(sourcePath, destinationPath) {
123550
+ if (fs.existsSync(sourcePath)) {
123551
+ fs.copyFileSync(sourcePath, destinationPath);
123552
+ }
123553
+ }
123504
123554
  const startLocalAcpAgent = async (options) => {
123505
123555
  options.signal?.throwIfAborted();
123506
123556
  const launch = await resolveACPProcessLaunchAsync({
@@ -123525,47 +123575,46 @@ ${fallbackStderrTail}` : errorMessage2;
123525
123575
  isResume: false
123526
123576
  };
123527
123577
  const baseEnv = withoutElectronBootstrapCredentials(options.env ?? process.env);
123528
- const shouldUseWorkdirCodexHome = options.cliType === "builtin" && options.agentType === "codex" && !baseEnv.CODEX_HOME && (baseEnv.LODY_E2E === "1" || baseEnv.LODY_TITLE_AGENT === "1");
123529
- const env2 = shouldUseWorkdirCodexHome ? {
123530
- ...baseEnv,
123531
- CODEX_HOME: path__default$1.join(options.workdir, ".codex")
123532
- } : baseEnv;
123578
+ const { env: env2, isTitleAgentCodexRun, shouldUseWorkdirCodexHome } = prepareCodexHomeEnv(options, baseEnv);
123533
123579
  const loginShellEnv = await getLoginShellEnv();
123534
- const envWithAcpStartup = withoutElectronBootstrapCredentials(withLodyNpmCacheForNpx(launch.command, withDefaultAcpPathEntries(mergeACPProcessEnv(launch, mergeLoginShellEnv(env2, loginShellEnv)), options.agentType)));
123580
+ const mergedStartupEnv = withoutElectronBootstrapCredentials(withLodyNpmCacheForNpx(launch.command, withDefaultAcpPathEntries(mergeACPProcessEnv(launch, mergeLoginShellEnv(env2, loginShellEnv)), options.agentType)));
123581
+ const envWithAcpStartup = isTitleAgentCodexRun ? withTitleAgentCodexConfig(mergedStartupEnv) : mergedStartupEnv;
123535
123582
  const keepCodexHome = env2.LODY_KEEP_CODEX_HOME === "1";
123536
123583
  const defaultCodexHome = path__default$1.join(os__default$1.homedir(), ".codex");
123537
123584
  const defaultAuthPath = path__default$1.join(defaultCodexHome, "auth.json");
123585
+ const defaultConfigPath = path__default$1.join(defaultCodexHome, "config.toml");
123538
123586
  const localAuthPath = env2.CODEX_HOME ? path__default$1.join(env2.CODEX_HOME, "auth.json") : null;
123539
- const isTitleAgentRun = baseEnv.LODY_TITLE_AGENT === "1";
123540
- const TITLE_AGENT_CODEX_CONFIG_TOML = `project_doc_max_bytes = 0
123541
- include_environment_context = false
123542
-
123543
- [skills]
123544
- include_instructions = false
123545
-
123546
- [skills.bundled]
123547
- enabled = false
123548
- `;
123587
+ const localConfigPath = env2.CODEX_HOME ? path__default$1.join(env2.CODEX_HOME, "config.toml") : null;
123549
123588
  let lastStderrTail = "";
123550
123589
  const attemptStart = async (attemptArgs, startupTimeouts) => {
123551
123590
  options.signal?.throwIfAborted();
123552
123591
  lastStderrTail = "";
123553
- let copiedAuth = false;
123554
123592
  if (shouldUseWorkdirCodexHome) {
123555
123593
  fs.mkdirSync(env2.CODEX_HOME, {
123556
123594
  recursive: true
123557
123595
  });
123558
- if (isTitleAgentRun) {
123559
- fs.writeFileSync(path__default$1.join(env2.CODEX_HOME, "config.toml"), TITLE_AGENT_CODEX_CONFIG_TOML);
123596
+ if (isTitleAgentCodexRun && localConfigPath && fs.existsSync(defaultConfigPath)) {
123597
+ copyTitleAgentCodexConfig(defaultConfigPath, localConfigPath);
123560
123598
  }
123561
123599
  if (localAuthPath && fs.existsSync(defaultAuthPath) && !fs.existsSync(localAuthPath)) {
123562
123600
  try {
123563
123601
  fs.copyFileSync(defaultAuthPath, localAuthPath);
123564
- copiedAuth = true;
123565
123602
  } catch {
123566
123603
  }
123567
123604
  }
123568
123605
  }
123606
+ const cleanupCodexHome = () => {
123607
+ if (!shouldUseWorkdirCodexHome || !env2.CODEX_HOME || keepCodexHome) {
123608
+ return;
123609
+ }
123610
+ try {
123611
+ fs.rmSync(env2.CODEX_HOME, {
123612
+ recursive: true,
123613
+ force: true
123614
+ });
123615
+ } catch {
123616
+ }
123617
+ };
123569
123618
  captureAcpSpawnStarted(spawnAnalyticsProps);
123570
123619
  let agentProcess;
123571
123620
  try {
@@ -123581,12 +123630,15 @@ enabled = false
123581
123630
  spawnImpl: options.spawnImpl
123582
123631
  });
123583
123632
  } catch (error2) {
123633
+ cleanupCodexHome();
123584
123634
  captureAcpSpawnFailed({
123585
123635
  ...spawnAnalyticsProps,
123586
123636
  reason: classifyCliSpawnReason(error2)
123587
123637
  });
123588
123638
  throw error2;
123589
123639
  }
123640
+ agentProcess.once("exit", cleanupCodexHome);
123641
+ agentProcess.once("error", cleanupCodexHome);
123590
123642
  options.logger.debug(`[acp-startup] spawned ACP process (cliType=${options.cliType} agentType=${options.agentType} workdir=${options.workdir})`);
123591
123643
  const stderrStream = agentProcess.stderr;
123592
123644
  let stderrTail = "";
@@ -123624,24 +123676,6 @@ enabled = false
123624
123676
  ],
123625
123677
  getStderrTail: () => stderrTail
123626
123678
  });
123627
- if (shouldUseWorkdirCodexHome && env2.CODEX_HOME && !keepCodexHome) {
123628
- const cleanup = () => {
123629
- try {
123630
- if (copiedAuth && localAuthPath) {
123631
- fs.rmSync(localAuthPath, {
123632
- force: true
123633
- });
123634
- }
123635
- fs.rmSync(env2.CODEX_HOME, {
123636
- recursive: true,
123637
- force: true
123638
- });
123639
- } catch {
123640
- }
123641
- };
123642
- agentProcess.once("exit", cleanup);
123643
- agentProcess.once("error", cleanup);
123644
- }
123645
123679
  if (!agentProcess.stdout) {
123646
123680
  throw new Error("Agent process stdout is not available");
123647
123681
  }
@@ -124463,7 +124497,7 @@ enabled = false
124463
124497
  options.logger.debug(`[title-generator] Skipping unavailable title config option ${key2}=${String(value)}`);
124464
124498
  }
124465
124499
  }
124466
- const extractTitleChunkFromNotification = (notification) => {
124500
+ const extractTitleChunkFromNotification = (notification, agentType) => {
124467
124501
  const update2 = notification.update;
124468
124502
  if (update2.sessionUpdate !== "agent_message_chunk") {
124469
124503
  return null;
@@ -124473,6 +124507,12 @@ enabled = false
124473
124507
  return null;
124474
124508
  }
124475
124509
  const codexMeta = update2._meta?.["codex"];
124510
+ if (agentType === "codex") {
124511
+ if (!codexMeta || typeof codexMeta !== "object" || !("phase" in codexMeta) || codexMeta.phase !== "final_answer") {
124512
+ return null;
124513
+ }
124514
+ return content.text;
124515
+ }
124476
124516
  if (codexMeta && typeof codexMeta === "object" && "phase" in codexMeta && codexMeta.phase !== "final_answer") {
124477
124517
  return null;
124478
124518
  }
@@ -124487,6 +124527,32 @@ enabled = false
124487
124527
  if (!withoutMarkdown) return null;
124488
124528
  return withoutMarkdown.slice(0, 80);
124489
124529
  };
124530
+ const looksLikeProviderControlPayload = (candidate) => {
124531
+ const trimmed2 = candidate.trim();
124532
+ if (/^\{\s*['"]?(?:type|status|error)['"]?\s*:/i.test(trimmed2) || /["']type["']\s*:\s*["'](?:error|warning)["']/i.test(trimmed2) || /\binvalid_request_error\b/i.test(trimmed2) || /^HTTP\s+[45]\d\d\b/i.test(trimmed2) || /^(?:internal server error|bad request|too many requests)\s*(?:[:({-]|$)/i.test(trimmed2) || /^(?:error|warning|failed|failure)\s*(?:[:{]|\[)/i.test(trimmed2)) {
124533
+ return true;
124534
+ }
124535
+ try {
124536
+ const parsed = JSON.parse(trimmed2);
124537
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
124538
+ return false;
124539
+ }
124540
+ const record2 = parsed;
124541
+ return record2.type === "error" || record2.type === "warning" || typeof record2.status === "number" && record2.status >= 400 || typeof record2.error === "string" || typeof record2.error === "object" && record2.error !== null;
124542
+ } catch {
124543
+ return false;
124544
+ }
124545
+ };
124546
+ const sanitizeGeneratedTitle = (candidate) => {
124547
+ if (!candidate) {
124548
+ return null;
124549
+ }
124550
+ const withoutInternalInstructions = sanitizeLodyInternalInstructions(candidate);
124551
+ if (!withoutInternalInstructions || looksLikeProviderControlPayload(withoutInternalInstructions)) {
124552
+ return null;
124553
+ }
124554
+ return sanitizeTitle(withoutInternalInstructions);
124555
+ };
124490
124556
  const ensureWorkdirIsGitRepo = async (workdir, logger2) => {
124491
124557
  const isGitRepo = async () => {
124492
124558
  try {
@@ -124537,13 +124603,8 @@ enabled = false
124537
124603
  }
124538
124604
  };
124539
124605
  const generateTitleIsolated = async (options) => {
124540
- const workdir = path__default$1.join(os__default$1.tmpdir(), "lody-title-agent");
124541
- fs.mkdirSync(workdir, {
124542
- recursive: true
124543
- });
124544
- if (options.agentType === "codex" && !await ensureWorkdirIsGitRepo(workdir, options.logger)) {
124545
- return sanitizeTitle(options.taskPrompt);
124546
- }
124606
+ const fallbackTitle = sanitizeGeneratedTitle(options.taskPrompt);
124607
+ const workdir = fs.mkdtempSync(path__default$1.join(os__default$1.tmpdir(), "lody-title-agent-"));
124547
124608
  const sleep2 = (ms2) => new Promise((resolve2) => setTimeout(resolve2, ms2));
124548
124609
  const tryGenerateWithArgs = async (extraArgs) => {
124549
124610
  let collectedText = "";
@@ -124564,7 +124625,7 @@ enabled = false
124564
124625
  terminalManager: noopTerminalManager,
124565
124626
  terminalEnabled: false,
124566
124627
  onUpdateMessage: (msg) => {
124567
- const text = extractTitleChunkFromNotification(msg);
124628
+ const text = extractTitleChunkFromNotification(msg, options.agentType);
124568
124629
  if (text) {
124569
124630
  collectedText += text;
124570
124631
  }
@@ -124599,16 +124660,16 @@ enabled = false
124599
124660
  while (Date.now() < deadline && collectedText.trim() === "") {
124600
124661
  await sleep2(100);
124601
124662
  }
124602
- const fromStream = sanitizeTitle(collectedText);
124663
+ const fromStream = sanitizeGeneratedTitle(collectedText);
124603
124664
  if (fromStream) {
124604
124665
  return fromStream;
124605
124666
  }
124606
- const rawTitle = extractTextFromAgentResponse(response2) ?? options.taskPrompt;
124607
- const sanitized = sanitizeTitle(rawTitle);
124667
+ const rawTitle = extractTextFromAgentResponse(response2);
124668
+ const sanitized = sanitizeGeneratedTitle(rawTitle);
124608
124669
  if (sanitized) {
124609
124670
  return sanitized;
124610
124671
  }
124611
- return sanitizeTitle(options.taskPrompt);
124672
+ return fallbackTitle;
124612
124673
  } finally {
124613
124674
  await shutdownLocalAcpAgent({
124614
124675
  agentProcess,
@@ -124619,20 +124680,26 @@ enabled = false
124619
124680
  });
124620
124681
  }
124621
124682
  };
124622
- let lastError = null;
124623
124683
  try {
124624
- const title2 = await tryGenerateWithArgs([]);
124625
- if (title2) {
124626
- return title2;
124684
+ if (options.agentType === "codex" && !await ensureWorkdirIsGitRepo(workdir, options.logger)) {
124685
+ return fallbackTitle;
124686
+ }
124687
+ try {
124688
+ return await tryGenerateWithArgs([]) ?? fallbackTitle;
124689
+ } catch (error2) {
124690
+ options.logger.debug("Failed to generate title with args:", error2);
124691
+ return fallbackTitle;
124692
+ }
124693
+ } finally {
124694
+ try {
124695
+ fs.rmSync(workdir, {
124696
+ recursive: true,
124697
+ force: true
124698
+ });
124699
+ } catch (error2) {
124700
+ options.logger.debug(`[title-generator] Failed to remove temporary workdir ${workdir}: ${formatErrorMessage(error2)}`);
124627
124701
  }
124628
- } catch (error2) {
124629
- options.logger.debug("Failed to generate title with args:", error2);
124630
- lastError = error2;
124631
- }
124632
- if (lastError) {
124633
- throw lastError;
124634
124702
  }
124635
- return null;
124636
124703
  };
124637
124704
  const titleToBranchName = (title2) => {
124638
124705
  if (!title2 || typeof title2 !== "string") {
@@ -160566,7 +160633,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
160566
160633
  pendingProcessLifecycleAction = null;
160567
160634
  store = new SessionTransientStore();
160568
160635
  sessionActivePresence;
160569
- titleGenerationInFlight = /* @__PURE__ */ new Set();
160636
+ titleGenerationInFlight = /* @__PURE__ */ new Map();
160570
160637
  archiveWatchHandle = null;
160571
160638
  archiveInFlight = /* @__PURE__ */ new Set();
160572
160639
  deleteWatchHandle = null;
@@ -166081,18 +166148,23 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
166081
166148
  if (usesAcpProvidedSessionTitle(cliType, agentType)) {
166082
166149
  return;
166083
166150
  }
166084
- if (this.titleGenerationInFlight.has(sessionId)) {
166151
+ const existingGeneration = this.titleGenerationInFlight.get(sessionId);
166152
+ if (existingGeneration) {
166153
+ try {
166154
+ await existingGeneration;
166155
+ } catch (error2) {
166156
+ this.logger.debug(`[${sessionId}] Existing session title generation failed: ${formatErrorMessage(error2)}`);
166157
+ }
166085
166158
  return;
166086
166159
  }
166087
- this.titleGenerationInFlight.add(sessionId);
166088
- try {
166160
+ const generation = (async () => {
166089
166161
  const sessionDoc = await this.workspaceDocument.getOrCreateSessionDoc(sessionId);
166090
166162
  const meta = await sessionDoc.getMetaState();
166091
166163
  const existingTitle = meta?.title?.trim();
166092
166164
  const canOverwriteExistingTitle = meta?.titleSource === "draft";
166093
166165
  if (existingTitle && !canOverwriteExistingTitle) {
166094
166166
  this.logger.debug(`[${sessionId}] Skipping session title generation because title already set: ${existingTitle}`);
166095
- return;
166167
+ return meta?.titleSource === "generated" ? existingTitle : null;
166096
166168
  }
166097
166169
  this.logger.debug(`[${sessionId}] Generating session title because title is missing`);
166098
166170
  const resolvedTitleConfig = titleConfig ?? await this.resolveTitleConfig(sessionId, meta?.agentConfigId);
@@ -166108,20 +166180,27 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
166108
166180
  });
166109
166181
  if (!title2) {
166110
166182
  this.logger.debug(`[${sessionId}] Session title generation returned empty result`);
166111
- return;
166183
+ return null;
166112
166184
  }
166113
166185
  const applied = await sessionDoc.setTitleIfSourceIn(title2, "generated", [
166114
166186
  "draft"
166115
166187
  ]);
166116
166188
  if (!applied) {
166117
166189
  this.logger.debug(`[${sessionId}] Skipping generated title because title changed while generation was in flight`);
166118
- return;
166190
+ } else {
166191
+ this.logger.debug(`[${sessionId}] Session title stored in metadata: ${title2}`);
166119
166192
  }
166120
- this.logger.debug(`[${sessionId}] Session title stored in metadata: ${title2}`);
166193
+ return title2;
166194
+ })();
166195
+ this.titleGenerationInFlight.set(sessionId, generation);
166196
+ try {
166197
+ await generation;
166121
166198
  } catch (error2) {
166122
166199
  this.logger.debug(`[${sessionId}] Failed to generate/store session title: ${formatErrorMessage(error2)}`);
166123
166200
  } finally {
166124
- this.titleGenerationInFlight.delete(sessionId);
166201
+ if (this.titleGenerationInFlight.get(sessionId) === generation) {
166202
+ this.titleGenerationInFlight.delete(sessionId);
166203
+ }
166125
166204
  }
166126
166205
  }
166127
166206
  async startSessionDispatchWatcher() {
@@ -166536,11 +166615,14 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
166536
166615
  let metaCustomAcp;
166537
166616
  let metaRuntimeOverrides;
166538
166617
  let metaAgentConfigId;
166618
+ let reusableTitlePromise;
166539
166619
  try {
166540
166620
  const sessionDoc = await this.workspaceDocument.getOrCreateSessionDoc(sessionId);
166541
166621
  const meta = await sessionDoc.getMetaState();
166542
166622
  metaBranchName = meta?.branchName?.trim() || null;
166543
166623
  metaAgentConfigId = meta?.agentConfigId;
166624
+ const generatedMetaTitle = meta?.titleSource === "generated" ? meta.title?.trim() : "";
166625
+ reusableTitlePromise = generatedMetaTitle ? Promise.resolve(generatedMetaTitle) : this.titleGenerationInFlight.get(sessionId);
166544
166626
  const agentConfig = metaAgentConfigId ? await this.workspaceDocument.getAgentConfigById(metaAgentConfigId) : null;
166545
166627
  const legacyLaunchConfig = await readLegacySessionLaunchConfig({
166546
166628
  repo: this.workspaceDocument.repo,
@@ -166559,7 +166641,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
166559
166641
  this.logger.debug(`[${sessionId}] Failed to read session meta before branch rename: ${formatErrorMessage(error2)}`);
166560
166642
  }
166561
166643
  const resolvedTitleConfig = titleConfig ?? await this.resolveTitleConfig(sessionId, metaAgentConfigId);
166562
- const branchName = await this.generateBranchNameWithTimeout(cliType, agentType, trimmedPrompt, env2, 2e4, resolvedTitleConfig, metaCustomAcp, metaRuntimeOverrides);
166644
+ const branchName = await this.generateBranchNameWithTimeout(cliType, agentType, trimmedPrompt, env2, 2e4, resolvedTitleConfig, metaCustomAcp, metaRuntimeOverrides, reusableTitlePromise);
166563
166645
  if (!branchName) {
166564
166646
  this.logger.debug(`[${sessionId}] Skipping branch rename: name generation timed out`);
166565
166647
  return;
@@ -166589,13 +166671,13 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
166589
166671
  this.logger.debug(`[${sessionId}] Failed to rename branch: ${formatErrorMessage(error2)}`);
166590
166672
  }
166591
166673
  }
166592
- async generateBranchNameWithTimeout(cliType, agentType, taskPrompt, env2, timeoutMs, titleConfig, customAcp, runtimeOverrides) {
166674
+ async generateBranchNameWithTimeout(cliType, agentType, taskPrompt, env2, timeoutMs, titleConfig, customAcp, runtimeOverrides, reusableTitlePromise) {
166593
166675
  let timeoutHandle = null;
166594
166676
  const timeoutPromise = new Promise((resolve2) => {
166595
166677
  timeoutHandle = setTimeout(() => resolve2(null), timeoutMs);
166596
166678
  });
166597
166679
  const namePromise = (async () => {
166598
- const title2 = await generateTitleIsolated({
166680
+ const title2 = reusableTitlePromise ? await reusableTitlePromise : await generateTitleIsolated({
166599
166681
  cliType,
166600
166682
  agentType,
166601
166683
  customAcp,
@@ -213634,7 +213716,7 @@ ${page}${helpTipBottom}${choiceDescription}${ansiEscapes.cursorHide}`;
213634
213716
  data = createReviewBundleSnapshot(bundle);
213635
213717
  }
213636
213718
  const { injectReviewSnapshot } = await import("./chunks/index-VoI6Ds2-.js");
213637
- const { resolveReviewViewerTemplate } = await import("./chunks/review-viewer-Bgp2Thsg.js").then(async (m) => {
213719
+ const { resolveReviewViewerTemplate } = await import("./chunks/review-viewer--d2HZGpO.js").then(async (m) => {
213638
213720
  await m.__tla;
213639
213721
  return m;
213640
213722
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lody",
3
- "version": "0.78.0",
3
+ "version": "0.79.0",
4
4
  "description": "Lody Agent CLI tool for managing remote command execution",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",