@letta-ai/letta-code 0.30.8 → 0.30.9

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.
package/letta.js CHANGED
@@ -3306,6 +3306,16 @@ function trackBoundaryError(options) {
3306
3306
  recentChunks: options.recentChunks
3307
3307
  });
3308
3308
  }
3309
+ function trackEndTurnNoAssistant(params) {
3310
+ telemetry.trackError("end_turn_no_assistant", `end_turn fell back to ${params.fallbackKind}`, "headless_result_extraction", {
3311
+ modelId: params.modelHandle,
3312
+ runId: params.runId,
3313
+ isSubagent: params.isSubagent,
3314
+ subagentType: params.subagentType,
3315
+ modelHandle: params.modelHandle,
3316
+ fallbackKind: params.fallbackKind
3317
+ });
3318
+ }
3309
3319
  var init_error_reporting = __esm(() => {
3310
3320
  init_telemetry();
3311
3321
  });
@@ -4084,6 +4094,10 @@ async function writeJsonFile(path2, data, options) {
4084
4094
  var init_fs = () => {};
4085
4095
 
4086
4096
  // src/utils/secrets.ts
4097
+ function scopeServiceName(name) {
4098
+ const testPrefix = process.env.LETTA_TEST_SECRETS_SERVICE_PREFIX?.trim();
4099
+ return testPrefix ? `${testPrefix}:${name}` : name;
4100
+ }
4087
4101
  function getErrorMessage(error) {
4088
4102
  return error instanceof Error ? error.message : String(error);
4089
4103
  }
@@ -4245,7 +4259,7 @@ async function isKeychainAvailable() {
4245
4259
  return false;
4246
4260
  }
4247
4261
  }
4248
- var secrets, secretsAvailable = false, SERVICE_NAME = "letta-code", API_KEY_NAME = "letta-api-key", REFRESH_TOKEN_NAME = "letta-refresh-token", warnedSecretReadFailures, secretGetOverrideForTests = null;
4262
+ var secrets, secretsAvailable = false, SERVICE_NAME, API_KEY_NAME = "letta-api-key", REFRESH_TOKEN_NAME = "letta-refresh-token", warnedSecretReadFailures, secretGetOverrideForTests = null;
4249
4263
  var init_secrets = __esm(() => {
4250
4264
  init_debug();
4251
4265
  try {
@@ -4254,6 +4268,7 @@ var init_secrets = __esm(() => {
4254
4268
  } catch {
4255
4269
  secretsAvailable = false;
4256
4270
  }
4271
+ SERVICE_NAME = scopeServiceName("letta-code");
4257
4272
  warnedSecretReadFailures = new Set;
4258
4273
  });
4259
4274
 
@@ -5461,7 +5476,7 @@ var package_default;
5461
5476
  var init_package = __esm(() => {
5462
5477
  package_default = {
5463
5478
  name: "@letta-ai/letta-code",
5464
- version: "0.30.8",
5479
+ version: "0.30.9",
5465
5480
  description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
5466
5481
  type: "module",
5467
5482
  packageManager: "bun@1.3.0",
@@ -6441,7 +6456,13 @@ class TelemetryManager {
6441
6456
  model_id: options?.modelId,
6442
6457
  run_id: options?.runId,
6443
6458
  recent_chunks: options?.recentChunks,
6444
- debug_log_tail: debugLogFile.getTail()
6459
+ debug_log_tail: debugLogFile.getTail(),
6460
+ is_subagent: options?.isSubagent,
6461
+ subagent_type: options?.subagentType,
6462
+ model_handle: options?.modelHandle,
6463
+ fallback_kind: options?.fallbackKind,
6464
+ platform: process.platform,
6465
+ version: getVersion()
6445
6466
  };
6446
6467
  this.track("error", data);
6447
6468
  }
@@ -154249,14 +154270,29 @@ function spawnWithLauncher(launcher, options3) {
154249
154270
  stdio: ["ignore", "pipe", "pipe"],
154250
154271
  detached: process.platform !== "win32"
154251
154272
  });
154252
- const killProcessGroup = (signal) => {
154273
+ const killProcessTree = (signal) => {
154253
154274
  if (childProcess.pid) {
154275
+ if (process.platform === "win32") {
154276
+ const taskkill = spawn2("taskkill.exe", ["/pid", String(childProcess.pid), "/t", "/f"], {
154277
+ stdio: "ignore",
154278
+ windowsHide: true
154279
+ });
154280
+ taskkill.once("error", () => {
154281
+ try {
154282
+ childProcess.kill("SIGKILL");
154283
+ } catch {}
154284
+ });
154285
+ taskkill.once("close", (code2) => {
154286
+ if (code2 === 0)
154287
+ return;
154288
+ try {
154289
+ childProcess.kill("SIGKILL");
154290
+ } catch {}
154291
+ });
154292
+ return;
154293
+ }
154254
154294
  try {
154255
- if (process.platform !== "win32") {
154256
- process.kill(-childProcess.pid, signal);
154257
- } else {
154258
- childProcess.kill(signal);
154259
- }
154295
+ process.kill(-childProcess.pid, signal);
154260
154296
  } catch {
154261
154297
  try {
154262
154298
  childProcess.kill(signal);
@@ -154268,20 +154304,28 @@ function spawnWithLauncher(launcher, options3) {
154268
154304
  const stderrChunks = [];
154269
154305
  let timedOut = false;
154270
154306
  let killTimer = null;
154271
- const timeoutId = options3.timeoutMs ? setTimeout(() => {
154272
- timedOut = true;
154273
- killProcessGroup("SIGTERM");
154274
- }, options3.timeoutMs) : null;
154275
- const abortHandler = () => {
154276
- killProcessGroup("SIGTERM");
154307
+ let completed = false;
154308
+ const terminateProcess = () => {
154309
+ if (process.platform === "win32") {
154310
+ killProcessTree("SIGKILL");
154311
+ return;
154312
+ }
154313
+ killProcessTree("SIGTERM");
154277
154314
  if (!killTimer) {
154278
154315
  killTimer = setTimeout(() => {
154279
- if (childProcess.exitCode === null && !childProcess.killed) {
154280
- killProcessGroup("SIGKILL");
154316
+ if (!completed) {
154317
+ killProcessTree("SIGKILL");
154281
154318
  }
154282
- }, ABORT_KILL_TIMEOUT_MS);
154319
+ }, FORCE_KILL_GRACE_MS);
154283
154320
  }
154284
154321
  };
154322
+ const timeoutId = options3.timeoutMs ? setTimeout(() => {
154323
+ timedOut = true;
154324
+ terminateProcess();
154325
+ }, options3.timeoutMs) : null;
154326
+ const abortHandler = () => {
154327
+ terminateProcess();
154328
+ };
154285
154329
  if (options3.signal) {
154286
154330
  options3.signal.addEventListener("abort", abortHandler, { once: true });
154287
154331
  }
@@ -154294,6 +154338,7 @@ function spawnWithLauncher(launcher, options3) {
154294
154338
  options3.onOutput?.(chunk.toString("utf8"), "stderr");
154295
154339
  });
154296
154340
  childProcess.on("error", (err) => {
154341
+ completed = true;
154297
154342
  if (timeoutId)
154298
154343
  clearTimeout(timeoutId);
154299
154344
  if (killTimer) {
@@ -154306,6 +154351,7 @@ function spawnWithLauncher(launcher, options3) {
154306
154351
  reject(buildSpawnError(err, executable, options3.cwd));
154307
154352
  });
154308
154353
  childProcess.on("close", (code2) => {
154354
+ completed = true;
154309
154355
  if (timeoutId)
154310
154356
  clearTimeout(timeoutId);
154311
154357
  if (killTimer) {
@@ -154340,7 +154386,7 @@ function spawnWithLauncher(launcher, options3) {
154340
154386
  });
154341
154387
  });
154342
154388
  }
154343
- var ShellExecutionError, ABORT_KILL_TIMEOUT_MS = 2000;
154389
+ var ShellExecutionError, FORCE_KILL_GRACE_MS = 2000;
154344
154390
  var init_shell_runner = __esm(() => {
154345
154391
  init_usable_directory();
154346
154392
  init_worktree_ownership();
@@ -157771,9 +157817,6 @@ async function sendMessageStreamWithBackend(backend, conversationId, messages, o
157771
157817
  }
157772
157818
  }
157773
157819
  const extraHeaders = {};
157774
- if (process.env.LETTA_RESPONSES_WS === "1") {
157775
- extraHeaders["X-Experimental-OpenAI-Responses-Websocket"] = "true";
157776
- }
157777
157820
  if (previousResponseId) {
157778
157821
  extraHeaders[RESPONSE_STATE_HEADER] = encodeResponseStateHeader({
157779
157822
  v: 1,
@@ -328456,7 +328499,7 @@ function createNoopModPanelHandle() {
328456
328499
  update() {}
328457
328500
  };
328458
328501
  }
328459
- function createLettaModApi(registry2, owner, capabilities, getClient2, onChange, onDiagnostic, builtinCommandIds, reservedToolNames, signal) {
328502
+ function createLettaModApi(registry2, owner, capabilities, getClient2, onChange, onDiagnostic, onNotification, builtinCommandIds, reservedToolNames, signal) {
328460
328503
  const isLive = () => isOwnerLive(registry2, owner);
328461
328504
  const guardLive = (capability) => {
328462
328505
  if (isLive())
@@ -328766,11 +328809,16 @@ function createLettaModApi(registry2, owner, capabilities, getClient2, onChange,
328766
328809
  },
328767
328810
  unregister: unregisterPermission
328768
328811
  },
328769
- diagnostics: {
328770
- report: reportDiagnostic
328771
- },
328812
+ diagnostics: { report: reportDiagnostic },
328772
328813
  ui: {
328773
328814
  closePanel,
328815
+ notify(message) {
328816
+ if (!capabilities.ui.panels || !message.trim())
328817
+ return;
328818
+ if (!guardLive({ id: "notify", kind: "panel" }))
328819
+ return;
328820
+ onNotification?.(message.trim());
328821
+ },
328774
328822
  openPanel(panel) {
328775
328823
  if (!capabilities.ui.panels) {
328776
328824
  return createNoopModPanelHandle();
@@ -328893,7 +328941,7 @@ async function loadLocalMods(options3) {
328893
328941
  if (typeof factory !== "function") {
328894
328942
  throw new Error("Mod must export a default function or activate() function");
328895
328943
  }
328896
- const dispose = await factory(createLettaModApi(registry2, owner, capabilities, getConfiguredClient, onChange, options3.onDiagnostic, builtinCommandIds, reservedToolNames, abortController.signal));
328944
+ const dispose = await factory(createLettaModApi(registry2, owner, capabilities, getConfiguredClient, onChange, options3.onDiagnostic, options3.onNotification, builtinCommandIds, reservedToolNames, abortController.signal));
328897
328945
  if (typeof dispose === "function") {
328898
328946
  registry2.disposers.push({
328899
328947
  abortController,
@@ -356516,6 +356564,9 @@ function updateLocalConversationRecord(current, body, updatedAt) {
356516
356564
  if (typeof bodyRecord.summary === "string" || bodyRecord.summary === null) {
356517
356565
  next.summary = bodyRecord.summary;
356518
356566
  }
356567
+ if (isStringArray2(bodyRecord.tags)) {
356568
+ next.tags = bodyRecord.tags;
356569
+ }
356519
356570
  return next;
356520
356571
  }
356521
356572
  function normalizeAgentRecord(value, defaultAgentModel) {
@@ -412081,35 +412132,35 @@ function buildSlackConversationSummary(msg) {
412081
412132
  if (msg.chatType === "direct") {
412082
412133
  if (msg.threadId?.trim()) {
412083
412134
  const preview2 = truncateChannelSummaryPreview(msg.text);
412084
- return preview2 ? `[Slack] DM thread with ${msg.senderName?.trim() || msg.senderId}: ${preview2}` : `[Slack] DM thread with ${msg.senderName?.trim() || msg.senderId}`;
412135
+ return preview2 ? `DM thread with ${msg.senderName?.trim() || msg.senderId}: ${preview2}` : `DM thread with ${msg.senderName?.trim() || msg.senderId}`;
412085
412136
  }
412086
- return `[Slack] DM with ${msg.senderName?.trim() || msg.senderId}`;
412137
+ return `DM with ${msg.senderName?.trim() || msg.senderId}`;
412087
412138
  }
412088
412139
  const preview = truncateChannelSummaryPreview(msg.text);
412089
412140
  const channelLabel = msg.chatLabel && msg.chatLabel !== msg.chatId ? ` in ${msg.chatLabel}` : "";
412090
412141
  if (preview)
412091
- return `[Slack] Thread${channelLabel}: ${preview}`;
412092
- return `[Slack] Thread${channelLabel || ` ${msg.chatId}`}`;
412142
+ return `Thread${channelLabel}: ${preview}`;
412143
+ return `Thread${channelLabel || ` ${msg.chatId}`}`;
412093
412144
  }
412094
412145
  function buildDiscordConversationSummary(msg) {
412095
412146
  if (msg.chatType === "direct") {
412096
- return `[Discord] DM with ${msg.senderName?.trim() || msg.senderId}`;
412147
+ return `DM with ${msg.senderName?.trim() || msg.senderId}`;
412097
412148
  }
412098
412149
  const preview = truncateChannelSummaryPreview(msg.text);
412099
412150
  const channelLabel = msg.chatLabel && msg.chatLabel !== msg.chatId ? ` in ${msg.chatLabel}` : "";
412100
412151
  if (preview)
412101
- return `[Discord] Thread${channelLabel}: ${preview}`;
412102
- return `[Discord] Thread${channelLabel || ` ${msg.chatId}`}`;
412152
+ return `Thread${channelLabel}: ${preview}`;
412153
+ return `Thread${channelLabel || ` ${msg.chatId}`}`;
412103
412154
  }
412104
412155
  function buildTelegramConversationSummary(msg) {
412105
412156
  if (msg.chatType === "direct") {
412106
- return `[Telegram] DM with ${msg.senderName?.trim() || msg.senderId}`;
412157
+ return `DM with ${msg.senderName?.trim() || msg.senderId}`;
412107
412158
  }
412108
412159
  const preview = truncateChannelSummaryPreview(msg.text);
412109
412160
  const channelLabel = msg.chatLabel && msg.chatLabel !== msg.chatId ? ` in ${msg.chatLabel}` : "";
412110
412161
  if (preview)
412111
- return `[Telegram] Topic${channelLabel}: ${preview}`;
412112
- return `[Telegram] Topic${channelLabel || ` ${msg.chatId}`}`;
412162
+ return `Topic${channelLabel}: ${preview}`;
412163
+ return `Topic${channelLabel || ` ${msg.chatId}`}`;
412113
412164
  }
412114
412165
  function buildWhatsAppConversationSummary(msg) {
412115
412166
  if (msg.chatType === "direct") {
@@ -447354,7 +447405,7 @@ function isRuntimeStartCommand(value) {
447354
447405
  if (!value || typeof value !== "object")
447355
447406
  return false;
447356
447407
  const c = value;
447357
- return c.type === "runtime_start" && typeof c.request_id === "string" && (c.agent_id === undefined || typeof c.agent_id === "string") && (c.create_agent === undefined || isRuntimeStartCreateAgentOptions(c.create_agent)) && (c.conversation_id === undefined || typeof c.conversation_id === "string") && (c.create_conversation === undefined || isRuntimeStartCreateConversationOptions(c.create_conversation)) && (c.cwd === undefined || c.cwd === null || typeof c.cwd === "string") && (c.mode === undefined || isDevicePermissionMode(c.mode)) && (c.skill_sources === undefined || isSkillSourceArray(c.skill_sources)) && (c.preserve_skill_sources === undefined || typeof c.preserve_skill_sources === "boolean") && (c.client_info === undefined || isRuntimeStartClientInfo(c.client_info)) && (c.recover_approvals === undefined || typeof c.recover_approvals === "boolean") && (c.force_device_status === undefined || typeof c.force_device_status === "boolean") && (c.wait_for_replay === undefined || typeof c.wait_for_replay === "boolean") && (c.external_tools === undefined || Array.isArray(c.external_tools) && c.external_tools.every(isRuntimeStartExternalToolsGroup));
447408
+ return c.type === "runtime_start" && typeof c.request_id === "string" && (c.agent_id === undefined || typeof c.agent_id === "string") && (c.create_agent === undefined || isRuntimeStartCreateAgentOptions(c.create_agent)) && (c.conversation_id === undefined || typeof c.conversation_id === "string") && (c.create_conversation === undefined || isRuntimeStartCreateConversationOptions(c.create_conversation)) && (c.conversation_source_tags === undefined || isStringArray7(c.conversation_source_tags)) && (c.cwd === undefined || c.cwd === null || typeof c.cwd === "string") && (c.mode === undefined || isDevicePermissionMode(c.mode)) && (c.skill_sources === undefined || isSkillSourceArray(c.skill_sources)) && (c.preserve_skill_sources === undefined || typeof c.preserve_skill_sources === "boolean") && (c.client_info === undefined || isRuntimeStartClientInfo(c.client_info)) && (c.recover_approvals === undefined || typeof c.recover_approvals === "boolean") && (c.force_device_status === undefined || typeof c.force_device_status === "boolean") && (c.wait_for_replay === undefined || typeof c.wait_for_replay === "boolean") && (c.external_tools === undefined || Array.isArray(c.external_tools) && c.external_tools.every(isRuntimeStartExternalToolsGroup));
447358
447409
  }
447359
447410
  function isTerminalSpawnCommand(value) {
447360
447411
  if (!value || typeof value !== "object")
@@ -450433,6 +450484,34 @@ async function resolveRuntimeStartConversation(parsed, agent2, created) {
450433
450484
  created.conversation = true;
450434
450485
  return conversation;
450435
450486
  }
450487
+ function removeMatchingSourcePrefix(summary, sourceTags) {
450488
+ if (typeof summary !== "string")
450489
+ return summary;
450490
+ const match4 = summary.match(/^\s*\[([^\]]+)\]\s*/);
450491
+ if (!match4)
450492
+ return summary;
450493
+ const prefix = match4[1]?.trim().toLowerCase();
450494
+ const matchesSourceTag = sourceTags.some((tag) => LEGACY_SUMMARY_PREFIX_BY_SOURCE_TAG[tag] === prefix);
450495
+ return matchesSourceTag ? summary.slice(match4[0].length) : summary;
450496
+ }
450497
+ async function applyRuntimeStartConversationSourceTags(parsed, conversation) {
450498
+ const sourceTags = parsed.conversation_source_tags;
450499
+ if (conversation.id === "default" || !sourceTags?.length) {
450500
+ return conversation;
450501
+ }
450502
+ const currentTags = Reflect.get(conversation, "tags");
450503
+ const existingTags = Array.isArray(currentTags) ? currentTags.filter((tag) => typeof tag === "string") : [];
450504
+ const missingTags = sourceTags.filter((tag) => !existingTags.includes(tag));
450505
+ const summary = removeMatchingSourcePrefix(conversation.summary, sourceTags);
450506
+ const summaryChanged = summary !== conversation.summary;
450507
+ if (missingTags.length === 0 && !summaryChanged) {
450508
+ return conversation;
450509
+ }
450510
+ return getBackend().updateConversation(conversation.id, {
450511
+ ...missingTags.length > 0 ? { tags: [...new Set([...existingTags, ...missingTags])] } : {},
450512
+ ...summaryChanged ? { summary } : {}
450513
+ });
450514
+ }
450436
450515
  async function applyRuntimeStartState(parsed, context3, scope, scopedRuntime) {
450437
450516
  if (parsed.skill_sources === undefined && parsed.preserve_skill_sources !== true) {
450438
450517
  scopedRuntime.skillSources = undefined;
@@ -450473,6 +450552,7 @@ async function handleRuntimeStartCommand(parsed, context3) {
450473
450552
  validateRuntimeStartShape(parsed);
450474
450553
  agent2 = await resolveRuntimeStartAgent(parsed, created);
450475
450554
  conversation = await resolveRuntimeStartConversation(parsed, agent2, created);
450555
+ conversation = await applyRuntimeStartConversationSourceTags(parsed, conversation);
450476
450556
  runtimeScope = buildRuntimeScope(agent2, conversation);
450477
450557
  const { connectionId } = context3;
450478
450558
  const assertConnectionOpen = () => {
@@ -450527,6 +450607,7 @@ function handleRuntimeStartProtocolCommand(parsed, context3) {
450527
450607
  });
450528
450608
  return true;
450529
450609
  }
450610
+ var LEGACY_SUMMARY_PREFIX_BY_SOURCE_TAG;
450530
450611
  var init_runtime_start = __esm(async () => {
450531
450612
  init_create5();
450532
450613
  init_create_agent_request();
@@ -450541,6 +450622,12 @@ var init_runtime_start = __esm(async () => {
450541
450622
  init_external_tools(),
450542
450623
  init_protocol_inbound()
450543
450624
  ]);
450625
+ LEGACY_SUMMARY_PREFIX_BY_SOURCE_TAG = {
450626
+ "channel:discord": "discord",
450627
+ "channel:slack": "slack",
450628
+ "channel:telegram": "telegram",
450629
+ "origin:schedule": "schedule"
450630
+ };
450544
450631
  });
450545
450632
 
450546
450633
  // src/websocket/listener/commands/settings.ts
@@ -451373,10 +451460,10 @@ function getRecoverableStatusNoticeVisibility(kind) {
451373
451460
  return "transcript";
451374
451461
  }
451375
451462
  }
451376
- function getRecoverableRetryNoticeVisibility(kind, attempt) {
451463
+ function getRecoverableRetryNoticeVisibility(kind) {
451377
451464
  switch (kind) {
451378
451465
  case "transient_provider_retry":
451379
- return attempt === 1 ? "debug_only" : "transcript";
451466
+ return "transcript";
451380
451467
  default:
451381
451468
  return "transcript";
451382
451469
  }
@@ -451526,7 +451613,7 @@ function emitRecoverableStatusNotice(socket, runtime, params) {
451526
451613
  });
451527
451614
  }
451528
451615
  function emitRecoverableRetryNotice(socket, runtime, params) {
451529
- const visibility = getRecoverableRetryNoticeVisibility(params.kind, params.attempt);
451616
+ const visibility = getRecoverableRetryNoticeVisibility(params.kind);
451530
451617
  if (visibility === "debug_only") {
451531
451618
  debugLog("recovery", `Debug-only retry notice (${params.kind}, attempt ${params.attempt}/${params.maxAttempts}): ${params.message}`);
451532
451619
  mirrorRecoverableNoticeToDesktopDebugPanel(params.message);
@@ -453218,6 +453305,50 @@ var init_stream_resume = __esm(() => {
453218
453305
  init_client2();
453219
453306
  });
453220
453307
 
453308
+ // src/cli/helpers/stream-terminal-eof-guard.ts
453309
+ function getTerminalEofGraceMs() {
453310
+ const raw2 = process.env.LETTA_STREAM_TERMINAL_EOF_GRACE_MS;
453311
+ if (raw2) {
453312
+ const parsed = Number(raw2);
453313
+ if (Number.isFinite(parsed) && parsed > 0) {
453314
+ return parsed;
453315
+ }
453316
+ }
453317
+ return DEFAULT_TERMINAL_EOF_GRACE_MS;
453318
+ }
453319
+ function createTerminalEofGuard(context3) {
453320
+ let timer = null;
453321
+ let fired = false;
453322
+ return {
453323
+ arm: () => {
453324
+ if (timer) {
453325
+ clearTimeout(timer);
453326
+ }
453327
+ const graceMs = getTerminalEofGraceMs();
453328
+ timer = setTimeout(() => {
453329
+ fired = true;
453330
+ debugWarn("drainStream", "Terminal-EOF guard fired: stop_reason=%s received but stream did not end within %dms - aborting HTTP read", context3.getStopReason(), graceMs);
453331
+ telemetry.trackError("stream_terminal_eof_guard_fired", `Stream received stop_reason=${context3.getStopReason()} but HTTP body did not end within ${graceMs}ms`, "stream_drain", {
453332
+ runId: context3.getRunId() ?? undefined
453333
+ });
453334
+ context3.abortHttpRead();
453335
+ }, graceMs);
453336
+ },
453337
+ clear: () => {
453338
+ if (timer) {
453339
+ clearTimeout(timer);
453340
+ timer = null;
453341
+ }
453342
+ },
453343
+ fired: () => fired
453344
+ };
453345
+ }
453346
+ var DEFAULT_TERMINAL_EOF_GRACE_MS = 2000;
453347
+ var init_stream_terminal_eof_guard = __esm(() => {
453348
+ init_telemetry();
453349
+ init_debug();
453350
+ });
453351
+
453221
453352
  // src/cli/helpers/stream.ts
453222
453353
  function summarizeStreamForDebug(stream12) {
453223
453354
  if (!stream12 || typeof stream12 !== "object") {
@@ -453283,6 +453414,11 @@ async function drainStream(stream12, buffers, refresh, abortSignal, onFirstMessa
453283
453414
  let fallbackError = null;
453284
453415
  let lastChunkDebugSummary = "none";
453285
453416
  let abortedViaListener = false;
453417
+ const terminalEofGuard = createTerminalEofGuard({
453418
+ getStopReason: () => streamProcessor.stopReason,
453419
+ getRunId: () => streamProcessor.lastRunId,
453420
+ abortHttpRead: () => abortStreamController(stream12, "terminal_eof_guard")
453421
+ });
453286
453422
  const startAbortGen = buffers.abortGeneration || 0;
453287
453423
  const abortHandler = () => {
453288
453424
  abortedViaListener = true;
@@ -453323,6 +453459,9 @@ async function drainStream(stream12, buffers, refresh, abortSignal, onFirstMessa
453323
453459
  logTiming(`TTFT: ${formatDuration(ttft)} (from POST to first content)`);
453324
453460
  }
453325
453461
  const { shouldOutput, errorInfo, updatedApproval } = streamProcessor.processChunk(chunk);
453462
+ if (streamProcessor.stopReason !== null) {
453463
+ terminalEofGuard.arm();
453464
+ }
453326
453465
  try {
453327
453466
  chunkLog.append(chunk);
453328
453467
  } catch {}
@@ -453397,6 +453536,7 @@ async function drainStream(stream12, buffers, refresh, abortSignal, onFirstMessa
453397
453536
  }
453398
453537
  queueMicrotask(refresh);
453399
453538
  } finally {
453539
+ terminalEofGuard.clear();
453400
453540
  try {
453401
453541
  chunkLog.flush();
453402
453542
  } catch {}
@@ -453409,6 +453549,11 @@ async function drainStream(stream12, buffers, refresh, abortSignal, onFirstMessa
453409
453549
  if (!stopReason && streamProcessor.stopReason) {
453410
453550
  stopReason = streamProcessor.stopReason;
453411
453551
  }
453552
+ if (terminalEofGuard.fired()) {
453553
+ upsertStatusLine(buffers, `terminal-eof-${startTime}`, [
453554
+ "Stream did not close after completing, continued without waiting"
453555
+ ]);
453556
+ }
453412
453557
  if (abortedViaListener && !stopReason) {
453413
453558
  stopReason = "cancelled";
453414
453559
  markIncompleteToolsAsCancelled(buffers, true, "user_interrupt");
@@ -453462,7 +453607,8 @@ async function drainStream(stream12, buffers, refresh, abortSignal, onFirstMessa
453462
453607
  lastRunId: streamProcessor.lastRunId,
453463
453608
  lastSeqId: streamProcessor.lastSeqId,
453464
453609
  apiDurationMs,
453465
- fallbackError
453610
+ fallbackError,
453611
+ terminalEofGuardFired: terminalEofGuard.fired()
453466
453612
  };
453467
453613
  }
453468
453614
  async function drainStreamWithResume(stream12, buffers, refresh, abortSignal, onFirstMessage, onChunkProcessed, contextTracker, seenSeqIdThreshold, resumePolicy) {
@@ -453655,6 +453801,7 @@ var init_stream = __esm(async () => {
453655
453801
  init_tui_perf();
453656
453802
  init_chunk_log();
453657
453803
  init_stream_resume();
453804
+ init_stream_terminal_eof_guard();
453658
453805
  await __promiseAll([
453659
453806
  init_message(),
453660
453807
  init_accumulator()
@@ -453835,6 +453982,62 @@ var init_approval_suggestions = __esm(async () => {
453835
453982
  ]);
453836
453983
  });
453837
453984
 
453985
+ // src/websocket/listener/cloud-retry-message.ts
453986
+ function isRecord12(value) {
453987
+ return typeof value === "object" && value !== null && !Array.isArray(value);
453988
+ }
453989
+ function optionalString4(value) {
453990
+ return typeof value === "string" && value.length > 0 ? value : null;
453991
+ }
453992
+ function parseCloudRetryMessage(value) {
453993
+ if (!isRecord12(value) || value.message_type !== "retry_message") {
453994
+ return null;
453995
+ }
453996
+ const attempt = value.attempt;
453997
+ const maxAttempts = value.max_attempts;
453998
+ const delayMs = value.delay_ms;
453999
+ const retryKind = value.retry_kind;
454000
+ if (typeof value.message !== "string" || value.message.length === 0 || retryKind !== "provider_retry" && retryKind !== "transport_fallback" || typeof attempt !== "number" || !Number.isInteger(attempt) || attempt < 1 || typeof maxAttempts !== "number" || !Number.isInteger(maxAttempts) || maxAttempts < attempt || typeof delayMs !== "number" || !Number.isFinite(delayMs) || delayMs < 0 || typeof value.provider !== "string" || value.provider.length === 0) {
454001
+ return null;
454002
+ }
454003
+ return {
454004
+ message: value.message,
454005
+ retryKind,
454006
+ attempt,
454007
+ maxAttempts,
454008
+ delayMs,
454009
+ provider: value.provider,
454010
+ fromTransport: optionalString4(value.from_transport),
454011
+ toTransport: optionalString4(value.to_transport),
454012
+ errorCode: optionalString4(value.error_code),
454013
+ runId: optionalString4(value.run_id),
454014
+ stepId: optionalString4(value.step_id)
454015
+ };
454016
+ }
454017
+ function normalizeCloudRetryWireMessage(value) {
454018
+ const retry3 = parseCloudRetryMessage(value);
454019
+ if (!retry3) {
454020
+ return null;
454021
+ }
454022
+ return {
454023
+ ...createLifecycleMessageBase("retry", retry3.runId),
454024
+ message: retry3.message,
454025
+ reason: "llm_api_error",
454026
+ attempt: retry3.attempt,
454027
+ max_attempts: retry3.maxAttempts,
454028
+ delay_ms: retry3.delayMs,
454029
+ retry_kind: retry3.retryKind,
454030
+ provider: retry3.provider,
454031
+ from_transport: retry3.fromTransport,
454032
+ to_transport: retry3.toTransport,
454033
+ error_code: retry3.errorCode,
454034
+ step_id: retry3.stepId
454035
+ };
454036
+ }
454037
+ var init_cloud_retry_message = __esm(async () => {
454038
+ await init_protocol_outbound();
454039
+ });
454040
+
453838
454041
  // src/websocket/listener/turn-input-state.ts
453839
454042
  function ensureTurnInputMessageOtids(messages) {
453840
454043
  let didChange = false;
@@ -454111,7 +454314,7 @@ async function drainRecoveryStreamWithEmission(recoveryStream, socket, runtime,
454111
454314
  });
454112
454315
  }
454113
454316
  if (shouldOutput) {
454114
- const normalizedChunk = normalizeToolReturnWireMessage(chunk);
454317
+ const normalizedChunk = normalizeCloudRetryWireMessage(chunk) ?? normalizeToolReturnWireMessage(chunk);
454115
454318
  if (normalizedChunk) {
454116
454319
  emitCanonicalMessageDelta(socket, runtime, {
454117
454320
  ...normalizedChunk,
@@ -454558,6 +454761,7 @@ var init_recovery = __esm(async () => {
454558
454761
  init_stream(),
454559
454762
  init_toolset(),
454560
454763
  init_approval_suggestions(),
454764
+ init_cloud_retry_message(),
454561
454765
  init_continuation_input(),
454562
454766
  init_interrupts(),
454563
454767
  init_mod_adapter2(),
@@ -454610,16 +454814,17 @@ var init_approval_recovery = __esm(() => {
454610
454814
  });
454611
454815
 
454612
454816
  // src/websocket/listener/provider-fallback.ts
454613
- function createProviderFallbackState(agent2) {
454817
+ function createProviderFallbackState(agent2, overrideModel) {
454614
454818
  const llmConfig = agent2?.llm_config;
454615
454819
  const model = llmConfig?.model;
454616
454820
  if (!model) {
454617
- return { sourceModelId: null, attempted: false };
454821
+ return { sourceModelId: null, attempted: false, overrideModel };
454618
454822
  }
454619
454823
  const modelInfo = getModelInfoForLlmConfig(model, llmConfig) ?? getModelInfo(model);
454620
454824
  return {
454621
454825
  sourceModelId: modelInfo?.id ?? model,
454622
- attempted: false
454826
+ attempted: false,
454827
+ overrideModel
454623
454828
  };
454624
454829
  }
454625
454830
  function maybeApplyProviderFallback(state, attempt) {
@@ -458040,17 +458245,18 @@ async function emitListenerTurnStart(options3) {
458040
458245
  conversationId: options3.conversationId,
458041
458246
  input: options3.input
458042
458247
  };
458043
- await createListenerModEvents(modAdapters).emit("turn_start", event2, context3);
458248
+ const emission = await createListenerModEvents(modAdapters).emit("turn_start", event2, context3);
458044
458249
  const cancel = getTurnStartCancel(event2);
458045
458250
  if (cancel) {
458046
458251
  return { cancelled: true, reason: cancel.reason };
458047
458252
  }
458048
458253
  return {
458049
458254
  cancelled: false,
458255
+ handlerCount: emission.handlerCount,
458050
458256
  input: isTurnInputArray(event2.input) ? event2.input : options3.input
458051
458257
  };
458052
458258
  } catch {
458053
- return { cancelled: false, input: options3.input };
458259
+ return { cancelled: false, handlerCount: 0, input: options3.input };
458054
458260
  }
458055
458261
  }
458056
458262
  async function emitListenerTurnEnd(options3) {
@@ -459137,13 +459343,20 @@ async function prepareListenerTurn(params) {
459137
459343
  workingDirectory,
459138
459344
  permissionMode: permissionModeState.mode,
459139
459345
  cachedAgent
459140
- }) : { cancelled: false, input: messagesToSend };
459346
+ }) : { cancelled: false, handlerCount: 0, input: messagesToSend };
459141
459347
  if (isInterrupted()) {
459142
459348
  return { kind: "interrupted" };
459143
459349
  }
459144
459350
  if (turnStartEmission.cancelled) {
459145
459351
  return { kind: "cancelled", reason: turnStartEmission.reason };
459146
459352
  }
459353
+ let overrideModel;
459354
+ if (turnStartEmission.handlerCount > 0) {
459355
+ try {
459356
+ const conversation = await getBackend().retrieveConversation(conversationId);
459357
+ overrideModel = conversation.model ?? undefined;
459358
+ } catch {}
459359
+ }
459147
459360
  const currentInput = ensureTurnInputMessageOtids(turnStartEmission.input);
459148
459361
  const turnInput = createTurnInputState(currentInput, getInboundImageFailureModes({
459149
459362
  imageFailureMode: msg.imageFailureMode,
@@ -459185,7 +459398,8 @@ async function prepareListenerTurn(params) {
459185
459398
  pendingNormalizationInterruptedToolCallIds: [
459186
459399
  ...queuedInterruptedToolCallIds
459187
459400
  ],
459188
- preparedToolContext
459401
+ preparedToolContext,
459402
+ ...overrideModel ? { overrideModel } : {}
459189
459403
  };
459190
459404
  }
459191
459405
  var init_turn_setup = __esm(async () => {
@@ -459338,7 +459552,7 @@ async function handleIncomingMessageInner(msg, socket, runtime, onStatusChange,
459338
459552
  }
459339
459553
  let turnInput = setup.turnInput;
459340
459554
  const inboundUserTranscriptLines = setup.inboundUserTranscriptLines;
459341
- const providerFallback = createProviderFallbackState(setup.getCachedAgent());
459555
+ const providerFallback = createProviderFallbackState(setup.getCachedAgent(), setup.overrideModel);
459342
459556
  let pendingNormalizationInterruptedToolCallIds = setup.pendingNormalizationInterruptedToolCallIds;
459343
459557
  const preparedToolContext = setup.preparedToolContext;
459344
459558
  const buildSendOptions = () => ({
@@ -459430,7 +459644,7 @@ async function handleIncomingMessageInner(msg, socket, runtime, onStatusChange,
459430
459644
  }
459431
459645
  }
459432
459646
  if (shouldOutput) {
459433
- const normalizedChunk = normalizeToolReturnWireMessage(chunk);
459647
+ const normalizedChunk = normalizeCloudRetryWireMessage(chunk) ?? normalizeToolReturnWireMessage(chunk);
459434
459648
  if (normalizedChunk) {
459435
459649
  emitCanonicalMessageDelta(socket, runtime, {
459436
459650
  ...normalizedChunk,
@@ -459446,8 +459660,14 @@ async function handleIncomingMessageInner(msg, socket, runtime, onStatusChange,
459446
459660
  const stopReason = result.stopReason;
459447
459661
  const approvals = result.approvals || [];
459448
459662
  const fallbackError = result.fallbackError ?? null;
459449
- if (finishIfInterrupted(runId || runtime.activeRunId)) {
459450
- break;
459663
+ if (result.terminalEofGuardFired) {
459664
+ emitStatusDelta(socket, runtime, {
459665
+ message: "Stream did not close after completing, continued without waiting",
459666
+ level: "warning",
459667
+ runId: runId || runtime.activeRunId,
459668
+ agentId,
459669
+ conversationId
459670
+ });
459451
459671
  }
459452
459672
  if (finishIfInterrupted(runId || runtime.activeRunId)) {
459453
459673
  break;
@@ -459900,6 +460120,7 @@ var init_turn = __esm(async () => {
459900
460120
  init_message(),
459901
460121
  init_accumulator(),
459902
460122
  init_stream(),
460123
+ init_cloud_retry_message(),
459903
460124
  init_interrupts(),
459904
460125
  init_protocol_outbound(),
459905
460126
  init_recoverable_notices(),
@@ -461503,7 +461724,18 @@ function createMissedPongWatchdog(maxUnansweredPings) {
461503
461724
  }
461504
461725
  };
461505
461726
  }
461506
- function startConnectionHeartbeat(runtime, transport, onStale, sendPing) {
461727
+ function getCurrentStreamTransport(runtime, controlTransport) {
461728
+ for (const connection of runtime.connections.values()) {
461729
+ if (connection.writer !== controlTransport)
461730
+ continue;
461731
+ const streamTransport = connection.streamWriter;
461732
+ if (streamTransport && streamTransport !== controlTransport) {
461733
+ return streamTransport;
461734
+ }
461735
+ }
461736
+ return null;
461737
+ }
461738
+ function startConnectionHeartbeat(runtime, transport, onStale, sendPing, options3 = {}) {
461507
461739
  runtime.lastPongAt = Date.now();
461508
461740
  const maxUnansweredPings = Math.max(1, Math.ceil(LISTENER_PONG_TIMEOUT_MS / LISTENER_HEARTBEAT_INTERVAL_MS));
461509
461741
  const watchdog = createMissedPongWatchdog(maxUnansweredPings);
@@ -461513,10 +461745,14 @@ function startConnectionHeartbeat(runtime, transport, onStale, sendPing) {
461513
461745
  return;
461514
461746
  }
461515
461747
  const sentAt = Date.now();
461516
- if (sendPing()) {
461748
+ if (sendPing(transport)) {
461517
461749
  watchdog.recordPing(sentAt);
461518
461750
  }
461519
- }, LISTENER_HEARTBEAT_INTERVAL_MS);
461751
+ const streamTransport = getCurrentStreamTransport(runtime, transport);
461752
+ if (streamTransport) {
461753
+ sendPing(streamTransport);
461754
+ }
461755
+ }, options3.intervalMs ?? LISTENER_HEARTBEAT_INTERVAL_MS);
461520
461756
  }
461521
461757
  var init_heartbeat = __esm(() => {
461522
461758
  init_constants3();
@@ -463963,7 +464199,7 @@ function dispatchInboundMessageWhenReady(params) {
463963
464199
  emitListenerStatus(listener, options3.onStatusChange, options3.connectionId);
463964
464200
  rememberAcceptedInputDisposition(runtime, clientMessageId, "started");
463965
464201
  acknowledgeInput({ accepted: true, disposition: "started" });
463966
- await processIncomingMessage(incoming, socket, runtime, options3.onStatusChange, options3.connectionId);
464202
+ await processIncomingMessage(incoming, getOrCreateProcessTransport(listener), runtime, options3.onStatusChange, options3.connectionId);
463967
464203
  emitListenerStatus(listener, options3.onStatusChange, options3.connectionId);
463968
464204
  if (runtime.queueRuntime.length > 0 || runtime.queuePumpScheduled || runtime.queuePumpActive) {
463969
464205
  scheduleQueuePump(runtime, socket, options3, processQueuedTurn);
@@ -463980,12 +464216,13 @@ function dispatchInboundMessageWhenReady(params) {
463980
464216
  }
463981
464217
  var MAX_ACCEPTED_INPUT_DISPOSITIONS = 4096;
463982
464218
  var init_inbound_dispatch = __esm(async () => {
464219
+ init_connection();
463983
464220
  init_runtime();
463984
464221
  await init_queue();
463985
464222
  });
463986
464223
 
463987
464224
  // src/websocket/listener/protocol-logging.ts
463988
- function isRecord12(value) {
464225
+ function isRecord13(value) {
463989
464226
  return typeof value === "object" && value !== null && !Array.isArray(value);
463990
464227
  }
463991
464228
  function formatLogValue(value) {
@@ -464009,14 +464246,14 @@ function pushField(fields, key2, value, label = key2) {
464009
464246
  fields.push(`${label}=${formatted}`);
464010
464247
  }
464011
464248
  function summarizeInputPayload(payload) {
464012
- if (!isRecord12(payload))
464249
+ if (!isRecord13(payload))
464013
464250
  return [];
464014
464251
  const fields = [];
464015
464252
  pushField(fields, "kind", payload.kind);
464016
464253
  if (payload.kind === "create_message") {
464017
464254
  pushField(fields, "messages", payload.messages);
464018
464255
  pushField(fields, "client_tool_allowlist", payload.client_tool_allowlist);
464019
- if (isRecord12(payload.client_toolset)) {
464256
+ if (isRecord13(payload.client_toolset)) {
464020
464257
  pushField(fields, "client_toolset.base", payload.client_toolset.base);
464021
464258
  pushField(fields, "client_toolset.include", payload.client_toolset.include);
464022
464259
  }
@@ -464033,9 +464270,9 @@ function summarizeRuntimeStartCommand(command) {
464033
464270
  const fields = [];
464034
464271
  pushField(fields, "agent_id", command.agent_id, "agent");
464035
464272
  pushField(fields, "conversation_id", command.conversation_id, "conversation");
464036
- if (isRecord12(command.create_agent))
464273
+ if (isRecord13(command.create_agent))
464037
464274
  fields.push("create_agent=true");
464038
- if (isRecord12(command.create_conversation)) {
464275
+ if (isRecord13(command.create_conversation)) {
464039
464276
  fields.push("create_conversation=true");
464040
464277
  }
464041
464278
  pushField(fields, "cwd", command.cwd);
@@ -464044,17 +464281,17 @@ function summarizeRuntimeStartCommand(command) {
464044
464281
  return fields;
464045
464282
  }
464046
464283
  function summarizeV2Command(parsed) {
464047
- if (!isRecord12(parsed) || typeof parsed.type !== "string")
464284
+ if (!isRecord13(parsed) || typeof parsed.type !== "string")
464048
464285
  return "unknown";
464049
464286
  const fields = [];
464050
- const runtime = isRecord12(parsed.runtime) ? parsed.runtime : null;
464287
+ const runtime = isRecord13(parsed.runtime) ? parsed.runtime : null;
464051
464288
  if (runtime) {
464052
464289
  fields.push(`runtime=${runtime.agent_id ?? "<unknown>"}/${runtime.conversation_id ?? "<unknown>"}`);
464053
464290
  }
464054
464291
  pushField(fields, "request_id", parsed.request_id);
464055
464292
  if (parsed.type === "input") {
464056
464293
  fields.push(...summarizeInputPayload(parsed.payload));
464057
- } else if (parsed.type === "change_device_state" && isRecord12(parsed.payload)) {
464294
+ } else if (parsed.type === "change_device_state" && isRecord13(parsed.payload)) {
464058
464295
  pushField(fields, "mode", parsed.payload.mode);
464059
464296
  pushField(fields, "cwd", parsed.payload.cwd);
464060
464297
  pushField(fields, "agent_id", parsed.payload.agent_id);
@@ -464064,7 +464301,7 @@ function summarizeV2Command(parsed) {
464064
464301
  } else if (parsed.type === "runtime_external_tools_update" && Array.isArray(parsed.updates)) {
464065
464302
  fields.push(`updates=${parsed.updates.length}`);
464066
464303
  fields.push(`runtimes=${parsed.updates.reduce((count, update2) => {
464067
- return count + (isRecord12(update2) && Array.isArray(update2.runtimes) ? update2.runtimes.length : 0);
464304
+ return count + (isRecord13(update2) && Array.isArray(update2.runtimes) ? update2.runtimes.length : 0);
464068
464305
  }, 0)}`);
464069
464306
  } else {
464070
464307
  for (const key2 of [
@@ -465096,8 +465333,8 @@ async function startConnectedListenerRuntime(runtime, transport, opts, processQu
465096
465333
  startConnectionHeartbeat(runtime, transport, () => {
465097
465334
  trackListenerError4("listener_pong_timeout", new Error(`No relay pong within ${LISTENER_PONG_TIMEOUT_MS}ms; terminating half-open socket to force reconnect`), "listener_heartbeat");
465098
465335
  runtime.socket?.terminate();
465099
- }, () => {
465100
- return safeTransportSend(transport, { type: "ping" }, "listener_ping_send_failed", "listener_heartbeat");
465336
+ }, (heartbeatTransport) => {
465337
+ return safeTransportSend(heartbeatTransport, { type: "ping" }, "listener_ping_send_failed", "listener_heartbeat");
465101
465338
  });
465102
465339
  }
465103
465340
  if (options3.startProcessServices === false)
@@ -466127,7 +466364,7 @@ function decodeJwtClaims(token2, sharedSecret) {
466127
466364
  } catch {
466128
466365
  return { error: unauthorized("invalid websocket jwt") };
466129
466366
  }
466130
- if (!isRecord13(header) || header.alg !== "HS256") {
466367
+ if (!isRecord14(header) || header.alg !== "HS256") {
466131
466368
  return { error: unauthorized("invalid websocket jwt") };
466132
466369
  }
466133
466370
  const expectedSignature = createHmac("sha256", sharedSecret).update(`${encodedHeader}.${encodedClaims}`).digest();
@@ -466165,7 +466402,7 @@ function audienceMatches(actual, expectedAudience) {
466165
466402
  return false;
466166
466403
  }
466167
466404
  function isJwtClaims(value) {
466168
- if (!isRecord13(value) || !Number.isSafeInteger(value.exp)) {
466405
+ if (!isRecord14(value) || !Number.isSafeInteger(value.exp)) {
466169
466406
  return false;
466170
466407
  }
466171
466408
  if (value.nbf !== undefined && !Number.isSafeInteger(value.nbf)) {
@@ -466185,7 +466422,7 @@ function isJwtClaims(value) {
466185
466422
  }
466186
466423
  return true;
466187
466424
  }
466188
- function isRecord13(value) {
466425
+ function isRecord14(value) {
466189
466426
  return typeof value === "object" && value !== null && !Array.isArray(value);
466190
466427
  }
466191
466428
  function base64UrlDecode(value) {
@@ -470434,7 +470671,7 @@ import {
470434
470671
  } from "node:fs";
470435
470672
  import { tmpdir as tmpdir9 } from "node:os";
470436
470673
  import path41 from "node:path";
470437
- function isRecord14(value) {
470674
+ function isRecord15(value) {
470438
470675
  return typeof value === "object" && value !== null && !Array.isArray(value);
470439
470676
  }
470440
470677
  function isPathInsideOrEqual2(childPath, parentPath) {
@@ -470450,7 +470687,7 @@ function readPackageJson(packageJsonPath) {
470450
470687
  } catch (error54) {
470451
470688
  throw new Error(`Could not read package.json: ${error54 instanceof Error ? error54.message : String(error54)}`);
470452
470689
  }
470453
- if (!isRecord14(parsed)) {
470690
+ if (!isRecord15(parsed)) {
470454
470691
  throw new Error("package.json must be an object");
470455
470692
  }
470456
470693
  return parsed;
@@ -470460,7 +470697,7 @@ function formatRepository(repository) {
470460
470697
  const trimmed2 = repository.trim();
470461
470698
  return trimmed2 || undefined;
470462
470699
  }
470463
- if (!isRecord14(repository))
470700
+ if (!isRecord15(repository))
470464
470701
  return;
470465
470702
  const url2 = repository.url;
470466
470703
  if (typeof url2 !== "string")
@@ -470990,7 +471227,7 @@ function hasRuntimeDependencies(packageJson) {
470990
471227
  if (!packageJson)
470991
471228
  return false;
470992
471229
  const dependencies4 = packageJson.dependencies;
470993
- return isRecord14(dependencies4) && Object.keys(dependencies4).length > 0;
471230
+ return isRecord15(dependencies4) && Object.keys(dependencies4).length > 0;
470994
471231
  }
470995
471232
  function readPackageJsonIfExists(packageDirectory) {
470996
471233
  const packageJsonPath = path41.join(packageDirectory, "package.json");
@@ -477806,6 +478043,9 @@ function uniqueSources(sources) {
477806
478043
  byKey.set(sourceKey(source2), source2);
477807
478044
  return [...byKey.values()];
477808
478045
  }
478046
+ function channelTagsForSources(sources) {
478047
+ return [...new Set(sources.map((source2) => `channel:${source2.channel}`))];
478048
+ }
477809
478049
  function stopReasonFromDelta(message) {
477810
478050
  const delta2 = message.delta;
477811
478051
  return delta2.message_type === "stop_reason" && "stop_reason" in delta2 && typeof delta2.stop_reason === "string" ? delta2.stop_reason : null;
@@ -478047,9 +478287,11 @@ class ChannelGateway {
478047
478287
  }
478048
478288
  async performRuntimeRegistration(state, delivery) {
478049
478289
  const tool2 = await this.hooks.buildExternalTool(delivery.runtime, delivery.sources);
478290
+ const conversationTags = channelTagsForSources(delivery.sources);
478050
478291
  const signature = JSON.stringify({
478051
478292
  mode: delivery.defaultPermissionMode ?? null,
478052
- tool: tool2
478293
+ tool: tool2,
478294
+ conversationTags
478053
478295
  });
478054
478296
  if (state.registrationSignature === signature && state.registration) {
478055
478297
  return state.registration;
@@ -478057,6 +478299,7 @@ class ChannelGateway {
478057
478299
  const registration = this.client.runtimeStart({
478058
478300
  agent_id: delivery.runtime.agent_id,
478059
478301
  conversation_id: delivery.runtime.conversation_id,
478302
+ ...conversationTags.length > 0 ? { conversation_source_tags: conversationTags } : {},
478060
478303
  ...delivery.defaultPermissionMode ? { mode: delivery.defaultPermissionMode } : {},
478061
478304
  recover_approvals: true,
478062
478305
  force_device_status: false,
@@ -492188,7 +492431,7 @@ async function connectMcpServer(config3, options3 = {}) {
492188
492431
  return {
492189
492432
  content: Array.isArray(result.content) ? result.content : [],
492190
492433
  ...result.isError === true ? { isError: true } : {},
492191
- ...isRecord15(result.structuredContent) ? { structuredContent: result.structuredContent } : {}
492434
+ ...isRecord16(result.structuredContent) ? { structuredContent: result.structuredContent } : {}
492192
492435
  };
492193
492436
  },
492194
492437
  close: async () => {
@@ -492261,11 +492504,11 @@ function mergeHeaders4(init, headers) {
492261
492504
  };
492262
492505
  }
492263
492506
  function normalizeInputSchema(value) {
492264
- if (isRecord15(value) && value.type === "object")
492507
+ if (isRecord16(value) && value.type === "object")
492265
492508
  return value;
492266
492509
  return { type: "object", properties: {} };
492267
492510
  }
492268
- function isRecord15(value) {
492511
+ function isRecord16(value) {
492269
492512
  return typeof value === "object" && value !== null && !Array.isArray(value);
492270
492513
  }
492271
492514
  var DEFAULT_CLIENT_INFO;
@@ -492277,7 +492520,7 @@ var init_mcp_client = __esm(() => {
492277
492520
  init_streamableHttp();
492278
492521
  DEFAULT_CLIENT_INFO = {
492279
492522
  name: "letta-code",
492280
- version: "0.30.8"
492523
+ version: "0.30.9"
492281
492524
  };
492282
492525
  });
492283
492526
 
@@ -492673,7 +492916,7 @@ function toExternalToolResult(result) {
492673
492916
  };
492674
492917
  }
492675
492918
  function normalizeContent2(item) {
492676
- if (isRecord16(item)) {
492919
+ if (isRecord17(item)) {
492677
492920
  if (item.type === "text" && typeof item.text === "string") {
492678
492921
  return { type: "text", text: item.text };
492679
492922
  }
@@ -492687,7 +492930,7 @@ function normalizeContent2(item) {
492687
492930
  }
492688
492931
  return { type: "text", text: JSON.stringify(item) };
492689
492932
  }
492690
- function isRecord16(value) {
492933
+ function isRecord17(value) {
492691
492934
  return typeof value === "object" && value !== null && !Array.isArray(value);
492692
492935
  }
492693
492936
  var CLIENT_MCP_RUNTIME_KEY;
@@ -495437,6 +495680,15 @@ ${loadedContents.join(`
495437
495680
  const lastReasoning = reversed.find((line) => line.kind === "reasoning" && ("text" in line) && typeof line.text === "string" && line.text.trim().length > 0);
495438
495681
  const lastToolResult = reversed.find((line) => line.kind === "tool_call" && ("resultText" in line) && typeof line.resultText === "string" && (line.resultText ?? "").trim().length > 0);
495439
495682
  const resultText = lastAssistant?.text || lastReasoning?.text || lastToolResult?.resultText || "No assistant response found";
495683
+ if (!lastAssistant && (lastReasoning || lastToolResult)) {
495684
+ trackEndTurnNoAssistant({
495685
+ fallbackKind: lastReasoning ? "reasoning" : "tool_call",
495686
+ modelHandle: agent2.llm_config?.model ?? model,
495687
+ runId: lastKnownRunId ?? undefined,
495688
+ isSubagent,
495689
+ subagentType: systemPromptPreset ?? agent2.tags?.find((t2) => t2.startsWith("type:"))?.slice(5)
495690
+ });
495691
+ }
495440
495692
  const stats = sessionStats.getSnapshot();
495441
495693
  const usage = {
495442
495694
  prompt_tokens: stats.usage.promptTokens,
@@ -500356,8 +500608,9 @@ function useLocalModAdapter(context3, options3 = {}) {
500356
500608
  ...agentModsDirectory ? { agentModsDirectory } : {},
500357
500609
  disabled,
500358
500610
  getBackend,
500359
- getClient
500360
- }), [agentModsDirectory, disabled]);
500611
+ getClient,
500612
+ onNotification: options3.onNotification
500613
+ }), [agentModsDirectory, disabled, options3.onNotification]);
500361
500614
  const snapshot = import_react43.useSyncExternalStore(adapter.subscribe, adapter.getSnapshot, adapter.getSnapshot);
500362
500615
  import_react43.useEffect(() => {
500363
500616
  adapter.reload();
@@ -552980,6 +553233,7 @@ function App2({
552980
553233
  }, [isExecutingTool]);
552981
553234
  const refreshDerivedRef = import_react120.useRef(null);
552982
553235
  const appendTaskNotificationEvents = import_react120.useCallback((summaries) => appendTaskNotificationEventsToBuffer(summaries, buffersRef.current, () => uid("event"), () => refreshDerivedRef.current?.()), []);
553236
+ const appendModNotification = import_react120.useCallback((message) => appendTaskNotificationEvents([message]), [appendTaskNotificationEvents]);
552983
553237
  const consumeQueuedMessages = import_react120.useCallback(() => {
552984
553238
  const len = tuiQueueRef.current?.length ?? 0;
552985
553239
  if (len === 0)
@@ -553399,7 +553653,8 @@ function App2({
553399
553653
  const agentModsDirectory = modContext.memfs.enabled && modContext.memfs.memoryDir ? join92(modContext.memfs.memoryDir, "mods") : null;
553400
553654
  const modAdapter = useLocalModAdapter(modContext, {
553401
553655
  agentModsDirectory,
553402
- disabled: modsDisabled
553656
+ disabled: modsDisabled,
553657
+ onNotification: appendModNotification
553403
553658
  });
553404
553659
  import_react120.useEffect(() => {
553405
553660
  modAdapterRef.current = modAdapter;
@@ -559380,4 +559635,4 @@ function registerBunOAuthFlows() {
559380
559635
  registerBunOAuthFlows();
559381
559636
  await init_src5().then(() => exports_src2);
559382
559637
 
559383
- //# debugId=11C66507CCCFFD3064756E2164756E21
559638
+ //# debugId=5CF69A1BB16338C364756E2164756E21