@letta-ai/letta-code 0.28.4 → 0.28.6

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
@@ -3138,6 +3138,29 @@ var init_letta_client = __esm(() => {
3138
3138
  init_error();
3139
3139
  });
3140
3140
 
3141
+ // src/auth/oauth-refresh.ts
3142
+ async function refreshAccessTokenSingleFlight(refreshToken, deviceId, deviceName, refresh = refreshAccessToken) {
3143
+ const refreshKey = `${refreshToken}\x00${deviceId}`;
3144
+ const existing = inFlightRefreshes.get(refreshKey);
3145
+ if (existing) {
3146
+ return await existing;
3147
+ }
3148
+ const pending = refresh(refreshToken, deviceId, deviceName);
3149
+ inFlightRefreshes.set(refreshKey, pending);
3150
+ try {
3151
+ return await pending;
3152
+ } finally {
3153
+ if (inFlightRefreshes.get(refreshKey) === pending) {
3154
+ inFlightRefreshes.delete(refreshKey);
3155
+ }
3156
+ }
3157
+ }
3158
+ var inFlightRefreshes;
3159
+ var init_oauth_refresh = __esm(() => {
3160
+ init_oauth();
3161
+ inFlightRefreshes = new Map;
3162
+ });
3163
+
3141
3164
  // src/agent/agent-id.ts
3142
3165
  function isLocalAgentId(agentId) {
3143
3166
  return agentId.startsWith("agent-local-");
@@ -3846,14 +3869,13 @@ class SettingsManager {
3846
3869
  }
3847
3870
  async getSettingsWithSecureTokens() {
3848
3871
  const baseSettings = this.getSettings();
3849
- let secureTokens = { ...this.secureTokensCache };
3872
+ const secureTokens = { ...this.secureTokensCache };
3850
3873
  if (!getRuntimeContext()) {
3851
3874
  const secretsAvailable2 = await this.isKeychainAvailable();
3852
3875
  if (secretsAvailable2) {
3853
- secureTokens = {
3854
- ...secureTokens,
3855
- ...await this.getSecureTokens()
3856
- };
3876
+ const storedTokens = await this.getSecureTokens();
3877
+ secureTokens.apiKey = storedTokens.apiKey ?? secureTokens.apiKey;
3878
+ secureTokens.refreshToken = storedTokens.refreshToken ?? secureTokens.refreshToken;
3857
3879
  }
3858
3880
  }
3859
3881
  const fallbackRefreshToken = !secureTokens.refreshToken && baseSettings.refreshToken ? baseSettings.refreshToken : secureTokens.refreshToken;
@@ -4815,7 +4837,7 @@ var package_default;
4815
4837
  var init_package = __esm(() => {
4816
4838
  package_default = {
4817
4839
  name: "@letta-ai/letta-code",
4818
- version: "0.28.4",
4840
+ version: "0.28.6",
4819
4841
  description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
4820
4842
  type: "module",
4821
4843
  packageManager: "bun@1.3.0",
@@ -5113,7 +5135,7 @@ async function getClient() {
5113
5135
  try {
5114
5136
  const deviceId = settingsManager.getOrCreateDeviceId();
5115
5137
  const deviceName = hostname();
5116
- const tokens = await refreshAccessToken(settings.refreshToken, deviceId, deviceName);
5138
+ const tokens = await refreshAccessTokenSingleFlight(settings.refreshToken, deviceId, deviceName);
5117
5139
  settingsManager.updateSettings({
5118
5140
  env: { LETTA_API_KEY: tokens.access_token },
5119
5141
  refreshToken: tokens.refresh_token || settings.refreshToken,
@@ -5176,6 +5198,7 @@ var SDK_DIAGNOSTIC_MAX_LEN = 400, SDK_DIAGNOSTIC_MAX_LINES = 4, lastSDKDiagnosti
5176
5198
  var init_client2 = __esm(() => {
5177
5199
  init_letta_client();
5178
5200
  init_oauth();
5201
+ init_oauth_refresh();
5179
5202
  init_settings_manager();
5180
5203
  init_error_reporting();
5181
5204
  init_debug();
@@ -5855,6 +5878,7 @@ __export(exports_oauth, {
5855
5878
  requestDeviceCode: () => requestDeviceCode,
5856
5879
  refreshAccessToken: () => refreshAccessToken,
5857
5880
  pollForToken: () => pollForToken,
5881
+ OAuthRefreshError: () => OAuthRefreshError,
5858
5882
  OAUTH_CONFIG: () => OAUTH_CONFIG,
5859
5883
  LETTA_CLOUD_API_URL: () => LETTA_CLOUD_API_URL
5860
5884
  });
@@ -6035,11 +6059,22 @@ async function refreshAccessToken(refreshToken, deviceId, deviceName) {
6035
6059
  });
6036
6060
  if (!response.ok) {
6037
6061
  const error = await response.json();
6038
- throw new Error(`Failed to refresh access token from ${authHost}: ${error.error_description || error.error}`);
6062
+ throw new OAuthRefreshError(`Failed to refresh access token from ${authHost}: ${error.error_description || error.error}`, {
6063
+ retryable: response.status === 408 || response.status === 429 || response.status >= 500,
6064
+ status: response.status,
6065
+ oauthCode: error.error
6066
+ });
6039
6067
  }
6040
6068
  return await response.json();
6041
6069
  } catch (error) {
6042
- throw toOAuthActionError("refresh access token", error);
6070
+ if (error instanceof OAuthRefreshError) {
6071
+ throw error;
6072
+ }
6073
+ const actionError = toOAuthActionError("refresh access token", error);
6074
+ throw new OAuthRefreshError(actionError.message, {
6075
+ retryable: true,
6076
+ cause: error
6077
+ });
6043
6078
  }
6044
6079
  }
6045
6080
  async function revokeToken(refreshToken) {
@@ -6125,7 +6160,7 @@ function classifyCredentialValidationError(error) {
6125
6160
  }
6126
6161
  return { ok: false, reason: "unknown", message };
6127
6162
  }
6128
- var LETTA_CLOUD_API_URL = "https://api.letta.com", OAUTH_CONFIG;
6163
+ var LETTA_CLOUD_API_URL = "https://api.letta.com", OAUTH_CONFIG, OAuthRefreshError;
6129
6164
  var init_oauth = __esm(() => {
6130
6165
  init_letta_client();
6131
6166
  init_error();
@@ -6136,6 +6171,18 @@ var init_oauth = __esm(() => {
6136
6171
  authBaseUrl: "https://app.letta.com",
6137
6172
  apiBaseUrl: LETTA_CLOUD_API_URL
6138
6173
  };
6174
+ OAuthRefreshError = class OAuthRefreshError extends Error {
6175
+ retryable;
6176
+ status;
6177
+ oauthCode;
6178
+ constructor(message, options) {
6179
+ super(message, { cause: options.cause });
6180
+ this.name = "OAuthRefreshError";
6181
+ this.retryable = options.retryable;
6182
+ this.status = options.status;
6183
+ this.oauthCode = options.oauthCode;
6184
+ }
6185
+ };
6139
6186
  });
6140
6187
 
6141
6188
  // src/backend/backend-mode.ts
@@ -6157,6 +6204,7 @@ var init_backend_mode = __esm(() => {
6157
6204
  var exports_agent_tags = {};
6158
6205
  __export(exports_agent_tags, {
6159
6206
  buildCreatedAgentTags: () => buildCreatedAgentTags,
6207
+ ONBOARDING_ORIGIN_TAG: () => ONBOARDING_ORIGIN_TAG,
6160
6208
  LETTA_CODE_SUBAGENT_TAG: () => LETTA_CODE_SUBAGENT_TAG,
6161
6209
  LETTA_CODE_ORIGIN_TAG: () => LETTA_CODE_ORIGIN_TAG,
6162
6210
  GIT_MEMORY_ENABLED_TAG: () => GIT_MEMORY_ENABLED_TAG
@@ -6174,7 +6222,7 @@ function buildCreatedAgentTags(options = {}) {
6174
6222
  }
6175
6223
  return Array.from(new Set(tags));
6176
6224
  }
6177
- var LETTA_CODE_ORIGIN_TAG = "origin:letta-code", LETTA_CODE_SUBAGENT_TAG = "role:subagent", GIT_MEMORY_ENABLED_TAG = "git-memory-enabled";
6225
+ var LETTA_CODE_ORIGIN_TAG = "origin:letta-code", ONBOARDING_ORIGIN_TAG = "origin:onboarding", LETTA_CODE_SUBAGENT_TAG = "role:subagent", GIT_MEMORY_ENABLED_TAG = "git-memory-enabled";
6178
6226
 
6179
6227
  // src/utils/text-files.ts
6180
6228
  import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
@@ -168362,238 +168410,91 @@ var init_approval_controller = __esm(async () => {
168362
168410
  await init_presentation();
168363
168411
  });
168364
168412
 
168365
- // src/channels/slack/file-upload.ts
168366
- import { readFile as readFile4 } from "node:fs/promises";
168367
- import { basename as basename7, extname as extname3 } from "node:path";
168368
- function resolveUploadMimeType(filePath) {
168369
- switch (extname3(filePath).toLowerCase()) {
168370
- case ".png":
168371
- return "image/png";
168372
- case ".jpg":
168373
- case ".jpeg":
168374
- return "image/jpeg";
168375
- case ".gif":
168376
- return "image/gif";
168377
- case ".webp":
168378
- return "image/webp";
168379
- case ".pdf":
168380
- return "application/pdf";
168381
- case ".txt":
168382
- return "text/plain";
168383
- case ".md":
168384
- return "text/markdown";
168385
- default:
168386
- return;
168387
- }
168388
- }
168389
- async function uploadSlackFile(slackClient, msg) {
168390
- if (!msg.mediaPath) {
168391
- throw new Error("mediaPath is required for Slack file uploads.");
168392
- }
168393
- const buffer = await readFile4(msg.mediaPath);
168394
- const uploadFileName = msg.fileName ?? basename7(msg.mediaPath);
168395
- const uploadTitle = msg.title ?? uploadFileName;
168396
- const uploadMimeType = resolveUploadMimeType(uploadFileName);
168397
- const uploadUrlResp = await slackClient.files.getUploadURLExternal({
168398
- filename: uploadFileName,
168399
- length: buffer.length
168400
- });
168401
- if (!uploadUrlResp.ok || !uploadUrlResp.upload_url || !uploadUrlResp.file_id) {
168402
- throw new Error(`Failed to get Slack upload URL: ${uploadUrlResp.error ?? "unknown error"}`);
168403
- }
168404
- const uploadResp = await fetch(uploadUrlResp.upload_url, {
168405
- method: "POST",
168406
- ...uploadMimeType ? { headers: { "Content-Type": uploadMimeType } } : {},
168407
- body: buffer
168408
- });
168409
- if (!uploadResp.ok) {
168410
- throw new Error(`Failed to upload Slack file: HTTP ${uploadResp.status}`);
168411
- }
168412
- const threadTs = resolveSlackOutboundThreadTs({
168413
- chatId: msg.chatId,
168414
- threadId: msg.threadId,
168415
- replyToMessageId: msg.replyToMessageId
168416
- });
168417
- const completeResp = await slackClient.files.completeUploadExternal({
168418
- files: [{ id: uploadUrlResp.file_id, title: uploadTitle }],
168419
- channel_id: msg.chatId,
168420
- ...msg.text.trim() ? { initial_comment: msg.text } : {},
168421
- ...threadTs ? { thread_ts: threadTs } : {}
168422
- });
168423
- if (!completeResp.ok) {
168424
- throw new Error(`Failed to complete Slack upload: ${completeResp.error ?? "unknown error"}`);
168425
- }
168426
- return { messageId: uploadUrlResp.file_id };
168427
- }
168428
- var init_file_upload = __esm(() => {
168429
- init_utils5();
168430
- });
168431
-
168432
- // src/channels/slack/inbound-debounce.ts
168433
- function buildSlackDebounceKey(rawMessage, accountId) {
168434
- const senderId = rawMessage.user ?? rawMessage.bot_id ?? null;
168435
- if (!senderId)
168436
- return null;
168437
- const messageTs = rawMessage.ts ?? rawMessage.event_ts;
168438
- const isDm = rawMessage.channel.startsWith("D");
168439
- const scope = rawMessage.thread_ts ? `${rawMessage.channel}:${rawMessage.thread_ts}` : rawMessage.parent_user_id && messageTs ? `${rawMessage.channel}:maybe-thread:${messageTs}` : messageTs && !isDm ? `${rawMessage.channel}:${messageTs}` : rawMessage.channel;
168440
- return `slack:${accountId}:${scope}:${senderId}`;
168441
- }
168442
- function buildTopLevelSlackConversationKey(rawMessage, accountId) {
168443
- if (rawMessage.thread_ts || rawMessage.parent_user_id)
168444
- return null;
168445
- if (rawMessage.channel.startsWith("D"))
168446
- return null;
168447
- const senderId = rawMessage.user ?? rawMessage.bot_id;
168448
- return senderId ? `slack:${accountId}:${rawMessage.channel}:${senderId}` : null;
168449
- }
168450
- function resolveSlackInboundDebounceMs(config3) {
168451
- const raw = process.env.LETTA_SLACK_INBOUND_DEBOUNCE_MS;
168452
- if (typeof raw === "string" && raw.trim() !== "") {
168453
- const envOverride = Number(raw);
168454
- if (Number.isFinite(envOverride) && envOverride >= 0) {
168455
- return Math.trunc(envOverride);
168456
- }
168457
- }
168458
- const fromConfig = config3.inboundDebounceMs;
168459
- return typeof fromConfig === "number" && Number.isFinite(fromConfig) && fromConfig >= 0 ? Math.trunc(fromConfig) : 0;
168413
+ // src/channels/slack/attachment-stream.ts
168414
+ import { randomUUID as randomUUID8 } from "node:crypto";
168415
+ import { mkdir as mkdir4, open as open2, rename, rm as rm2 } from "node:fs/promises";
168416
+ import { join as join12 } from "node:path";
168417
+ function sanitizeFileName(name) {
168418
+ const normalized = name.trim().replace(/[^\w.-]+/g, "_");
168419
+ return normalized.length > 0 ? normalized : "attachment";
168460
168420
  }
168461
- function createSlackInboundDebounceController(params) {
168462
- const { config: config3 } = params;
168463
- const debounceMs = resolveSlackInboundDebounceMs(config3);
168464
- const pendingTopLevelKeys = new Map;
168465
- const appMentionRetryKeys = new Map;
168466
- const appMentionDispatchedKeys = new Map;
168467
- function pruneAppMentionMaps(now) {
168468
- for (const [key, expiresAt] of appMentionRetryKeys) {
168469
- if (expiresAt <= now)
168470
- appMentionRetryKeys.delete(key);
168471
- }
168472
- for (const [key, expiresAt] of appMentionDispatchedKeys) {
168473
- if (expiresAt <= now)
168474
- appMentionDispatchedKeys.delete(key);
168475
- }
168476
- }
168477
- function dedupeEntries(entries) {
168478
- const indexByMessageKey = new Map;
168479
- const deduped = [];
168480
- for (const entry of entries) {
168481
- const messageId = entry.inbound.messageId;
168482
- const messageKey = isNonEmptyString2(messageId) ? `${entry.inbound.chatId}:${messageId}` : null;
168483
- if (!messageKey) {
168484
- deduped.push(entry);
168485
- continue;
168421
+ async function saveSlackAttachmentStream(params) {
168422
+ const inboundDir = join12(getChannelDir("slack"), "inbound", sanitizeFileName(params.accountId));
168423
+ await mkdir4(inboundDir, { recursive: true });
168424
+ const filePath = join12(inboundDir, `${Date.now()}-${randomUUID8()}-${sanitizeFileName(params.fileName)}`);
168425
+ const temporaryPath = `${filePath}.partial`;
168426
+ const fileHandle = await open2(temporaryPath, "wx");
168427
+ const reader = params.body.getReader();
168428
+ let sizeBytes = 0;
168429
+ let completed = false;
168430
+ try {
168431
+ while (true) {
168432
+ const { done, value } = await reader.read();
168433
+ if (done) {
168434
+ break;
168486
168435
  }
168487
- const existingIndex = indexByMessageKey.get(messageKey);
168488
- if (existingIndex === undefined) {
168489
- indexByMessageKey.set(messageKey, deduped.length);
168490
- deduped.push(entry);
168436
+ if (!value?.byteLength) {
168491
168437
  continue;
168492
168438
  }
168493
- const current = deduped[existingIndex];
168494
- if (current && current.opts.source !== "app_mention" && current.inbound.isMention !== true) {
168495
- deduped[existingIndex] = entry;
168439
+ sizeBytes += value.byteLength;
168440
+ if (params.maxBytes !== undefined && sizeBytes > params.maxBytes) {
168441
+ throw new SlackAttachmentDownloadError("exceeds_auto_download_limit", `Slack attachment exceeds automatic download limit (${params.maxBytes} bytes).`);
168496
168442
  }
168497
- }
168498
- return deduped;
168499
- }
168500
- const debouncer = createInboundDebouncer({
168501
- debounceMs,
168502
- buildKey: ({ raw }) => buildSlackDebounceKey(raw, config3.accountId),
168503
- shouldDebounce: ({ inbound }) => !inbound.attachments?.length && !inbound.reaction,
168504
- onFlush: async (entries) => {
168505
- const dedupedEntries = dedupeEntries(entries);
168506
- const last = dedupedEntries[dedupedEntries.length - 1];
168507
- if (!last)
168508
- return;
168509
- const flushedKey = buildSlackDebounceKey(last.raw, config3.accountId);
168510
- const conversationKey = buildTopLevelSlackConversationKey(last.raw, config3.accountId);
168511
- if (flushedKey && conversationKey) {
168512
- const pending = pendingTopLevelKeys.get(conversationKey);
168513
- pending?.delete(flushedKey);
168514
- if (pending?.size === 0)
168515
- pendingTopLevelKeys.delete(conversationKey);
168516
- }
168517
- if (isNonEmptyString2(last.inbound.messageId)) {
168518
- const seenKey = `${last.inbound.chatId}:${last.inbound.messageId}`;
168519
- pruneAppMentionMaps(Date.now());
168520
- if (last.opts.source === "app_mention") {
168521
- appMentionDispatchedKeys.set(seenKey, Date.now() + APP_MENTION_RETRY_TTL_MS);
168522
- } else if (appMentionDispatchedKeys.has(seenKey)) {
168523
- appMentionDispatchedKeys.delete(seenKey);
168524
- appMentionRetryKeys.delete(seenKey);
168525
- return;
168443
+ let offset = 0;
168444
+ while (offset < value.byteLength) {
168445
+ const { bytesWritten } = await fileHandle.write(value, offset, value.byteLength - offset);
168446
+ if (bytesWritten <= 0) {
168447
+ throw new Error("Slack attachment write made no progress.");
168526
168448
  }
168527
- appMentionRetryKeys.delete(seenKey);
168449
+ offset += bytesWritten;
168528
168450
  }
168529
- const onMessage = params.getOnMessage();
168530
- if (!onMessage)
168451
+ }
168452
+ completed = true;
168453
+ } finally {
168454
+ if (!completed) {
168455
+ await reader.cancel().catch(() => {
168531
168456
  return;
168532
- const text = dedupedEntries.length === 1 ? last.inbound.text : dedupedEntries.map((entry) => entry.inbound.text).filter(Boolean).join(`
168533
- `);
168534
- try {
168535
- await onMessage({
168536
- ...last.inbound,
168537
- text,
168538
- isMention: dedupedEntries.some((entry) => entry.opts.wasMentioned || entry.inbound.isMention === true)
168539
- });
168540
- } catch (error54) {
168541
- console.error("[Slack] Error handling debounced inbound message:", error54);
168542
- }
168543
- },
168544
- onError: (error54) => {
168545
- console.error("[Slack] Inbound debounce flush failed:", error54 instanceof Error ? error54.message : error54);
168457
+ });
168546
168458
  }
168547
- });
168548
- return {
168549
- async dispatch(entry) {
168550
- const debounceKey = buildSlackDebounceKey(entry.raw, config3.accountId);
168551
- const conversationKey = buildTopLevelSlackConversationKey(entry.raw, config3.accountId);
168552
- const canDebounce = debounceMs > 0 && !entry.inbound.attachments?.length && !entry.inbound.reaction && Boolean(debounceKey);
168553
- if (!canDebounce && conversationKey) {
168554
- for (const pendingKey of Array.from(pendingTopLevelKeys.get(conversationKey) ?? [])) {
168555
- try {
168556
- await debouncer.flushKey(pendingKey);
168557
- } catch {}
168558
- }
168559
- }
168560
- if (canDebounce && debounceKey && conversationKey) {
168561
- const pending = pendingTopLevelKeys.get(conversationKey) ?? new Set;
168562
- pending.add(debounceKey);
168563
- pendingTopLevelKeys.set(conversationKey, pending);
168564
- }
168565
- await debouncer.enqueue(entry);
168566
- },
168567
- rememberAppMentionRetry(seenKey) {
168568
- const now = Date.now();
168569
- pruneAppMentionMaps(now);
168570
- appMentionRetryKeys.set(seenKey, now + APP_MENTION_RETRY_TTL_MS);
168571
- },
168572
- consumeAppMentionRetry(seenKey) {
168573
- pruneAppMentionMaps(Date.now());
168574
- if (!appMentionRetryKeys.has(seenKey))
168575
- return false;
168576
- appMentionRetryKeys.delete(seenKey);
168577
- return true;
168578
- },
168579
- clear() {
168580
- pendingTopLevelKeys.clear();
168581
- appMentionRetryKeys.clear();
168582
- appMentionDispatchedKeys.clear();
168459
+ try {
168460
+ reader.releaseLock();
168461
+ } catch {}
168462
+ await fileHandle.close().catch(() => {
168463
+ return;
168464
+ });
168465
+ if (!completed) {
168466
+ await rm2(temporaryPath, { force: true }).catch(() => {
168467
+ return;
168468
+ });
168583
168469
  }
168584
- };
168470
+ }
168471
+ try {
168472
+ await rename(temporaryPath, filePath);
168473
+ } catch (error54) {
168474
+ await rm2(temporaryPath, { force: true }).catch(() => {
168475
+ return;
168476
+ });
168477
+ throw error54;
168478
+ }
168479
+ return { localPath: filePath, sizeBytes };
168585
168480
  }
168586
- var APP_MENTION_RETRY_TTL_MS = 60000;
168587
- var init_inbound_debounce = __esm(() => {
168588
- init_utils5();
168481
+ var SlackAttachmentDownloadError;
168482
+ var init_attachment_stream = __esm(() => {
168483
+ init_config2();
168484
+ SlackAttachmentDownloadError = class SlackAttachmentDownloadError extends Error {
168485
+ reason;
168486
+ constructor(reason, message) {
168487
+ super(message);
168488
+ this.reason = reason;
168489
+ this.name = "SlackAttachmentDownloadError";
168490
+ }
168491
+ };
168589
168492
  });
168590
168493
 
168591
168494
  // src/channels/slack/media.ts
168592
- import { randomUUID as randomUUID8 } from "node:crypto";
168593
- import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile6 } from "node:fs/promises";
168594
- import { basename as basename8, extname as extname4, join as join12 } from "node:path";
168595
- async function mapSlackThreadMessage(message, attachmentOptions) {
168596
- const attachments = await resolveSlackMessageAttachments(message, attachmentOptions);
168495
+ import { basename as basename7, extname as extname3 } from "node:path";
168496
+ async function mapSlackThreadMessage(message, attachmentOptions, sourceThreadId) {
168497
+ const attachments = await resolveSlackMessageAttachments(message, attachmentOptions, sourceThreadId);
168597
168498
  return {
168598
168499
  text: resolveSlackThreadMessageText(message),
168599
168500
  userId: isNonEmptyString3(message.user) ? message.user : undefined,
@@ -168694,10 +168595,6 @@ function assertSlackFileUrl(rawUrl) {
168694
168595
  }
168695
168596
  return parsed;
168696
168597
  }
168697
- function sanitizeFileName(name) {
168698
- const normalized = name.trim().replace(/[^\w.-]+/g, "_");
168699
- return normalized.length > 0 ? normalized : "attachment";
168700
- }
168701
168598
  function extensionForMimeType2(mimeType) {
168702
168599
  switch (mimeType?.toLowerCase()) {
168703
168600
  case "image/png":
@@ -168735,7 +168632,7 @@ function resolveMimeType(name, fallback) {
168735
168632
  if (fallback) {
168736
168633
  return fallback;
168737
168634
  }
168738
- switch (extname4(name).toLowerCase()) {
168635
+ switch (extname3(name).toLowerCase()) {
168739
168636
  case ".png":
168740
168637
  return "image/png";
168741
168638
  case ".jpg":
@@ -168811,51 +168708,93 @@ async function fetchWithSlackAuth(url2, token) {
168811
168708
  }
168812
168709
  return fetch(resolved.href, { redirect: "follow" });
168813
168710
  }
168814
- async function saveSlackAttachment(params) {
168815
- const inboundDir = join12(getChannelDir("slack"), "inbound", sanitizeFileName(params.accountId));
168816
- await mkdir4(inboundDir, { recursive: true });
168817
- const filePath = join12(inboundDir, `${Date.now()}-${randomUUID8()}-${sanitizeFileName(params.fileName)}`);
168818
- await writeFile6(filePath, params.buffer);
168819
- return filePath;
168711
+ function resolveSlackAttachmentFileName(params) {
168712
+ const hintedName = params.file.name ?? (params.url ? basename7(new URL(params.url).pathname) : undefined) ?? `${params.file.id ?? "attachment"}${extensionForMimeType2(params.file.mimetype)}`;
168713
+ return extname3(hintedName) || !params.mimeType ? hintedName : `${hintedName}${extensionForMimeType2(params.mimeType)}`;
168714
+ }
168715
+ function resolveSlackAttachmentMimeType(params) {
168716
+ const preferredMimeType = params.responseMimeType && !isGenericSlackMimeType(params.responseMimeType) ? params.responseMimeType : params.file.mimetype && !isGenericSlackMimeType(params.file.mimetype) ? params.file.mimetype : undefined;
168717
+ return resolveMimeType(params.fileName, preferredMimeType);
168718
+ }
168719
+ function createUndownloadedSlackAttachment(params) {
168720
+ const fileName = resolveSlackAttachmentFileName({ file: params.file });
168721
+ const mimeType = resolveSlackAttachmentMimeType({
168722
+ file: params.file,
168723
+ fileName
168724
+ });
168725
+ return {
168726
+ id: params.file.id,
168727
+ name: fileName,
168728
+ mimeType,
168729
+ sizeBytes: params.file.size,
168730
+ kind: resolveAttachmentKind(mimeType),
168731
+ sourceMessageId: params.sourceMessageId,
168732
+ ...params.sourceThreadId ? { sourceThreadId: params.sourceThreadId } : {},
168733
+ downloadReason: params.reason,
168734
+ ...params.reason === "exceeds_auto_download_limit" ? { autoDownloadLimitBytes: MAX_SLACK_ATTACHMENT_BYTES } : {}
168735
+ };
168820
168736
  }
168821
- async function downloadSlackAttachment(params) {
168737
+ async function materializeSlackAttachment(params) {
168738
+ if (params.maxBytes !== undefined && typeof params.file.size === "number" && params.file.size > params.maxBytes) {
168739
+ throw new SlackAttachmentDownloadError("exceeds_auto_download_limit", `Slack attachment is ${params.file.size} bytes; automatic download limit is ${params.maxBytes} bytes.`);
168740
+ }
168822
168741
  const url2 = params.file.url_private_download ?? params.file.url_private;
168823
168742
  if (!url2) {
168824
- return null;
168743
+ throw new SlackAttachmentDownloadError("missing_download_url", "Slack attachment does not include a private download URL.");
168825
168744
  }
168826
- const response = await fetchWithSlackAuth(url2, params.token);
168745
+ const response = await fetchWithSlackAuth(url2, params.token).catch((error54) => {
168746
+ throw new SlackAttachmentDownloadError("download_failed", error54 instanceof Error ? error54.message : "Slack attachment fetch failed.");
168747
+ });
168827
168748
  if (!response.ok) {
168828
- return null;
168749
+ throw new SlackAttachmentDownloadError("download_failed", `Slack attachment fetch failed with HTTP ${response.status}.`);
168829
168750
  }
168830
- const arrayBuffer = await response.arrayBuffer();
168831
- if (arrayBuffer.byteLength > MAX_SLACK_ATTACHMENT_BYTES) {
168832
- return null;
168751
+ const contentLengthHeader = response.headers.get("content-length");
168752
+ const contentLength = contentLengthHeader ? Number(contentLengthHeader) : undefined;
168753
+ if (params.maxBytes !== undefined && contentLength !== undefined && Number.isFinite(contentLength) && contentLength > params.maxBytes) {
168754
+ await response.body?.cancel().catch(() => {
168755
+ return;
168756
+ });
168757
+ throw new SlackAttachmentDownloadError("exceeds_auto_download_limit", `Slack attachment is ${contentLength} bytes; automatic download limit is ${params.maxBytes} bytes.`);
168833
168758
  }
168834
- const buffer = Buffer.from(arrayBuffer);
168835
- const hintedName = params.file.name ?? basename8(new URL(url2).pathname) ?? `${params.file.id ?? "attachment"}${extensionForMimeType2(params.file.mimetype)}`;
168836
168759
  const responseMimeType = response.headers.get("content-type")?.split(";")[0]?.trim() || undefined;
168837
- const fileMimeType = params.file.mimetype;
168838
- const preferredMimeType = responseMimeType && !isGenericSlackMimeType(responseMimeType) ? responseMimeType : fileMimeType && !isGenericSlackMimeType(fileMimeType) ? fileMimeType : undefined;
168839
- const mimeType = resolveMimeType(hintedName, preferredMimeType);
168840
- const fileName = extname4(hintedName) || !mimeType ? hintedName : `${hintedName}${extensionForMimeType2(mimeType)}`;
168841
- const localPath = await saveSlackAttachment({
168760
+ const hintedName = resolveSlackAttachmentFileName({
168761
+ file: params.file,
168762
+ url: url2
168763
+ });
168764
+ const mimeType = resolveSlackAttachmentMimeType({
168765
+ file: params.file,
168766
+ fileName: hintedName,
168767
+ responseMimeType
168768
+ });
168769
+ const fileName = resolveSlackAttachmentFileName({
168770
+ file: params.file,
168771
+ url: url2,
168772
+ mimeType
168773
+ });
168774
+ if (!response.body) {
168775
+ throw new SlackAttachmentDownloadError("download_failed", "Slack attachment response did not include a body.");
168776
+ }
168777
+ const saved = await saveSlackAttachmentStream({
168842
168778
  accountId: params.accountId,
168843
168779
  fileName,
168844
- buffer
168780
+ body: response.body,
168781
+ maxBytes: params.maxBytes
168845
168782
  });
168846
168783
  const kind = resolveAttachmentKind(mimeType);
168847
168784
  const attachment = {
168848
168785
  id: params.file.id,
168849
168786
  name: fileName,
168850
168787
  mimeType,
168851
- sizeBytes: buffer.byteLength,
168788
+ sizeBytes: saved.sizeBytes,
168852
168789
  kind,
168853
- localPath
168790
+ localPath: saved.localPath,
168791
+ sourceMessageId: params.sourceMessageId,
168792
+ ...params.sourceThreadId ? { sourceThreadId: params.sourceThreadId } : {}
168854
168793
  };
168855
168794
  if (kind === "audio" && params.transcribeVoice) {
168856
168795
  const { isTranscriptionConfigured: isTranscriptionConfigured2, transcribeAudioFile: transcribeAudioFile2 } = await Promise.resolve().then(() => (init_transcription(), exports_transcription));
168857
168796
  if (isTranscriptionConfigured2()) {
168858
- const result = await transcribeAudioFile2(localPath);
168797
+ const result = await transcribeAudioFile2(saved.localPath);
168859
168798
  if (result.success && result.text) {
168860
168799
  attachment.transcription = result.text;
168861
168800
  } else if (result.error) {
@@ -168906,13 +168845,28 @@ async function resolveSlackFilesAsAttachments(params) {
168906
168845
  if (params.files.length === 0) {
168907
168846
  return [];
168908
168847
  }
168909
- const resolved = await Promise.all(params.files.map((file3) => downloadSlackAttachment({
168910
- accountId: params.accountId,
168911
- token: params.token,
168912
- file: file3,
168913
- transcribeVoice: params.transcribeVoice
168914
- }).catch(() => null)));
168915
- return resolved.filter((attachment) => Boolean(attachment));
168848
+ const resolved = await Promise.all(params.files.map(async (file3) => {
168849
+ try {
168850
+ return await materializeSlackAttachment({
168851
+ accountId: params.accountId,
168852
+ token: params.token,
168853
+ file: file3,
168854
+ sourceMessageId: params.sourceMessageId,
168855
+ sourceThreadId: params.sourceThreadId,
168856
+ maxBytes: MAX_SLACK_ATTACHMENT_BYTES,
168857
+ transcribeVoice: params.transcribeVoice
168858
+ });
168859
+ } catch (error54) {
168860
+ const reason = error54 instanceof SlackAttachmentDownloadError ? error54.reason : "download_failed";
168861
+ return createUndownloadedSlackAttachment({
168862
+ file: file3,
168863
+ sourceMessageId: params.sourceMessageId,
168864
+ sourceThreadId: params.sourceThreadId,
168865
+ reason
168866
+ });
168867
+ }
168868
+ }));
168869
+ return resolved;
168916
168870
  }
168917
168871
  function resolveSlackThreadAttachmentOptions(params) {
168918
168872
  if (!isNonEmptyString3(params.accountId) || !isNonEmptyString3(params.token)) {
@@ -168933,7 +168887,7 @@ function hasSlackThreadMessageContent(message, attachmentOptions) {
168933
168887
  function hasHydratedSlackThreadMessageContent(message) {
168934
168888
  return message.text.length > 0 || Boolean(message.attachments?.length);
168935
168889
  }
168936
- async function resolveSlackMessageAttachments(message, attachmentOptions) {
168890
+ async function resolveSlackMessageAttachments(message, attachmentOptions, sourceThreadId) {
168937
168891
  if (!attachmentOptions) {
168938
168892
  return [];
168939
168893
  }
@@ -168941,14 +168895,19 @@ async function resolveSlackMessageAttachments(message, attachmentOptions) {
168941
168895
  accountId: attachmentOptions.accountId,
168942
168896
  token: attachmentOptions.token,
168943
168897
  files: collectSlackFiles(message),
168898
+ sourceMessageId: message.ts,
168899
+ sourceThreadId: sourceThreadId ?? (isNonEmptyString3(message.thread_ts) ? message.thread_ts : null),
168944
168900
  transcribeVoice: attachmentOptions.transcribeVoice
168945
168901
  });
168946
168902
  }
168947
168903
  async function resolveSlackInboundAttachments(params) {
168904
+ const rawEvent = asRecord4(params.rawEvent);
168948
168905
  return resolveSlackFilesAsAttachments({
168949
168906
  accountId: params.accountId,
168950
168907
  token: params.token,
168951
168908
  files: collectSlackFiles(params.rawEvent),
168909
+ sourceMessageId: isNonEmptyString3(rawEvent?.ts) ? rawEvent.ts : undefined,
168910
+ sourceThreadId: isNonEmptyString3(rawEvent?.thread_ts) ? rawEvent.thread_ts : null,
168952
168911
  transcribeVoice: params.transcribeVoice
168953
168912
  });
168954
168913
  }
@@ -168970,7 +168929,7 @@ async function resolveSlackCurrentMessageAttachments(params) {
168970
168929
  });
168971
168930
  const message = (response.messages ?? []).find((entry) => entry.ts === params.messageTs);
168972
168931
  if (message) {
168973
- return resolveSlackMessageAttachments(message, attachmentOptions);
168932
+ return resolveSlackMessageAttachments(message, attachmentOptions, params.threadTs);
168974
168933
  }
168975
168934
  const nextCursor = response.response_metadata?.next_cursor;
168976
168935
  cursor = typeof nextCursor === "string" && nextCursor.trim().length > 0 ? nextCursor.trim() : undefined;
@@ -168996,7 +168955,7 @@ async function resolveSlackThreadStarter(params) {
168996
168955
  if (!hasSlackThreadMessageContent(message, attachmentOptions)) {
168997
168956
  return null;
168998
168957
  }
168999
- const mapped3 = await mapSlackThreadMessage(message, attachmentOptions);
168958
+ const mapped3 = await mapSlackThreadMessage(message, attachmentOptions, params.threadTs);
169000
168959
  return hasHydratedSlackThreadMessageContent(mapped3) ? mapped3 : null;
169001
168960
  } catch {
169002
168961
  return null;
@@ -169041,7 +169000,7 @@ async function resolveSlackThreadHistory(params) {
169041
169000
  const nextCursor = response.response_metadata?.next_cursor;
169042
169001
  cursor = typeof nextCursor === "string" && nextCursor.trim().length > 0 ? nextCursor.trim() : undefined;
169043
169002
  } while (cursor);
169044
- const mapped3 = await Promise.all(retained.map((message) => mapSlackThreadMessage(message, attachmentOptions)));
169003
+ const mapped3 = await Promise.all(retained.map((message) => mapSlackThreadMessage(message, attachmentOptions, params.threadTs)));
169045
169004
  return mapped3.filter(hasHydratedSlackThreadMessageContent);
169046
169005
  } catch {
169047
169006
  return [];
@@ -169075,7 +169034,7 @@ async function resolveSlackChannelHistory(params) {
169075
169034
  }
169076
169035
  var MAX_SLACK_ATTACHMENTS = 8, MAX_SLACK_ATTACHMENT_BYTES, ALLOWED_SLACK_HOST_SUFFIXES;
169077
169036
  var init_media2 = __esm(() => {
169078
- init_config2();
169037
+ init_attachment_stream();
169079
169038
  MAX_SLACK_ATTACHMENT_BYTES = 20 * 1024 * 1024;
169080
169039
  ALLOWED_SLACK_HOST_SUFFIXES = [
169081
169040
  "slack.com",
@@ -169084,6 +169043,286 @@ var init_media2 = __esm(() => {
169084
169043
  ];
169085
169044
  });
169086
169045
 
169046
+ // src/channels/slack/attachment-download.ts
169047
+ function isNonEmptyString4(value) {
169048
+ return typeof value === "string" && value.trim().length > 0;
169049
+ }
169050
+ async function resolveCanonicalSlackMessage(params) {
169051
+ if (isNonEmptyString4(params.threadTs)) {
169052
+ let cursor;
169053
+ do {
169054
+ const response2 = await params.client.conversations.replies({
169055
+ channel: params.channelId,
169056
+ ts: params.threadTs,
169057
+ limit: 200,
169058
+ inclusive: true,
169059
+ ...cursor ? { cursor } : {}
169060
+ });
169061
+ const message = (response2.messages ?? []).find((entry) => entry.ts === params.messageTs);
169062
+ if (message) {
169063
+ return message;
169064
+ }
169065
+ const nextCursor = response2.response_metadata?.next_cursor;
169066
+ cursor = isNonEmptyString4(nextCursor) ? nextCursor.trim() : undefined;
169067
+ } while (cursor);
169068
+ return null;
169069
+ }
169070
+ const response = await params.client.conversations.history({
169071
+ channel: params.channelId,
169072
+ oldest: params.messageTs,
169073
+ latest: params.messageTs,
169074
+ inclusive: true,
169075
+ limit: 1
169076
+ });
169077
+ return (response.messages ?? []).find((entry) => entry.ts === params.messageTs) ?? null;
169078
+ }
169079
+ async function downloadSlackAttachmentById(params) {
169080
+ const message = await resolveCanonicalSlackMessage(params);
169081
+ if (!message) {
169082
+ throw new Error(`Slack message ${params.messageTs} was not found in chat ${params.channelId}.`);
169083
+ }
169084
+ const file3 = collectSlackFiles(message).find((entry) => entry.id === params.attachmentId);
169085
+ if (!file3) {
169086
+ throw new Error(`Slack attachment ${params.attachmentId} is not attached to message ${params.messageTs}.`);
169087
+ }
169088
+ return await materializeSlackAttachment({
169089
+ accountId: params.accountId,
169090
+ token: params.token,
169091
+ file: file3,
169092
+ sourceMessageId: params.messageTs,
169093
+ sourceThreadId: params.threadTs ?? null
169094
+ });
169095
+ }
169096
+ var init_attachment_download = __esm(() => {
169097
+ init_media2();
169098
+ });
169099
+
169100
+ // src/channels/slack/file-upload.ts
169101
+ import { readFile as readFile4 } from "node:fs/promises";
169102
+ import { basename as basename8, extname as extname4 } from "node:path";
169103
+ function resolveUploadMimeType(filePath) {
169104
+ switch (extname4(filePath).toLowerCase()) {
169105
+ case ".png":
169106
+ return "image/png";
169107
+ case ".jpg":
169108
+ case ".jpeg":
169109
+ return "image/jpeg";
169110
+ case ".gif":
169111
+ return "image/gif";
169112
+ case ".webp":
169113
+ return "image/webp";
169114
+ case ".pdf":
169115
+ return "application/pdf";
169116
+ case ".txt":
169117
+ return "text/plain";
169118
+ case ".md":
169119
+ return "text/markdown";
169120
+ default:
169121
+ return;
169122
+ }
169123
+ }
169124
+ async function uploadSlackFile(slackClient, msg) {
169125
+ if (!msg.mediaPath) {
169126
+ throw new Error("mediaPath is required for Slack file uploads.");
169127
+ }
169128
+ const buffer = await readFile4(msg.mediaPath);
169129
+ const uploadFileName = msg.fileName ?? basename8(msg.mediaPath);
169130
+ const uploadTitle = msg.title ?? uploadFileName;
169131
+ const uploadMimeType = resolveUploadMimeType(uploadFileName);
169132
+ const uploadUrlResp = await slackClient.files.getUploadURLExternal({
169133
+ filename: uploadFileName,
169134
+ length: buffer.length
169135
+ });
169136
+ if (!uploadUrlResp.ok || !uploadUrlResp.upload_url || !uploadUrlResp.file_id) {
169137
+ throw new Error(`Failed to get Slack upload URL: ${uploadUrlResp.error ?? "unknown error"}`);
169138
+ }
169139
+ const uploadResp = await fetch(uploadUrlResp.upload_url, {
169140
+ method: "POST",
169141
+ ...uploadMimeType ? { headers: { "Content-Type": uploadMimeType } } : {},
169142
+ body: buffer
169143
+ });
169144
+ if (!uploadResp.ok) {
169145
+ throw new Error(`Failed to upload Slack file: HTTP ${uploadResp.status}`);
169146
+ }
169147
+ const threadTs = resolveSlackOutboundThreadTs({
169148
+ chatId: msg.chatId,
169149
+ threadId: msg.threadId,
169150
+ replyToMessageId: msg.replyToMessageId
169151
+ });
169152
+ const completeResp = await slackClient.files.completeUploadExternal({
169153
+ files: [{ id: uploadUrlResp.file_id, title: uploadTitle }],
169154
+ channel_id: msg.chatId,
169155
+ ...msg.text.trim() ? { initial_comment: msg.text } : {},
169156
+ ...threadTs ? { thread_ts: threadTs } : {}
169157
+ });
169158
+ if (!completeResp.ok) {
169159
+ throw new Error(`Failed to complete Slack upload: ${completeResp.error ?? "unknown error"}`);
169160
+ }
169161
+ return { messageId: uploadUrlResp.file_id };
169162
+ }
169163
+ var init_file_upload = __esm(() => {
169164
+ init_utils5();
169165
+ });
169166
+
169167
+ // src/channels/slack/inbound-debounce.ts
169168
+ function buildSlackDebounceKey(rawMessage, accountId) {
169169
+ const senderId = rawMessage.user ?? rawMessage.bot_id ?? null;
169170
+ if (!senderId)
169171
+ return null;
169172
+ const messageTs = rawMessage.ts ?? rawMessage.event_ts;
169173
+ const isDm = rawMessage.channel.startsWith("D");
169174
+ const scope = rawMessage.thread_ts ? `${rawMessage.channel}:${rawMessage.thread_ts}` : rawMessage.parent_user_id && messageTs ? `${rawMessage.channel}:maybe-thread:${messageTs}` : messageTs && !isDm ? `${rawMessage.channel}:${messageTs}` : rawMessage.channel;
169175
+ return `slack:${accountId}:${scope}:${senderId}`;
169176
+ }
169177
+ function buildTopLevelSlackConversationKey(rawMessage, accountId) {
169178
+ if (rawMessage.thread_ts || rawMessage.parent_user_id)
169179
+ return null;
169180
+ if (rawMessage.channel.startsWith("D"))
169181
+ return null;
169182
+ const senderId = rawMessage.user ?? rawMessage.bot_id;
169183
+ return senderId ? `slack:${accountId}:${rawMessage.channel}:${senderId}` : null;
169184
+ }
169185
+ function resolveSlackInboundDebounceMs(config3) {
169186
+ const raw = process.env.LETTA_SLACK_INBOUND_DEBOUNCE_MS;
169187
+ if (typeof raw === "string" && raw.trim() !== "") {
169188
+ const envOverride = Number(raw);
169189
+ if (Number.isFinite(envOverride) && envOverride >= 0) {
169190
+ return Math.trunc(envOverride);
169191
+ }
169192
+ }
169193
+ const fromConfig = config3.inboundDebounceMs;
169194
+ return typeof fromConfig === "number" && Number.isFinite(fromConfig) && fromConfig >= 0 ? Math.trunc(fromConfig) : 0;
169195
+ }
169196
+ function createSlackInboundDebounceController(params) {
169197
+ const { config: config3 } = params;
169198
+ const debounceMs = resolveSlackInboundDebounceMs(config3);
169199
+ const pendingTopLevelKeys = new Map;
169200
+ const appMentionRetryKeys = new Map;
169201
+ const appMentionDispatchedKeys = new Map;
169202
+ function pruneAppMentionMaps(now) {
169203
+ for (const [key, expiresAt] of appMentionRetryKeys) {
169204
+ if (expiresAt <= now)
169205
+ appMentionRetryKeys.delete(key);
169206
+ }
169207
+ for (const [key, expiresAt] of appMentionDispatchedKeys) {
169208
+ if (expiresAt <= now)
169209
+ appMentionDispatchedKeys.delete(key);
169210
+ }
169211
+ }
169212
+ function dedupeEntries(entries) {
169213
+ const indexByMessageKey = new Map;
169214
+ const deduped = [];
169215
+ for (const entry of entries) {
169216
+ const messageId = entry.inbound.messageId;
169217
+ const messageKey = isNonEmptyString2(messageId) ? `${entry.inbound.chatId}:${messageId}` : null;
169218
+ if (!messageKey) {
169219
+ deduped.push(entry);
169220
+ continue;
169221
+ }
169222
+ const existingIndex = indexByMessageKey.get(messageKey);
169223
+ if (existingIndex === undefined) {
169224
+ indexByMessageKey.set(messageKey, deduped.length);
169225
+ deduped.push(entry);
169226
+ continue;
169227
+ }
169228
+ const current = deduped[existingIndex];
169229
+ if (current && current.opts.source !== "app_mention" && current.inbound.isMention !== true) {
169230
+ deduped[existingIndex] = entry;
169231
+ }
169232
+ }
169233
+ return deduped;
169234
+ }
169235
+ const debouncer = createInboundDebouncer({
169236
+ debounceMs,
169237
+ buildKey: ({ raw }) => buildSlackDebounceKey(raw, config3.accountId),
169238
+ shouldDebounce: ({ inbound }) => !inbound.attachments?.length && !inbound.reaction,
169239
+ onFlush: async (entries) => {
169240
+ const dedupedEntries = dedupeEntries(entries);
169241
+ const last = dedupedEntries[dedupedEntries.length - 1];
169242
+ if (!last)
169243
+ return;
169244
+ const flushedKey = buildSlackDebounceKey(last.raw, config3.accountId);
169245
+ const conversationKey = buildTopLevelSlackConversationKey(last.raw, config3.accountId);
169246
+ if (flushedKey && conversationKey) {
169247
+ const pending = pendingTopLevelKeys.get(conversationKey);
169248
+ pending?.delete(flushedKey);
169249
+ if (pending?.size === 0)
169250
+ pendingTopLevelKeys.delete(conversationKey);
169251
+ }
169252
+ if (isNonEmptyString2(last.inbound.messageId)) {
169253
+ const seenKey = `${last.inbound.chatId}:${last.inbound.messageId}`;
169254
+ pruneAppMentionMaps(Date.now());
169255
+ if (last.opts.source === "app_mention") {
169256
+ appMentionDispatchedKeys.set(seenKey, Date.now() + APP_MENTION_RETRY_TTL_MS);
169257
+ } else if (appMentionDispatchedKeys.has(seenKey)) {
169258
+ appMentionDispatchedKeys.delete(seenKey);
169259
+ appMentionRetryKeys.delete(seenKey);
169260
+ return;
169261
+ }
169262
+ appMentionRetryKeys.delete(seenKey);
169263
+ }
169264
+ const onMessage = params.getOnMessage();
169265
+ if (!onMessage)
169266
+ return;
169267
+ const text = dedupedEntries.length === 1 ? last.inbound.text : dedupedEntries.map((entry) => entry.inbound.text).filter(Boolean).join(`
169268
+ `);
169269
+ try {
169270
+ await onMessage({
169271
+ ...last.inbound,
169272
+ text,
169273
+ isMention: dedupedEntries.some((entry) => entry.opts.wasMentioned || entry.inbound.isMention === true)
169274
+ });
169275
+ } catch (error54) {
169276
+ console.error("[Slack] Error handling debounced inbound message:", error54);
169277
+ }
169278
+ },
169279
+ onError: (error54) => {
169280
+ console.error("[Slack] Inbound debounce flush failed:", error54 instanceof Error ? error54.message : error54);
169281
+ }
169282
+ });
169283
+ return {
169284
+ async dispatch(entry) {
169285
+ const debounceKey = buildSlackDebounceKey(entry.raw, config3.accountId);
169286
+ const conversationKey = buildTopLevelSlackConversationKey(entry.raw, config3.accountId);
169287
+ const canDebounce = debounceMs > 0 && !entry.inbound.attachments?.length && !entry.inbound.reaction && Boolean(debounceKey);
169288
+ if (!canDebounce && conversationKey) {
169289
+ for (const pendingKey of Array.from(pendingTopLevelKeys.get(conversationKey) ?? [])) {
169290
+ try {
169291
+ await debouncer.flushKey(pendingKey);
169292
+ } catch {}
169293
+ }
169294
+ }
169295
+ if (canDebounce && debounceKey && conversationKey) {
169296
+ const pending = pendingTopLevelKeys.get(conversationKey) ?? new Set;
169297
+ pending.add(debounceKey);
169298
+ pendingTopLevelKeys.set(conversationKey, pending);
169299
+ }
169300
+ await debouncer.enqueue(entry);
169301
+ },
169302
+ rememberAppMentionRetry(seenKey) {
169303
+ const now = Date.now();
169304
+ pruneAppMentionMaps(now);
169305
+ appMentionRetryKeys.set(seenKey, now + APP_MENTION_RETRY_TTL_MS);
169306
+ },
169307
+ consumeAppMentionRetry(seenKey) {
169308
+ pruneAppMentionMaps(Date.now());
169309
+ if (!appMentionRetryKeys.has(seenKey))
169310
+ return false;
169311
+ appMentionRetryKeys.delete(seenKey);
169312
+ return true;
169313
+ },
169314
+ clear() {
169315
+ pendingTopLevelKeys.clear();
169316
+ appMentionRetryKeys.clear();
169317
+ appMentionDispatchedKeys.clear();
169318
+ }
169319
+ };
169320
+ }
169321
+ var APP_MENTION_RETRY_TTL_MS = 60000;
169322
+ var init_inbound_debounce = __esm(() => {
169323
+ init_utils5();
169324
+ });
169325
+
169087
169326
  // src/channels/slack/ingress-controller.ts
169088
169327
  function createSlackIngressController(params) {
169089
169328
  const { config: config3 } = params;
@@ -169816,6 +170055,18 @@ function createSlackAdapter(config3) {
169816
170055
  throw error54;
169817
170056
  }
169818
170057
  }
170058
+ async function downloadAttachment(params) {
170059
+ const slackApp = await ensureApp();
170060
+ return await downloadSlackAttachmentById({
170061
+ accountId: config3.accountId,
170062
+ token: config3.botToken,
170063
+ attachmentId: params.attachmentId,
170064
+ channelId: params.chatId,
170065
+ threadTs: params.threadId,
170066
+ messageTs: params.messageId,
170067
+ client: slackApp.client
170068
+ });
170069
+ }
169819
170070
  async function sendLifecycleErrorReply(source2, errorText) {
169820
170071
  const threadTs = resolveSlackSourceThreadTs(source2);
169821
170072
  const text = formatSlackLifecycleErrorMessage(errorText);
@@ -170018,6 +170269,7 @@ function createSlackAdapter(config3) {
170018
170269
  await Promise.all(status.getUniqueSources(event2.sources).map((source2) => status.activate(source2, SLACK_ASSISTANT_WORKING_STATUS, activity)));
170019
170270
  },
170020
170271
  sendMessage,
170272
+ downloadAttachment,
170021
170273
  sendDirectReply,
170022
170274
  handleControlRequestEvent,
170023
170275
  prepareInboundMessage: (msg, options3) => prepareSlackInboundMessage({
@@ -170037,6 +170289,7 @@ var init_adapter3 = __esm(async () => {
170037
170289
  init_model_picker_blocks();
170038
170290
  init_error_reporting();
170039
170291
  init_agent_thread_tracker();
170292
+ init_attachment_download();
170040
170293
  init_file_upload();
170041
170294
  init_inbound_debounce();
170042
170295
  init_ingress_controller();
@@ -170427,13 +170680,47 @@ async function reactInSlack(ctx) {
170427
170680
  });
170428
170681
  return request.remove ? `Reaction removed on slack (message_id: ${result.messageId})` : `Reaction added on slack (message_id: ${result.messageId})`;
170429
170682
  }
170683
+ async function downloadSlackFile(ctx) {
170684
+ const { request } = ctx;
170685
+ if (!request.attachmentId?.trim()) {
170686
+ return "Error: Slack download-file requires attachmentId.";
170687
+ }
170688
+ if (!request.messageId?.trim()) {
170689
+ return "Error: Slack download-file requires messageId from the attachment's Slack context.";
170690
+ }
170691
+ if (typeof ctx.adapter.downloadAttachment !== "function") {
170692
+ return "Error: Running Slack adapter does not support attachment downloads.";
170693
+ }
170694
+ const attachment = await ctx.adapter.downloadAttachment({
170695
+ attachmentId: request.attachmentId,
170696
+ chatId: request.chatId,
170697
+ threadId: request.threadId ?? null,
170698
+ messageId: request.messageId
170699
+ });
170700
+ if (!attachment.localPath) {
170701
+ return `Error: Slack attachment ${request.attachmentId} was not downloaded.`;
170702
+ }
170703
+ return `Slack attachment downloaded (local_path: ${attachment.localPath})`;
170704
+ }
170430
170705
  var slackMessageActions;
170431
170706
  var init_message_actions2 = __esm(() => {
170432
170707
  init_target_resolution();
170433
170708
  slackMessageActions = {
170434
170709
  describeMessageTool() {
170435
170710
  return {
170436
- actions: ["send", "react", "upload-file"]
170711
+ actions: ["send", "react", "upload-file", "download-file"],
170712
+ schema: {
170713
+ properties: {
170714
+ attachmentId: {
170715
+ type: "string",
170716
+ description: "Slack attachment id for action='download-file'. Copy attachment_id from the channel notification."
170717
+ },
170718
+ messageId: {
170719
+ type: "string",
170720
+ description: "Target Slack message id for action='react', or the source message id containing attachmentId for action='download-file'."
170721
+ }
170722
+ }
170723
+ }
170437
170724
  };
170438
170725
  },
170439
170726
  async resolveMessageTarget(params) {
@@ -170453,6 +170740,8 @@ var init_message_actions2 = __esm(() => {
170453
170740
  return await sendSlackMessage(ctx);
170454
170741
  case "react":
170455
170742
  return await reactInSlack(ctx);
170743
+ case "download-file":
170744
+ return await downloadSlackFile(ctx);
170456
170745
  default:
170457
170746
  return `Error: Action "${ctx.request.action}" is not supported on slack.`;
170458
170747
  }
@@ -170692,7 +170981,7 @@ function resolveDiscordChannelMode(channelId, parentChannelId, isThread, allowed
170692
170981
  // src/channels/discord/media.ts
170693
170982
  import { randomUUID as randomUUID10 } from "node:crypto";
170694
170983
  import { mkdirSync as mkdirSync6 } from "node:fs";
170695
- import { writeFile as writeFile7 } from "node:fs/promises";
170984
+ import { writeFile as writeFile6 } from "node:fs/promises";
170696
170985
  import { tmpdir as tmpdir2 } from "node:os";
170697
170986
  import { join as join13 } from "node:path";
170698
170987
  function ensureAttachmentsDir() {
@@ -170756,7 +171045,7 @@ async function resolveDiscordInboundAttachments(params) {
170756
171045
  console.warn(`[Discord] Skipping oversized attachment ${name}: ${buffer.byteLength} bytes`);
170757
171046
  continue;
170758
171047
  }
170759
- await writeFile7(localPath, buffer);
171048
+ await writeFile6(localPath, buffer);
170760
171049
  const entry = {
170761
171050
  id: attachment.id,
170762
171051
  name,
@@ -170862,7 +171151,7 @@ function formatDiscordDeliveryError(error54) {
170862
171151
  }
170863
171152
 
170864
171153
  // src/channels/discord/utils.ts
170865
- function isNonEmptyString4(value) {
171154
+ function isNonEmptyString5(value) {
170866
171155
  return typeof value === "string" && value.length > 0;
170867
171156
  }
170868
171157
  function isDiscordTextChannel(channel) {
@@ -170929,7 +171218,7 @@ function shouldAutoThreadOnDiscordMention(account, channelId) {
170929
171218
  return account.autoThreadOnMention ?? false;
170930
171219
  }
170931
171220
  function buildDiscordIngressMessageKey(accountId, messageId) {
170932
- if (!isNonEmptyString4(accountId) || !isNonEmptyString4(messageId)) {
171221
+ if (!isNonEmptyString5(accountId) || !isNonEmptyString5(messageId)) {
170933
171222
  return null;
170934
171223
  }
170935
171224
  return `${accountId}:${messageId}`;
@@ -171013,13 +171302,13 @@ function createDiscordAdapter(config3) {
171013
171302
  return false;
171014
171303
  }
171015
171304
  function getLifecycleMessageKey(source2) {
171016
- if (source2.channel !== "discord" || !isNonEmptyString4(source2.chatId) || !isNonEmptyString4(source2.messageId)) {
171305
+ if (source2.channel !== "discord" || !isNonEmptyString5(source2.chatId) || !isNonEmptyString5(source2.messageId)) {
171017
171306
  return null;
171018
171307
  }
171019
171308
  return `${source2.chatId}:${source2.messageId}`;
171020
171309
  }
171021
171310
  function getLifecycleReplyKey(source2) {
171022
- if (source2.channel !== "discord" || !isNonEmptyString4(source2.chatId)) {
171311
+ if (source2.channel !== "discord" || !isNonEmptyString5(source2.chatId)) {
171023
171312
  return null;
171024
171313
  }
171025
171314
  return [
@@ -171032,7 +171321,7 @@ function createDiscordAdapter(config3) {
171032
171321
  if (source2.channel !== "discord")
171033
171322
  return null;
171034
171323
  const channelId = source2.threadId ?? source2.chatId;
171035
- return isNonEmptyString4(channelId) ? channelId : null;
171324
+ return isNonEmptyString5(channelId) ? channelId : null;
171036
171325
  }
171037
171326
  function getTypingSourceKey(source2) {
171038
171327
  const channelId = getTypingChannelId(source2);
@@ -171084,7 +171373,7 @@ function createDiscordAdapter(config3) {
171084
171373
  return true;
171085
171374
  }
171086
171375
  async function sendLifecycleReaction(source2, emoji3, remove = false) {
171087
- if (!client || !isNonEmptyString4(source2.messageId))
171376
+ if (!client || !isNonEmptyString5(source2.messageId))
171088
171377
  return;
171089
171378
  try {
171090
171379
  const channel = await client.channels.fetch(source2.chatId);
@@ -171623,7 +171912,7 @@ function createDiscordAdapter(config3) {
171623
171912
  clearTypingForChannel(chatId);
171624
171913
  },
171625
171914
  async prepareInboundMessage(msg, options3) {
171626
- if (!options3?.isFirstRouteTurn || msg.channel !== "discord" || msg.chatType !== "channel" || !isNonEmptyString4(msg.threadId) || !client) {
171915
+ if (!options3?.isFirstRouteTurn || msg.channel !== "discord" || msg.chatType !== "channel" || !isNonEmptyString5(msg.threadId) || !client) {
171627
171916
  return msg;
171628
171917
  }
171629
171918
  const starter = await resolveDiscordThreadStarter({
@@ -172044,7 +172333,7 @@ var WHATSAPP_PHONE_SUFFIX = "@s.whatsapp.net", WHATSAPP_LID_SUFFIX = "@lid", WHA
172044
172333
 
172045
172334
  // src/channels/whatsapp/media.ts
172046
172335
  import { randomUUID as randomUUID12 } from "node:crypto";
172047
- import { mkdir as mkdir5, writeFile as writeFile8 } from "node:fs/promises";
172336
+ import { mkdir as mkdir5, writeFile as writeFile7 } from "node:fs/promises";
172048
172337
  import { basename as basename10, extname as extname5, join as join14 } from "node:path";
172049
172338
  function unwrapWhatsAppMessageContent(message) {
172050
172339
  if (!message || typeof message !== "object")
@@ -172226,7 +172515,7 @@ async function collectWhatsAppAttachments(params) {
172226
172515
  if (!buffer) {
172227
172516
  return { attachments: [attachment] };
172228
172517
  }
172229
- await writeFile8(localPath, buffer);
172518
+ await writeFile7(localPath, buffer);
172230
172519
  attachment.localPath = localPath;
172231
172520
  if (isVoice && params.transcribeVoice) {
172232
172521
  const result = await transcribeAudioFile(localPath);
@@ -176039,7 +176328,7 @@ function isLocalAgentId2(agentId) {
176039
176328
  return isLocalAgentId(agentId);
176040
176329
  }
176041
176330
  function buildChatUrl(agentId, options3) {
176042
- const base2 = `${APP_BASE}/chat/${agentId}`;
176331
+ const base2 = `${CHAT_BASE}/chat/${agentId}`;
176043
176332
  const params = new URLSearchParams;
176044
176333
  if (options3?.view) {
176045
176334
  params.set("view", options3.view);
@@ -176066,11 +176355,16 @@ function buildAgentTerminalLink(agentId, options3, label = agentId) {
176066
176355
  const url2 = buildChatUrl(agentId, options3);
176067
176356
  return `\x1B]8;;${url2}\x1B\\${label}\x1B]8;;\x1B\\`;
176068
176357
  }
176069
- function buildAppUrl(path5) {
176070
- return `${APP_BASE}${path5}`;
176358
+ function buildChatWebUrl(path5) {
176359
+ return `${CHAT_BASE}${path5}`;
176360
+ }
176361
+ function buildPlatformUrl(path5) {
176362
+ return `${PLATFORM_BASE}${path5}`;
176071
176363
  }
176072
- var APP_BASE = "https://app.letta.com";
176073
- var init_app_urls = () => {};
176364
+ var CHAT_BASE = "https://chat.letta.com", PLATFORM_BASE = "https://platform.letta.com", LETTA_CHAT_API_KEYS_URL;
176365
+ var init_app_urls = __esm(() => {
176366
+ LETTA_CHAT_API_KEYS_URL = `${CHAT_BASE}/preferences/api-keys`;
176367
+ });
176074
176368
 
176075
176369
  // src/channels/registry-presentation.ts
176076
176370
  function channelDisplayName2(channelId) {
@@ -177257,14 +177551,19 @@ function escapeXmlText(text) {
177257
177551
  function escapeXmlAttribute(text) {
177258
177552
  return escapeXmlText(text).replace(/"/g, "&quot;").replace(/'/g, "&apos;");
177259
177553
  }
177554
+ function formatMebibytes(bytes) {
177555
+ const mebibytes = bytes / (1024 * 1024);
177556
+ const rounded = mebibytes >= 100 ? Math.round(mebibytes).toString() : mebibytes.toFixed(1);
177557
+ return `${rounded.replace(/\.0$/, "")} MiB`;
177558
+ }
177260
177559
  function hasNotificationAttachmentPaths(msg) {
177261
- if (msg.attachments?.length) {
177560
+ if (msg.attachments?.some((attachment) => attachment.localPath)) {
177262
177561
  return true;
177263
177562
  }
177264
- if (msg.threadContext?.starter?.attachments?.length) {
177563
+ if (msg.threadContext?.starter?.attachments?.some((attachment) => attachment.localPath)) {
177265
177564
  return true;
177266
177565
  }
177267
- return Boolean(msg.threadContext?.history?.some((entry) => entry.attachments?.length));
177566
+ return Boolean(msg.threadContext?.history?.some((entry) => entry.attachments?.some((attachment) => attachment.localPath)));
177268
177567
  }
177269
177568
  function buildChannelReminderText(msg) {
177270
177569
  const localTime = escapeXmlText(getLocalTime());
@@ -177287,7 +177586,7 @@ function buildChannelReminderText(msg) {
177287
177586
  lines.splice(lines.length - 2, 0, threadLine);
177288
177587
  }
177289
177588
  if (msg.channel === "slack") {
177290
- lines.splice(lines.length - 2, 0, 'On Slack, MessageChannel also supports action="react" with emoji + messageId, and action="upload-file" with media.', 'For Slack requests that require nontrivial work or several tool calls, send a short MessageChannel action="send" acknowledgement before starting other tools. This gives the Slack user verbal acknowledgement and a View in web link. Do not do this for no-ops, reaction-only responses, or simple no-tool answers.');
177589
+ lines.splice(lines.length - 2, 0, 'On Slack, MessageChannel also supports action="react" with emoji + messageId, action="upload-file" with media, and action="download-file" with attachmentId + messageId.', 'For Slack requests that require nontrivial work or several tool calls, send a short MessageChannel action="send" acknowledgement before starting other tools. This gives the Slack user verbal acknowledgement and a View in web link. Do not do this for no-ops, reaction-only responses, or simple no-tool answers.');
177291
177590
  }
177292
177591
  if (msg.channel === "telegram") {
177293
177592
  lines.splice(lines.length - 2, 0, 'On Telegram, MessageChannel also supports action="react" with emoji + messageId, and action="upload-file" with media.');
@@ -177307,11 +177606,13 @@ function buildChannelReminderText(msg) {
177307
177606
  return lines.join(`
177308
177607
  `);
177309
177608
  }
177310
- function buildAttachmentXml(attachment) {
177311
- const attrs = [
177312
- `kind="${escapeXmlAttribute(attachment.kind)}"`,
177313
- `local_path="${escapeXmlAttribute(attachment.localPath)}"`
177314
- ];
177609
+ function buildAttachmentXml(attachment, context3) {
177610
+ const attrs = [`kind="${escapeXmlAttribute(attachment.kind)}"`];
177611
+ if (attachment.localPath) {
177612
+ attrs.push(`local_path="${escapeXmlAttribute(attachment.localPath)}"`);
177613
+ } else {
177614
+ attrs.push('download_status="not_downloaded"');
177615
+ }
177315
177616
  if (attachment.id) {
177316
177617
  attrs.push(`attachment_id="${escapeXmlAttribute(attachment.id)}"`);
177317
177618
  }
@@ -177324,6 +177625,19 @@ function buildAttachmentXml(attachment) {
177324
177625
  if (typeof attachment.sizeBytes === "number") {
177325
177626
  attrs.push(`size_bytes="${attachment.sizeBytes}"`);
177326
177627
  }
177628
+ const sourceMessageId = attachment.sourceMessageId ?? context3.messageId;
177629
+ if (!attachment.localPath && sourceMessageId) {
177630
+ attrs.push(`source_message_id="${escapeXmlAttribute(sourceMessageId)}"`);
177631
+ }
177632
+ if (!attachment.localPath && attachment.sourceThreadId) {
177633
+ attrs.push(`source_thread_id="${escapeXmlAttribute(attachment.sourceThreadId)}"`);
177634
+ }
177635
+ if (attachment.downloadReason) {
177636
+ attrs.push(`download_reason="${escapeXmlAttribute(attachment.downloadReason)}"`);
177637
+ }
177638
+ if (typeof attachment.autoDownloadLimitBytes === "number") {
177639
+ attrs.push(`auto_download_limit_bytes="${attachment.autoDownloadLimitBytes}"`);
177640
+ }
177327
177641
  const children = [];
177328
177642
  if (attachment.transcription) {
177329
177643
  children.push(`<attempted_transcription>${escapeXmlText(attachment.transcription)}</attempted_transcription>`);
@@ -177331,6 +177645,17 @@ function buildAttachmentXml(attachment) {
177331
177645
  if (attachment.transcriptionError) {
177332
177646
  children.push(`<attempted_transcription_error>${escapeXmlText(attachment.transcriptionError)}</attempted_transcription_error>`);
177333
177647
  }
177648
+ if (!attachment.localPath && context3.channel === "slack" && attachment.id && sourceMessageId) {
177649
+ const accountArg = context3.accountId ? `, accountId="${escapeXmlAttribute(context3.accountId)}"` : "";
177650
+ const threadArg = attachment.sourceThreadId ? `, threadId="${escapeXmlAttribute(attachment.sourceThreadId)}"` : "";
177651
+ const action3 = `MessageChannel with action="download-file", channel="slack", chat_id="${escapeXmlAttribute(context3.chatId)}"${accountArg}${threadArg}, attachmentId="${escapeXmlAttribute(attachment.id)}", and messageId="${escapeXmlAttribute(sourceMessageId)}"`;
177652
+ if (attachment.downloadReason === "exceeds_auto_download_limit") {
177653
+ const sizeNote = typeof attachment.sizeBytes === "number" ? `This file is ${formatMebibytes(attachment.sizeBytes)}${typeof attachment.autoDownloadLimitBytes === "number" ? `, above the ${formatMebibytes(attachment.autoDownloadLimitBytes)} automatic download limit` : ""}. ` : "";
177654
+ children.push(`<download-instruction>${sizeNote}Call ${action3}. The tool downloads the file into the same Slack inbound attachment directory and returns its local_path. Do not ask the sender to reattach it.</download-instruction>`);
177655
+ } else {
177656
+ children.push(`<download-retry>Automatic download did not complete. Call ${action3} to retry. The action may return a precise error if Slack still cannot provide the file.</download-retry>`);
177657
+ }
177658
+ }
177334
177659
  if (children.length > 0) {
177335
177660
  return `<attachment ${attrs.join(" ")}>
177336
177661
  ${children.join(`
@@ -177380,7 +177705,7 @@ ${escapeXmlText(replyContext.text)}
177380
177705
  }
177381
177706
  return `<reply-context${attrString} />`;
177382
177707
  }
177383
- function buildThreadContextEntryXml(tagName, entry) {
177708
+ function buildThreadContextEntryXml(tagName, entry, context3) {
177384
177709
  const attrs = [];
177385
177710
  if (entry.senderId) {
177386
177711
  attrs.push(`sender_id="${escapeXmlAttribute(entry.senderId)}"`);
@@ -177394,7 +177719,10 @@ function buildThreadContextEntryXml(tagName, entry) {
177394
177719
  const attrString = attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
177395
177720
  const body = [
177396
177721
  ...entry.text ? [escapeXmlText(entry.text)] : [],
177397
- ...(entry.attachments ?? []).map(buildAttachmentXml)
177722
+ ...(entry.attachments ?? []).map((attachment) => buildAttachmentXml(attachment, {
177723
+ ...context3,
177724
+ messageId: entry.messageId
177725
+ }))
177398
177726
  ].join(`
177399
177727
  `);
177400
177728
  return `<${tagName}${attrString}>
@@ -177408,13 +177736,21 @@ function buildThreadContextXml(msg) {
177408
177736
  }
177409
177737
  const parts = [];
177410
177738
  if (threadContext.starter) {
177411
- parts.push(buildThreadContextEntryXml("thread-starter", threadContext.starter));
177739
+ parts.push(buildThreadContextEntryXml("thread-starter", threadContext.starter, {
177740
+ channel: msg.channel,
177741
+ accountId: msg.accountId,
177742
+ chatId: msg.chatId
177743
+ }));
177412
177744
  }
177413
177745
  const historyEntries = threadContext.history ?? [];
177414
177746
  if (historyEntries.length > 0) {
177415
177747
  parts.push([
177416
177748
  "<thread-history>",
177417
- ...historyEntries.map((entry) => buildThreadContextEntryXml("thread-message", entry)),
177749
+ ...historyEntries.map((entry) => buildThreadContextEntryXml("thread-message", entry, {
177750
+ channel: msg.channel,
177751
+ accountId: msg.accountId,
177752
+ chatId: msg.chatId
177753
+ })),
177418
177754
  "</thread-history>"
177419
177755
  ].join(`
177420
177756
  `));
@@ -177449,7 +177785,12 @@ function buildChannelNotificationXml(msg) {
177449
177785
  const reactionXml = buildReactionXml(msg);
177450
177786
  const replyContextXml = buildReplyContextXml(msg);
177451
177787
  const threadContextXml = buildThreadContextXml(msg);
177452
- const attachmentXml = (msg.attachments ?? []).map(buildAttachmentXml);
177788
+ const attachmentXml = (msg.attachments ?? []).map((attachment) => buildAttachmentXml(attachment, {
177789
+ channel: msg.channel,
177790
+ accountId: msg.accountId,
177791
+ chatId: msg.chatId,
177792
+ messageId: msg.messageId
177793
+ }));
177453
177794
  const body = [
177454
177795
  threadContextXml,
177455
177796
  replyContextXml,
@@ -179705,7 +180046,10 @@ This tool is currently scoped to a routed external channel turn. Plain assistant
179705
180046
  const slackWorkAcknowledgement = discovery.activeChannels.includes("slack") ? `
179706
180047
 
179707
180048
  For Slack requests that require nontrivial work or several tool calls, send one short MessageChannel call with action="send" before starting other tools. This gives the Slack user verbal acknowledgement and a View in web link. Do not do this for no-ops, reaction-only responses, or simple no-tool answers.` : "";
179708
- return `${description}${scopedReplyContract}${slackWorkAcknowledgement}
180049
+ const slackAttachmentDownload = discovery.activeChannels.includes("slack") ? `
180050
+
180051
+ Slack attachments that exceed the automatic download limit include an exact recovery instruction. Use action="download-file" with channel, chat_id, attachmentId, and messageId from that instruction. The action saves the file in the normal Slack inbound attachment directory and returns its local_path.` : "";
180052
+ return `${description}${scopedReplyContract}${slackWorkAcknowledgement}${slackAttachmentDownload}
179709
180053
 
179710
180054
  Currently active channels: ${channelList}. Available actions across the active channels: ${actionList}. The JSON schema reflects the currently active channel plugins.`;
179711
180055
  }
@@ -183403,7 +183747,7 @@ var init_apply_patch = __esm(() => {
183403
183747
  });
183404
183748
 
183405
183749
  // src/tools/impl/artifact-files.ts
183406
- import { mkdir as mkdir6, readFile as readFile6, stat as stat3, writeFile as writeFile9 } from "node:fs/promises";
183750
+ import { mkdir as mkdir6, readFile as readFile5, stat as stat3, writeFile as writeFile8 } from "node:fs/promises";
183407
183751
  import { homedir as homedir9 } from "node:os";
183408
183752
  import { dirname as dirname7, isAbsolute as isAbsolute5, join as join19, relative as relative2, resolve as resolve8 } from "node:path";
183409
183753
  function assertArtifactsExperimentEnabled(toolName) {
@@ -183461,7 +183805,7 @@ async function read_artifact_file(args) {
183461
183805
  if (stats.size > MAX_READ_FILE_BYTES) {
183462
183806
  throw new Error(`read_artifact_file: file is too large to read (${stats.size} bytes)`);
183463
183807
  }
183464
- const buffer = await readFile6(absolutePath);
183808
+ const buffer = await readFile5(absolutePath);
183465
183809
  return {
183466
183810
  path: relativePath,
183467
183811
  content: buffer.toString(encoding),
@@ -183475,7 +183819,7 @@ async function write_artifact_file(args) {
183475
183819
  const encoding = getEncoding(args.encoding);
183476
183820
  const buffer = Buffer.from(args.content, encoding);
183477
183821
  await mkdir6(dirname7(absolutePath), { recursive: true });
183478
- await writeFile9(absolutePath, buffer);
183822
+ await writeFile8(absolutePath, buffer);
183479
183823
  return {
183480
183824
  path: relativePath,
183481
183825
  bytes: buffer.byteLength,
@@ -184164,6 +184508,9 @@ class TurnLifecycle {
184164
184508
  get activeRunId() {
184165
184509
  return this.#state.kind === "active" ? this.#state.runId : null;
184166
184510
  }
184511
+ get executingToolCallIds() {
184512
+ return this.#state.kind === "active" || this.#state.kind === "cancelling" ? this.#state.executingToolCallIds : [];
184513
+ }
184167
184514
  get lastStopReason() {
184168
184515
  return this.#lastStopReason;
184169
184516
  }
@@ -187318,7 +187665,7 @@ var init_worktree_lock = __esm(() => {
187318
187665
 
187319
187666
  // src/websocket/listener/remote-settings.ts
187320
187667
  import { existsSync as existsSync20, readFileSync as readFileSync16 } from "node:fs";
187321
- import { mkdir as mkdir7, writeFile as writeFile10 } from "node:fs/promises";
187668
+ import { mkdir as mkdir7, writeFile as writeFile9 } from "node:fs/promises";
187322
187669
  import { homedir as homedir16 } from "node:os";
187323
187670
  import path12 from "node:path";
187324
187671
  function getRemoteSettingsPath() {
@@ -187362,7 +187709,7 @@ function saveRemoteSettings(updates) {
187362
187709
  };
187363
187710
  const snapshot = _cache;
187364
187711
  const settingsPath = getRemoteSettingsPath();
187365
- mkdir7(path12.dirname(settingsPath), { recursive: true }).then(() => writeFile10(settingsPath, JSON.stringify(snapshot, null, 2))).catch(() => {});
187712
+ mkdir7(path12.dirname(settingsPath), { recursive: true }).then(() => writeFile9(settingsPath, JSON.stringify(snapshot, null, 2))).catch(() => {});
187366
187713
  }
187367
187714
  function loadLegacyCwdCache() {
187368
187715
  try {
@@ -188157,7 +188504,7 @@ var require_filesystem = __commonJS((exports, module3) => {
188157
188504
  fs5.close(fd, () => {});
188158
188505
  return buffer.subarray(0, bytesRead);
188159
188506
  };
188160
- var readFile7 = (path13) => new Promise((resolve13, reject) => {
188507
+ var readFile6 = (path13) => new Promise((resolve13, reject) => {
188161
188508
  fs5.open(path13, "r", (err, fd) => {
188162
188509
  if (err) {
188163
188510
  reject(err);
@@ -188174,7 +188521,7 @@ var require_filesystem = __commonJS((exports, module3) => {
188174
188521
  LDD_PATH,
188175
188522
  SELF_PATH,
188176
188523
  readFileSync: readFileSync19,
188177
- readFile: readFile7
188524
+ readFile: readFile6
188178
188525
  };
188179
188526
  });
188180
188527
 
@@ -188216,7 +188563,7 @@ var require_elf = __commonJS((exports, module3) => {
188216
188563
  var require_detect_libc = __commonJS((exports, module3) => {
188217
188564
  var childProcess = __require("child_process");
188218
188565
  var { isLinux, getReport } = require_process();
188219
- var { LDD_PATH, SELF_PATH, readFile: readFile7, readFileSync: readFileSync19 } = require_filesystem();
188566
+ var { LDD_PATH, SELF_PATH, readFile: readFile6, readFileSync: readFileSync19 } = require_filesystem();
188220
188567
  var { interpreterPath } = require_elf();
188221
188568
  var cachedFamilyInterpreter;
188222
188569
  var cachedFamilyFilesystem;
@@ -188296,7 +188643,7 @@ var require_detect_libc = __commonJS((exports, module3) => {
188296
188643
  }
188297
188644
  cachedFamilyFilesystem = null;
188298
188645
  try {
188299
- const lddContent = await readFile7(LDD_PATH);
188646
+ const lddContent = await readFile6(LDD_PATH);
188300
188647
  cachedFamilyFilesystem = getFamilyFromLddContent(lddContent);
188301
188648
  } catch (e2) {}
188302
188649
  return cachedFamilyFilesystem;
@@ -188318,7 +188665,7 @@ var require_detect_libc = __commonJS((exports, module3) => {
188318
188665
  }
188319
188666
  cachedFamilyInterpreter = null;
188320
188667
  try {
188321
- const selfContent = await readFile7(SELF_PATH);
188668
+ const selfContent = await readFile6(SELF_PATH);
188322
188669
  const path13 = interpreterPath(selfContent);
188323
188670
  cachedFamilyInterpreter = familyFromInterpreterPath(path13);
188324
188671
  } catch (e2) {}
@@ -188378,7 +188725,7 @@ var require_detect_libc = __commonJS((exports, module3) => {
188378
188725
  }
188379
188726
  cachedVersionFilesystem = null;
188380
188727
  try {
188381
- const lddContent = await readFile7(LDD_PATH);
188728
+ const lddContent = await readFile6(LDD_PATH);
188382
188729
  const versionMatch = lddContent.match(RE_GLIBC_VERSION);
188383
188730
  if (versionMatch) {
188384
188731
  cachedVersionFilesystem = versionMatch[1];
@@ -194572,7 +194919,7 @@ __export(exports_image_resize_sips, {
194572
194919
  convertHeicToJpegWithSips: () => convertHeicToJpegWithSips
194573
194920
  });
194574
194921
  import { execFile as execFile2 } from "node:child_process";
194575
- import { mkdtemp, readFile as readFile7, rm as rm2, writeFile as writeFile11 } from "node:fs/promises";
194922
+ import { mkdtemp, readFile as readFile6, rm as rm3, writeFile as writeFile10 } from "node:fs/promises";
194576
194923
  import { tmpdir as tmpdir6 } from "node:os";
194577
194924
  import { join as join29 } from "node:path";
194578
194925
  function execFileAsync2(file3, args) {
@@ -194591,7 +194938,7 @@ async function convertHeicToJpegWithSips(buffer) {
194591
194938
  const inputPath = join29(workDir, "input.heic");
194592
194939
  const outputPath = join29(workDir, "output.jpg");
194593
194940
  try {
194594
- await writeFile11(inputPath, buffer);
194941
+ await writeFile10(inputPath, buffer);
194595
194942
  await execFileAsync2("/usr/bin/sips", [
194596
194943
  "-s",
194597
194944
  "format",
@@ -194603,9 +194950,9 @@ async function convertHeicToJpegWithSips(buffer) {
194603
194950
  "--out",
194604
194951
  outputPath
194605
194952
  ]);
194606
- return Buffer.from(await readFile7(outputPath));
194953
+ return Buffer.from(await readFile6(outputPath));
194607
194954
  } finally {
194608
- await rm2(workDir, { recursive: true, force: true });
194955
+ await rm3(workDir, { recursive: true, force: true });
194609
194956
  }
194610
194957
  }
194611
194958
  var init_image_resize_sips = () => {};
@@ -194685,13 +195032,40 @@ async function normalizeMessageContentImages(content, resize = resizeImageIfNeed
194685
195032
  const filteredParts = normalizedParts.filter((part) => part !== null);
194686
195033
  return didChange ? filteredParts : content;
194687
195034
  }
194688
- async function normalizeMessageImageParts(messages, resize = resizeImageIfNeeded3) {
195035
+ async function normalizeApprovalImages(message, resize, failureMode) {
195036
+ if (!Array.isArray(message.approvals)) {
195037
+ return message;
195038
+ }
194689
195039
  let didChange = false;
195040
+ const approvals = await Promise.all(message.approvals.map(async (approval) => {
195041
+ if (!("tool_return" in approval)) {
195042
+ return approval;
195043
+ }
195044
+ const toolReturn = await normalizeMessageContentImages(approval.tool_return, resize, failureMode);
195045
+ if (toolReturn === approval.tool_return) {
195046
+ return approval;
195047
+ }
195048
+ didChange = true;
195049
+ return {
195050
+ ...approval,
195051
+ tool_return: toolReturn
195052
+ };
195053
+ }));
195054
+ return didChange ? { ...message, approvals } : message;
195055
+ }
195056
+ async function normalizeMessageImageParts(messages, options3 = {}) {
195057
+ let didChange = false;
195058
+ const resize = options3.resize ?? resizeImageIfNeeded3;
194690
195059
  const normalizedMessages = await Promise.all(messages.map(async (message) => {
195060
+ const failureMode = getImageFailureMode(message, options3.failureModesByMessageOtid);
194691
195061
  if (!("content" in message)) {
194692
- return message;
195062
+ const normalizedApproval = await normalizeApprovalImages(message, resize, failureMode);
195063
+ if (normalizedApproval !== message) {
195064
+ didChange = true;
195065
+ }
195066
+ return normalizedApproval;
194693
195067
  }
194694
- const normalizedContent = await normalizeMessageContentImages(message.content, resize);
195068
+ const normalizedContent = await normalizeMessageContentImages(message.content, resize, failureMode);
194695
195069
  if (normalizedContent !== message.content) {
194696
195070
  didChange = true;
194697
195071
  return {
@@ -194703,18 +195077,51 @@ async function normalizeMessageImageParts(messages, resize = resizeImageIfNeeded
194703
195077
  }));
194704
195078
  return didChange ? normalizedMessages : messages;
194705
195079
  }
195080
+ function getImageFailureMode(message, failureModesByMessageOtid) {
195081
+ if (!failureModesByMessageOtid) {
195082
+ return "strict";
195083
+ }
195084
+ const otid = message.otid;
195085
+ return typeof otid === "string" ? failureModesByMessageOtid[otid] ?? "strict" : "strict";
195086
+ }
195087
+ function buildImageFailureModesByMessageOtid(messages, failureMode) {
195088
+ const entries = [];
195089
+ for (const message of messages) {
195090
+ if (!("content" in message)) {
195091
+ continue;
195092
+ }
195093
+ const otid = message.otid;
195094
+ if (typeof otid === "string" && otid.length > 0) {
195095
+ entries.push([otid, failureMode]);
195096
+ }
195097
+ }
195098
+ return entries.length > 0 ? Object.fromEntries(entries) : undefined;
195099
+ }
195100
+ function mergeImageFailureModesByMessageOtid(...failureModes) {
195101
+ const presentModes = failureModes.filter((modes) => modes !== undefined);
195102
+ return presentModes.length > 0 ? Object.assign({}, ...presentModes) : undefined;
195103
+ }
194706
195104
  function assertSupportedBase64ImageMediaTypes(messages) {
194707
195105
  for (const message of messages) {
194708
- if (!("content" in message) || typeof message.content === "string") {
195106
+ if ("content" in message) {
195107
+ assertSupportedBase64ImageContent(message.content);
194709
195108
  continue;
194710
195109
  }
194711
- for (const part of message.content) {
194712
- if (!isBase64ImageContentPart(part)) {
195110
+ for (const approval of message.approvals ?? []) {
195111
+ if (!("tool_return" in approval)) {
194713
195112
  continue;
194714
195113
  }
194715
- if (!isSupportedBase64ImageMediaType(part.source.media_type)) {
194716
- throw new Error(`Unsupported base64 image media type after normalization: ${part.source.media_type}`);
194717
- }
195114
+ assertSupportedBase64ImageContent(approval.tool_return);
195115
+ }
195116
+ }
195117
+ }
195118
+ function assertSupportedBase64ImageContent(content) {
195119
+ if (typeof content === "string") {
195120
+ return;
195121
+ }
195122
+ for (const part of content) {
195123
+ if (isBase64ImageContentPart(part) && !isSupportedBase64ImageMediaType(part.source.media_type)) {
195124
+ throw new Error(`Unsupported base64 image media type after normalization: ${part.source.media_type}`);
194718
195125
  }
194719
195126
  }
194720
195127
  }
@@ -194927,7 +195334,7 @@ __export(exports_skills2, {
194927
195334
  GLOBAL_SKILLS_DIR: () => GLOBAL_SKILLS_DIR
194928
195335
  });
194929
195336
  import { existsSync as existsSync22 } from "node:fs";
194930
- import { readdir as readdir3, readFile as readFile8, realpath, stat as stat4 } from "node:fs/promises";
195337
+ import { readdir as readdir3, readFile as readFile7, realpath, stat as stat4 } from "node:fs/promises";
194931
195338
  import { dirname as dirname12, join as join30 } from "node:path";
194932
195339
  import { fileURLToPath as fileURLToPath3 } from "node:url";
194933
195340
  function getBundledSkillsPath() {
@@ -195099,7 +195506,7 @@ async function findSkillFiles(currentPath, rootPath, skills, errors7, source2, v
195099
195506
  }
195100
195507
  }
195101
195508
  async function parseSkillFile(filePath, rootPath, source2) {
195102
- const content = await readFile8(filePath, "utf-8");
195509
+ const content = await readFile7(filePath, "utf-8");
195103
195510
  const { frontmatter, body } = parseFrontmatter(content);
195104
195511
  const normalizedRoot = rootPath.endsWith("/") ? rootPath.slice(0, -1) : rootPath;
195105
195512
  const relativePath = filePath.slice(normalizedRoot.length + 1);
@@ -195533,12 +195940,15 @@ function getStreamRequestContext(stream11) {
195533
195940
  return streamRequestContexts.get(stream11);
195534
195941
  }
195535
195942
  function buildConversationMessagesCreateRequestBody(conversationId, messages, opts = { streamTokens: true, background: true }, clientTools, clientSkills = []) {
195943
+ return buildRequestBodyFromPreparedMessages(conversationId, normalizeOutgoingApprovalMessages(messages, opts.approvalNormalization), opts, clientTools, clientSkills);
195944
+ }
195945
+ function buildRequestBodyFromPreparedMessages(conversationId, messages, opts, clientTools, clientSkills) {
195536
195946
  const isDefaultConversation = conversationId === "default";
195537
195947
  if (isDefaultConversation && !opts.agentId) {
195538
195948
  throw new Error("agentId is required in opts when using default conversation");
195539
195949
  }
195540
195950
  return {
195541
- messages: normalizeOutgoingApprovalMessages(messages, opts.approvalNormalization),
195951
+ messages,
195542
195952
  streaming: true,
195543
195953
  stream_tokens: opts.streamTokens ?? true,
195544
195954
  include_pings: true,
@@ -195560,7 +195970,10 @@ async function sendMessageStreamWithBackend(backend, conversationId, messages, o
195560
195970
  }) {
195561
195971
  const requestStartTime = isTimingsEnabled() ? performance.now() : undefined;
195562
195972
  const requestStartedAtMs = Date.now();
195563
- const normalizedMessages = opts.skipImageNormalization ? messages : await normalizeMessageImageParts(messages);
195973
+ const canonicalMessages = normalizeOutgoingApprovalMessages(messages, opts.approvalNormalization);
195974
+ const normalizedMessages = await normalizeMessageImageParts(canonicalMessages, {
195975
+ failureModesByMessageOtid: opts.imageFailureModesByMessageOtid
195976
+ });
195564
195977
  assertSupportedBase64ImageMediaTypes(normalizedMessages);
195565
195978
  const preparedToolContext = opts.preparedToolContext ? opts.preparedToolContext : await (async () => {
195566
195979
  await waitForToolsetReady();
@@ -195579,7 +195992,7 @@ async function sendMessageStreamWithBackend(backend, conversationId, messages, o
195579
195992
  const isApprovalContinuation = isApprovalContinuationRequest(normalizedMessages);
195580
195993
  const canUsePreviousResponseState = isApprovalContinuation && opts.allowResponseStateReuse === true;
195581
195994
  const previousResponseId = canUsePreviousResponseState ? responseStateIdsByScope.get(responseStateScope) : undefined;
195582
- const requestBody = buildConversationMessagesCreateRequestBody(conversationId, normalizedMessages, opts, clientTools, clientSkills);
195995
+ const requestBody = buildRequestBodyFromPreparedMessages(conversationId, normalizedMessages, opts, clientTools, clientSkills);
195583
195996
  if (isDebugEnabled()) {
195584
195997
  debugLog("agent-message", "sendMessageStream: conversationId=%s, agentId=%s", conversationId, opts.agentId ?? "(none)");
195585
195998
  const formattedSkills = clientSkills.map((skill) => `${skill.name} (${skill.location})`);
@@ -198391,7 +198804,7 @@ var require_typescript = __commonJS((exports, module3) => {
198391
198804
  walkUpParenthesizedTypesAndGetParentAndChild: () => walkUpParenthesizedTypesAndGetParentAndChild,
198392
198805
  whitespaceOrMapCommentRegExp: () => whitespaceOrMapCommentRegExp,
198393
198806
  writeCommentRange: () => writeCommentRange,
198394
- writeFile: () => writeFile12,
198807
+ writeFile: () => writeFile11,
198395
198808
  writeFileEnsuringDirectories: () => writeFileEnsuringDirectories,
198396
198809
  zipWith: () => zipWith
198397
198810
  });
@@ -204183,7 +204596,7 @@ ${lanes.join(`
204183
204596
  writeOutputIsTTY() {
204184
204597
  return process.stdout.isTTY;
204185
204598
  },
204186
- readFile: readFile9,
204599
+ readFile: readFile8,
204187
204600
  writeFile: writeFile22,
204188
204601
  watchFile: watchFile2,
204189
204602
  watchDirectory,
@@ -204376,7 +204789,7 @@ ${lanes.join(`
204376
204789
  function fsWatchWorker(fileOrDirectory, recursive, callback) {
204377
204790
  return _fs.watch(fileOrDirectory, fsSupportsRecursiveFsWatch ? { persistent: true, recursive: !!recursive } : { persistent: true }, callback);
204378
204791
  }
204379
- function readFile9(fileName, _encoding) {
204792
+ function readFile8(fileName, _encoding) {
204380
204793
  let buffer;
204381
204794
  try {
204382
204795
  buffer = _fs.readFileSync(fileName);
@@ -215700,7 +216113,7 @@ ${lanes.join(`
215700
216113
  sourceFilePath = isSourceFileInCommonSourceDirectory ? sourceFilePath.substring(commonSourceDirectory.length) : sourceFilePath;
215701
216114
  return combinePaths(newDirPath, sourceFilePath);
215702
216115
  }
215703
- function writeFile12(host, diagnostics2, fileName, text, writeByteOrderMark, sourceFiles, data) {
216116
+ function writeFile11(host, diagnostics2, fileName, text, writeByteOrderMark, sourceFiles, data) {
215704
216117
  host.writeFile(fileName, text, writeByteOrderMark, (hostErrorMessage) => {
215705
216118
  diagnostics2.add(createCompilerDiagnostic(Diagnostics.Could_not_write_file_0_Colon_1, fileName, hostErrorMessage));
215706
216119
  }, sourceFiles, data);
@@ -226153,8 +226566,8 @@ ${lanes.join(`
226153
226566
  return;
226154
226567
  }
226155
226568
  function tryRenameExternalModule(factory2, moduleName, sourceFile) {
226156
- const rename = sourceFile.renamedDependencies && sourceFile.renamedDependencies.get(moduleName.text);
226157
- return rename ? factory2.createStringLiteral(rename) : undefined;
226569
+ const rename2 = sourceFile.renamedDependencies && sourceFile.renamedDependencies.get(moduleName.text);
226570
+ return rename2 ? factory2.createStringLiteral(rename2) : undefined;
226158
226571
  }
226159
226572
  function tryGetModuleNameFromFile(factory2, file3, host, options3) {
226160
226573
  if (!file3) {
@@ -228683,8 +229096,8 @@ ${lanes.join(`
228683
229096
  function isMissingList(arr) {
228684
229097
  return !!arr.isMissingList;
228685
229098
  }
228686
- function parseBracketedList(kind, parseElement, open2, close) {
228687
- if (parseExpected(open2)) {
229099
+ function parseBracketedList(kind, parseElement, open3, close) {
229100
+ if (parseExpected(open3)) {
228688
229101
  const result = parseDelimitedList(kind, parseElement);
228689
229102
  parseExpected(close);
228690
229103
  return result;
@@ -230389,12 +230802,12 @@ ${lanes.join(`
230389
230802
  parseExpected(20);
230390
230803
  return finishNode(factory2.createJsxSpreadAttribute(expression), pos);
230391
230804
  }
230392
- function parseJsxClosingElement(open2, inExpressionContext) {
230805
+ function parseJsxClosingElement(open3, inExpressionContext) {
230393
230806
  const pos = getNodePos();
230394
230807
  parseExpected(31);
230395
230808
  const tagName = parseJsxElementName();
230396
230809
  if (parseExpected(32, undefined, false)) {
230397
- if (inExpressionContext || !tagNamesAreEquivalent(open2.tagName, tagName)) {
230810
+ if (inExpressionContext || !tagNamesAreEquivalent(open3.tagName, tagName)) {
230398
230811
  nextToken();
230399
230812
  } else {
230400
230813
  scanJsxText();
@@ -235323,7 +235736,7 @@ ${lanes.join(`
235323
235736
  const possibleOption = getSpellingSuggestion(unknownOption, diagnostics2.optionDeclarations, getOptionName);
235324
235737
  return possibleOption ? createDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, node, diagnostics2.unknownDidYouMeanDiagnostic, unknownOptionErrorText || unknownOption, possibleOption.name) : createDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, node, diagnostics2.unknownOptionDiagnostic, unknownOptionErrorText || unknownOption);
235325
235738
  }
235326
- function parseCommandLineWorker(diagnostics2, commandLine, readFile9) {
235739
+ function parseCommandLineWorker(diagnostics2, commandLine, readFile8) {
235327
235740
  const options3 = {};
235328
235741
  let watchOptions;
235329
235742
  const fileNames = [];
@@ -235361,7 +235774,7 @@ ${lanes.join(`
235361
235774
  }
235362
235775
  }
235363
235776
  function parseResponseFile(fileName) {
235364
- const text = tryReadFile(fileName, readFile9 || ((fileName2) => sys.readFile(fileName2)));
235777
+ const text = tryReadFile(fileName, readFile8 || ((fileName2) => sys.readFile(fileName2)));
235365
235778
  if (!isString(text)) {
235366
235779
  errors7.push(text);
235367
235780
  return;
@@ -235464,8 +235877,8 @@ ${lanes.join(`
235464
235877
  unknownDidYouMeanDiagnostic: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1,
235465
235878
  optionTypeMismatchDiagnostic: Diagnostics.Compiler_option_0_expects_an_argument
235466
235879
  };
235467
- function parseCommandLine(commandLine, readFile9) {
235468
- return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine, readFile9);
235880
+ function parseCommandLine(commandLine, readFile8) {
235881
+ return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine, readFile8);
235469
235882
  }
235470
235883
  function getOptionFromName(optionName, allowShort) {
235471
235884
  return getOptionDeclarationFromName(getOptionsNameMap, optionName, allowShort);
@@ -235533,8 +235946,8 @@ ${lanes.join(`
235533
235946
  result.originalFileName = result.fileName;
235534
235947
  return parseJsonSourceFileConfigFileContent(result, host, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), optionsToExtend, getNormalizedAbsolutePath(configFileName, cwd), undefined, extraFileExtensions, extendedConfigCache, watchOptionsToExtend);
235535
235948
  }
235536
- function readConfigFile(fileName, readFile9) {
235537
- const textOrDiagnostic = tryReadFile(fileName, readFile9);
235949
+ function readConfigFile(fileName, readFile8) {
235950
+ const textOrDiagnostic = tryReadFile(fileName, readFile8);
235538
235951
  return isString(textOrDiagnostic) ? parseConfigFileTextToJson(fileName, textOrDiagnostic) : { config: {}, error: textOrDiagnostic };
235539
235952
  }
235540
235953
  function parseConfigFileTextToJson(fileName, jsonText) {
@@ -235544,14 +235957,14 @@ ${lanes.join(`
235544
235957
  error: jsonSourceFile.parseDiagnostics.length ? jsonSourceFile.parseDiagnostics[0] : undefined
235545
235958
  };
235546
235959
  }
235547
- function readJsonConfigFile(fileName, readFile9) {
235548
- const textOrDiagnostic = tryReadFile(fileName, readFile9);
235960
+ function readJsonConfigFile(fileName, readFile8) {
235961
+ const textOrDiagnostic = tryReadFile(fileName, readFile8);
235549
235962
  return isString(textOrDiagnostic) ? parseJsonText(fileName, textOrDiagnostic) : { fileName, parseDiagnostics: [textOrDiagnostic] };
235550
235963
  }
235551
- function tryReadFile(fileName, readFile9) {
235964
+ function tryReadFile(fileName, readFile8) {
235552
235965
  let text;
235553
235966
  try {
235554
- text = readFile9(fileName);
235967
+ text = readFile8(fileName);
235555
235968
  } catch (e2) {
235556
235969
  return createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e2.message);
235557
235970
  }
@@ -298293,7 +298706,7 @@ ${lanes.join(`
298293
298706
  return;
298294
298707
  }
298295
298708
  const buildInfo = host.getBuildInfo() || { version: version2 };
298296
- writeFile12(host, emitterDiagnostics, buildInfoPath, getBuildInfoText(buildInfo), false, undefined, { buildInfo });
298709
+ writeFile11(host, emitterDiagnostics, buildInfoPath, getBuildInfoText(buildInfo), false, undefined, { buildInfo });
298297
298710
  emittedFilesList == null || emittedFilesList.push(buildInfoPath);
298298
298711
  }
298299
298712
  function emitJsFileOrBundle(sourceFileOrBundle, jsFilePath, sourceMapFilePath) {
@@ -298452,14 +298865,14 @@ ${lanes.join(`
298452
298865
  }
298453
298866
  if (sourceMapFilePath) {
298454
298867
  const sourceMap = sourceMapGenerator.toString();
298455
- writeFile12(host, emitterDiagnostics, sourceMapFilePath, sourceMap, false, sourceFiles);
298868
+ writeFile11(host, emitterDiagnostics, sourceMapFilePath, sourceMap, false, sourceFiles);
298456
298869
  }
298457
298870
  } else {
298458
298871
  writer.writeLine();
298459
298872
  }
298460
298873
  const text = writer.getText();
298461
298874
  const data = { sourceMapUrlPos, diagnostics: transform22.diagnostics };
298462
- writeFile12(host, emitterDiagnostics, jsFilePath, text, !!compilerOptions.emitBOM, sourceFiles, data);
298875
+ writeFile11(host, emitterDiagnostics, jsFilePath, text, !!compilerOptions.emitBOM, sourceFiles, data);
298463
298876
  writer.clear();
298464
298877
  return !data.skippedDtsWrite;
298465
298878
  }
@@ -302981,12 +303394,12 @@ ${lanes.join(`
302981
303394
  function createCompilerHost(options3, setParentNodes) {
302982
303395
  return createCompilerHostWorker(options3, setParentNodes);
302983
303396
  }
302984
- function createGetSourceFile(readFile9, setParentNodes) {
303397
+ function createGetSourceFile(readFile8, setParentNodes) {
302985
303398
  return (fileName, languageVersionOrOptions, onError) => {
302986
303399
  let text;
302987
303400
  try {
302988
303401
  mark("beforeIORead");
302989
- text = readFile9(fileName);
303402
+ text = readFile8(fileName);
302990
303403
  mark("afterIORead");
302991
303404
  measure("I/O Read", "beforeIORead", "afterIORead");
302992
303405
  } catch (e2) {
@@ -303777,7 +304190,7 @@ ${lanes.join(`
303777
304190
  getRedirectFromOutput,
303778
304191
  forEachResolvedProjectReference: forEachResolvedProjectReference2
303779
304192
  });
303780
- const readFile9 = host.readFile.bind(host);
304193
+ const readFile8 = host.readFile.bind(host);
303781
304194
  (_e = tracing) == null || _e.push(tracing.Phase.Program, "shouldProgramCreateNewSourceFiles", { hasOldProgram: !!oldProgram });
303782
304195
  const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options3);
303783
304196
  (_f = tracing) == null || _f.pop();
@@ -303953,7 +304366,7 @@ ${lanes.join(`
303953
304366
  shouldTransformImportCall,
303954
304367
  emitBuildInfo,
303955
304368
  fileExists,
303956
- readFile: readFile9,
304369
+ readFile: readFile8,
303957
304370
  directoryExists,
303958
304371
  getSymlinkCache,
303959
304372
  realpath: (_o = host.realpath) == null ? undefined : _o.bind(host),
@@ -330254,11 +330667,11 @@ ${newComment.split(`
330254
330667
  }
330255
330668
  function convertNamedExport(sourceFile, assignment, changes, exports2) {
330256
330669
  const { text } = assignment.left.name;
330257
- const rename = exports2.get(text);
330258
- if (rename !== undefined) {
330670
+ const rename2 = exports2.get(text);
330671
+ if (rename2 !== undefined) {
330259
330672
  const newNodes = [
330260
- makeConst(undefined, rename, assignment.right),
330261
- makeExportDeclaration([factory.createExportSpecifier(false, rename, text)])
330673
+ makeConst(undefined, rename2, assignment.right),
330674
+ makeExportDeclaration([factory.createExportSpecifier(false, rename2, text)])
330262
330675
  ];
330263
330676
  changes.replaceNodeWithNodes(sourceFile, assignment.parent, newNodes);
330264
330677
  } else {
@@ -346878,11 +347291,11 @@ ${content}
346878
347291
  }
346879
347292
  return createOutliningSpanFromBounds(node.getStart(sourceFile), node.getEnd(), "code");
346880
347293
  }
346881
- function spanForObjectOrArrayLiteral(node, open2 = 19) {
346882
- return spanForNode(node, false, !isArrayLiteralExpression(node.parent) && !isCallExpression(node.parent), open2);
347294
+ function spanForObjectOrArrayLiteral(node, open3 = 19) {
347295
+ return spanForNode(node, false, !isArrayLiteralExpression(node.parent) && !isCallExpression(node.parent), open3);
346883
347296
  }
346884
- function spanForNode(hintSpanNode, autoCollapse = false, useFullStart = true, open2 = 19, close = open2 === 19 ? 20 : 24) {
346885
- const openToken = findChildOfKind(n, open2, sourceFile);
347297
+ function spanForNode(hintSpanNode, autoCollapse = false, useFullStart = true, open3 = 19, close = open3 === 19 ? 20 : 24) {
347298
+ const openToken = findChildOfKind(n, open3, sourceFile);
346886
347299
  const closeToken = findChildOfKind(n, close, sourceFile);
346887
347300
  return openToken && closeToken && spanBetweenTokens(openToken, closeToken, hintSpanNode, sourceFile, autoCollapse, useFullStart);
346888
347301
  }
@@ -349150,9 +349563,9 @@ ${options3.prefix}` : `
349150
349563
  return end;
349151
349564
  }
349152
349565
  function getClassOrObjectBraceEnds(cls, sourceFile) {
349153
- const open2 = findChildOfKind(cls, 19, sourceFile);
349566
+ const open3 = findChildOfKind(cls, 19, sourceFile);
349154
349567
  const close = findChildOfKind(cls, 20, sourceFile);
349155
- return [open2 == null ? undefined : open2.end, close == null ? undefined : close.end];
349568
+ return [open3 == null ? undefined : open3.end, close == null ? undefined : close.end];
349156
349569
  }
349157
349570
  function getMembersOrProperties(node) {
349158
349571
  return isObjectLiteralExpression(node) ? node.properties : node.members;
@@ -354391,7 +354804,7 @@ ${options3.prefix}` : `
354391
354804
  walkUpParenthesizedTypesAndGetParentAndChild: () => walkUpParenthesizedTypesAndGetParentAndChild,
354392
354805
  whitespaceOrMapCommentRegExp: () => whitespaceOrMapCommentRegExp,
354393
354806
  writeCommentRange: () => writeCommentRange,
354394
- writeFile: () => writeFile12,
354807
+ writeFile: () => writeFile11,
354395
354808
  writeFileEnsuringDirectories: () => writeFileEnsuringDirectories,
354396
354809
  zipWith: () => zipWith
354397
354810
  });
@@ -367596,7 +368009,8 @@ function buildLoopStatus(runtime, params) {
367596
368009
  if (!listener) {
367597
368010
  return {
367598
368011
  status: "WAITING_ON_INPUT",
367599
- active_run_ids: []
368012
+ active_run_ids: [],
368013
+ executing_tool_call_ids: []
367600
368014
  };
367601
368015
  }
367602
368016
  const scope = getScopeForRuntime(runtime, params);
@@ -367608,7 +368022,8 @@ function buildLoopStatus(runtime, params) {
367608
368022
  const status = interruptedCacheActive ? !conversationRuntime?.isProcessing ? "WAITING_ON_INPUT" : conversationRuntime?.loopStatus === "WAITING_ON_APPROVAL" ? "WAITING_ON_INPUT" : conversationRuntime?.loopStatus ?? "WAITING_ON_INPUT" : recovered && recovered.pendingRequestIds.size > 0 && conversationRuntime?.loopStatus === "WAITING_ON_INPUT" ? "WAITING_ON_APPROVAL" : conversationRuntime?.loopStatus ?? "WAITING_ON_INPUT";
367609
368023
  return {
367610
368024
  status,
367611
- active_run_ids: interruptedCacheActive && !conversationRuntime?.isProcessing ? [] : conversationRuntime?.activeRunId ? [conversationRuntime.activeRunId] : []
368025
+ active_run_ids: interruptedCacheActive && !conversationRuntime?.isProcessing ? [] : conversationRuntime?.activeRunId ? [conversationRuntime.activeRunId] : [],
368026
+ executing_tool_call_ids: status === "EXECUTING_CLIENT_SIDE_TOOL" && conversationRuntime ? [...conversationRuntime.turnLifecycle.executingToolCallIds] : []
367612
368027
  };
367613
368028
  }
367614
368029
  function buildQueueSnapshot(runtime, params) {
@@ -368054,6 +368469,13 @@ async function switchCurrentRuntimeWorkingDirectory(workingDirectory) {
368054
368469
  process.env.USER_CWD = workingDirectory;
368055
368470
  updateRuntimeContext({ workingDirectory });
368056
368471
  }
368472
+ async function updateToolExecutionContextCwd(executionContextId, workingDirectory) {
368473
+ if (!executionContextId) {
368474
+ return;
368475
+ }
368476
+ const { updateToolExecutionContextWorkingDirectory } = await init_manager4().then(() => exports_manager2);
368477
+ updateToolExecutionContextWorkingDirectory(executionContextId, workingDirectory);
368478
+ }
368057
368479
  async function switchConversationWorkingDirectory(params) {
368058
368480
  const { runtime, workingDirectory } = params;
368059
368481
  const agentId = normalizeCwdAgentId(params.agentId);
@@ -368227,7 +368649,7 @@ import {
368227
368649
  lstat,
368228
368650
  mkdir as mkdir8,
368229
368651
  readdir as readdir5,
368230
- readFile as readFile9,
368652
+ readFile as readFile8,
368231
368653
  realpath as realpath2,
368232
368654
  rmdir,
368233
368655
  stat as stat6,
@@ -368464,7 +368886,7 @@ async function readProvisionConfig(primaryRoot) {
368464
368886
  include: []
368465
368887
  };
368466
368888
  try {
368467
- const raw = await readFile9(path19.join(primaryRoot, ".letta", "settings.json"), "utf8");
368889
+ const raw = await readFile8(path19.join(primaryRoot, ".letta", "settings.json"), "utf8");
368468
368890
  const parsed = JSON.parse(raw);
368469
368891
  const worktree = parsed.worktree;
368470
368892
  if (!worktree) {
@@ -368556,7 +368978,7 @@ async function copyLocalSettingsFile(primaryRoot, worktreePath) {
368556
368978
  }
368557
368979
  async function readWorktreeIncludeEntries(primaryRoot) {
368558
368980
  try {
368559
- const raw = await readFile9(path19.join(primaryRoot, ".worktreeinclude"), "utf8");
368981
+ const raw = await readFile8(path19.join(primaryRoot, ".worktreeinclude"), "utf8");
368560
368982
  return raw.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
368561
368983
  } catch {
368562
368984
  return [];
@@ -368657,13 +369079,10 @@ async function switchSessionToWorktree(params) {
368657
369079
  agentId: runtimeContext.agentId ?? null,
368658
369080
  conversationId: runtimeContext.conversationId
368659
369081
  });
368660
- return true;
368661
- }
368662
- await switchCurrentRuntimeWorkingDirectory(worktreePath);
368663
- if (params.executionContextId) {
368664
- const { updateToolExecutionContextWorkingDirectory } = await init_manager4().then(() => exports_manager2);
368665
- updateToolExecutionContextWorkingDirectory(params.executionContextId, worktreePath);
369082
+ } else {
369083
+ await switchCurrentRuntimeWorkingDirectory(worktreePath);
368666
369084
  }
369085
+ await updateToolExecutionContextCwd(params.executionContextId, worktreePath);
368667
369086
  return true;
368668
369087
  }
368669
369088
  async function resolveWorktreeGitDir(worktreePath) {
@@ -371064,15 +371483,15 @@ var require_parse3 = __commonJS((exports, module3) => {
371064
371483
  }
371065
371484
  if (value === "{" && opts.nobrace !== true) {
371066
371485
  increment("braces");
371067
- const open2 = {
371486
+ const open3 = {
371068
371487
  type: "brace",
371069
371488
  value,
371070
371489
  output: "(",
371071
371490
  outputIndex: state.output.length,
371072
371491
  tokensIndex: state.tokens.length
371073
371492
  };
371074
- braces.push(open2);
371075
- push(open2);
371493
+ braces.push(open3);
371494
+ push(open3);
371076
371495
  continue;
371077
371496
  }
371078
371497
  if (value === "}") {
@@ -371723,12 +372142,12 @@ var init_list_directory_gemini = __esm(() => {
371723
372142
  import { existsSync as existsSync27 } from "node:fs";
371724
372143
  import {
371725
372144
  mkdir as mkdir9,
371726
- readFile as readFile10,
371727
- rename,
371728
- rm as rm3,
372145
+ readFile as readFile9,
372146
+ rename as rename2,
372147
+ rm as rm4,
371729
372148
  stat as stat8,
371730
372149
  unlink as unlink2,
371731
- writeFile as writeFile12
372150
+ writeFile as writeFile11
371732
372151
  } from "node:fs/promises";
371733
372152
  import { dirname as dirname14, isAbsolute as isAbsolute15, relative as relative3, resolve as resolve18 } from "node:path";
371734
372153
  async function getMemoryWriteSyncMode() {
@@ -371809,7 +372228,7 @@ async function applyMemoryCommand(memoryDir, args) {
371809
372228
  throw new Error(`memory create: block already exists at ${pathArg}`);
371810
372229
  }
371811
372230
  await mkdir9(dirname14(filePath), { recursive: true });
371812
- await writeFile12(filePath, rendered, "utf8");
372231
+ await writeFile11(filePath, rendered, "utf8");
371813
372232
  return [relPath];
371814
372233
  }
371815
372234
  if (command === "str_replace") {
@@ -371826,7 +372245,7 @@ async function applyMemoryCommand(memoryDir, args) {
371826
372245
  }
371827
372246
  const nextBody = `${file3.body.slice(0, idx)}${newString}${file3.body.slice(idx + oldString.length)}`;
371828
372247
  const rendered = renderMemoryFile(file3.frontmatter, nextBody);
371829
- await writeFile12(filePath, rendered, "utf8");
372248
+ await writeFile11(filePath, rendered, "utf8");
371830
372249
  return [relPath];
371831
372250
  }
371832
372251
  if (command === "insert") {
@@ -371849,7 +372268,7 @@ async function applyMemoryCommand(memoryDir, args) {
371849
372268
  const nextBody = existingLines.join(`
371850
372269
  `);
371851
372270
  const rendered = renderMemoryFile(file3.frontmatter, nextBody);
371852
- await writeFile12(filePath, rendered, "utf8");
372271
+ await writeFile11(filePath, rendered, "utf8");
371853
372272
  return [relPath];
371854
372273
  }
371855
372274
  if (command === "delete") {
@@ -371858,7 +372277,7 @@ async function applyMemoryCommand(memoryDir, args) {
371858
372277
  const targetPath = resolveMemoryPath(memoryDir, label);
371859
372278
  if (existsSync27(targetPath) && (await stat8(targetPath)).isDirectory()) {
371860
372279
  const relPath2 = toRepoRelative(memoryDir, targetPath);
371861
- await rm3(targetPath, { recursive: true, force: false });
372280
+ await rm4(targetPath, { recursive: true, force: false });
371862
372281
  return [relPath2];
371863
372282
  }
371864
372283
  const filePath = resolveMemoryFilePath(memoryDir, label);
@@ -371881,7 +372300,7 @@ async function applyMemoryCommand(memoryDir, args) {
371881
372300
  }
371882
372301
  await loadEditableMemoryFile(oldFilePath, oldPathArg);
371883
372302
  await mkdir9(dirname14(newFilePath), { recursive: true });
371884
- await rename(oldFilePath, newFilePath);
372303
+ await rename2(oldFilePath, newFilePath);
371885
372304
  return [oldRelPath, newRelPath];
371886
372305
  }
371887
372306
  if (command === "update_description") {
@@ -371895,7 +372314,7 @@ async function applyMemoryCommand(memoryDir, args) {
371895
372314
  ...file3.frontmatter,
371896
372315
  description: newDescription
371897
372316
  }, file3.body);
371898
- await writeFile12(filePath, rendered, "utf8");
372317
+ await writeFile11(filePath, rendered, "utf8");
371899
372318
  return [relPath];
371900
372319
  }
371901
372320
  throw new Error(`Unsupported memory command: ${command}`);
@@ -371986,7 +372405,7 @@ function toRepoRelative(memoryDir, absolutePath) {
371986
372405
  return rel.replace(/\\/g, "/");
371987
372406
  }
371988
372407
  async function loadEditableMemoryFile(filePath, sourcePath) {
371989
- const content = await readFile10(filePath, "utf8").catch((error54) => {
372408
+ const content = await readFile9(filePath, "utf8").catch((error54) => {
371990
372409
  const message = error54 instanceof Error ? error54.message : String(error54);
371991
372410
  throw new Error(`memory: failed to read ${sourcePath}: ${message}`);
371992
372411
  });
@@ -372082,7 +372501,7 @@ var init_memory4 = __esm(() => {
372082
372501
 
372083
372502
  // src/tools/impl/memory-apply-patch.ts
372084
372503
  import { existsSync as existsSync28 } from "node:fs";
372085
- import { access, mkdir as mkdir10, rm as rm4, stat as stat9, unlink as unlink3 } from "node:fs/promises";
372504
+ import { access, mkdir as mkdir10, rm as rm5, stat as stat9, unlink as unlink3 } from "node:fs/promises";
372086
372505
  import { dirname as dirname15, isAbsolute as isAbsolute16, relative as relative4, resolve as resolve19 } from "node:path";
372087
372506
  async function getMemoryWriteSyncMode2() {
372088
372507
  const { getBackend: getBackend2 } = await Promise.resolve().then(() => (init_backend2(), exports_backend));
@@ -372235,7 +372654,7 @@ async function applyMemoryPatch(memoryDir, input) {
372235
372654
  continue;
372236
372655
  const stats = await stat9(absPath);
372237
372656
  if (stats.isDirectory()) {
372238
- await rm4(absPath, { recursive: true, force: false });
372657
+ await rm5(absPath, { recursive: true, force: false });
372239
372658
  } else {
372240
372659
  await unlink3(absPath);
372241
372660
  }
@@ -373246,6 +373665,7 @@ function normalizeMessageChannelInput(args) {
373246
373665
  replyToMessageId: firstNonEmptyString3(args.replyTo),
373247
373666
  threadId: firstNonEmptyString3(args.threadId) ?? null,
373248
373667
  messageId: firstNonEmptyString3(args.messageId),
373668
+ attachmentId: firstNonEmptyString3(args.attachmentId),
373249
373669
  emoji: firstNonEmptyString3(args.emoji),
373250
373670
  remove: firstDefinedBoolean(args.remove),
373251
373671
  mediaPath: firstNonEmptyString3(args.media),
@@ -373262,6 +373682,7 @@ function buildMessageChannelRequest(input, chatId, threadId) {
373262
373682
  replyToMessageId: input.replyToMessageId,
373263
373683
  threadId: threadId ?? input.threadId ?? null,
373264
373684
  messageId: input.messageId,
373685
+ attachmentId: input.attachmentId,
373265
373686
  emoji: input.emoji,
373266
373687
  remove: input.remove,
373267
373688
  mediaPath: input.mediaPath,
@@ -373367,6 +373788,9 @@ async function message_channel(args) {
373367
373788
  if (typeof input === "string") {
373368
373789
  return input;
373369
373790
  }
373791
+ if (input.channel === "slack" && input.action === "download-file" && input.target) {
373792
+ return "Error: Slack download-file requires chat_id from a routed channel context; target is not supported.";
373793
+ }
373370
373794
  try {
373371
373795
  let executionContext;
373372
373796
  if (input.chatId) {
@@ -373393,8 +373817,9 @@ async function message_channel(args) {
373393
373817
  accountId: resolvedAccountId,
373394
373818
  channelTurnSources: args.channelTurnSources
373395
373819
  });
373820
+ const requestThreadId = input.action === "download-file" ? input.threadId : inferredThreadId ?? route2.threadId ?? input.threadId;
373396
373821
  executionContext = {
373397
- request: buildMessageChannelRequest(input, input.chatId, inferredThreadId ?? route2.threadId ?? input.threadId),
373822
+ request: buildMessageChannelRequest(input, input.chatId, requestThreadId),
373398
373823
  route: route2,
373399
373824
  adapter: adapter2,
373400
373825
  plugin: plugin2
@@ -375709,10 +376134,10 @@ globstar while`, file3, fr, pattern4, pr, swallowee);
375709
376134
  }
375710
376135
  return filtered.join("/");
375711
376136
  }).join("|");
375712
- const [open2, close] = set3.length > 1 ? ["(?:", ")"] : ["", ""];
375713
- re = "^" + open2 + re + close + "$";
376137
+ const [open3, close] = set3.length > 1 ? ["(?:", ")"] : ["", ""];
376138
+ re = "^" + open3 + re + close + "$";
375714
376139
  if (this.partial) {
375715
- re = "^(?:\\/|" + open2 + re.slice(1, -1) + close + ")$";
376140
+ re = "^(?:\\/|" + open3 + re.slice(1, -1) + close + ")$";
375716
376141
  }
375717
376142
  if (this.negate)
375718
376143
  re = "^(?!" + re + ").+$";
@@ -381703,7 +382128,7 @@ __export(exports_skill, {
381703
382128
  getResolvedSkillsDir: () => getResolvedSkillsDir
381704
382129
  });
381705
382130
  import { existsSync as existsSync29, readdirSync as readdirSync10 } from "node:fs";
381706
- import { readFile as readFile11 } from "node:fs/promises";
382131
+ import { readFile as readFile10 } from "node:fs/promises";
381707
382132
  import { dirname as dirname16, join as join34 } from "node:path";
381708
382133
  function getMemorySkillsDirs2(agentId) {
381709
382134
  const dirs = new Set;
@@ -381735,41 +382160,41 @@ async function readSkillContent(skillId, skillsDir, agentId) {
381735
382160
  for (const projectSkillsDir of projectSkillsDirs) {
381736
382161
  const projectSkillPath = join34(projectSkillsDir, skillId, "SKILL.md");
381737
382162
  try {
381738
- const content = await readFile11(projectSkillPath, "utf-8");
382163
+ const content = await readFile10(projectSkillPath, "utf-8");
381739
382164
  return { content, path: projectSkillPath };
381740
382165
  } catch {}
381741
382166
  }
381742
382167
  if (agentId) {
381743
382168
  const agentSkillPath = join34(getAgentSkillsDir(agentId), skillId, "SKILL.md");
381744
382169
  try {
381745
- const content = await readFile11(agentSkillPath, "utf-8");
382170
+ const content = await readFile10(agentSkillPath, "utf-8");
381746
382171
  return { content, path: agentSkillPath };
381747
382172
  } catch {}
381748
382173
  }
381749
382174
  for (const memorySkillsDir of getMemorySkillsDirs2(agentId)) {
381750
382175
  const memorySkillPath = join34(memorySkillsDir, skillId, "SKILL.md");
381751
382176
  try {
381752
- const content = await readFile11(memorySkillPath, "utf-8");
382177
+ const content = await readFile10(memorySkillPath, "utf-8");
381753
382178
  return { content, path: memorySkillPath };
381754
382179
  } catch {}
381755
382180
  }
381756
382181
  const globalSkillPath = join34(GLOBAL_SKILLS_DIR, skillId, "SKILL.md");
381757
382182
  try {
381758
- const content = await readFile11(globalSkillPath, "utf-8");
382183
+ const content = await readFile10(globalSkillPath, "utf-8");
381759
382184
  return { content, path: globalSkillPath };
381760
382185
  } catch {}
381761
382186
  const bundledSkills = await getBundledSkills();
381762
382187
  const bundledSkill = bundledSkills.find((s2) => s2.id === skillId);
381763
382188
  if (bundledSkill?.path && isSkillAvailableForAgent(bundledSkill, agentId)) {
381764
382189
  try {
381765
- const content = await readFile11(bundledSkill.path, "utf-8");
382190
+ const content = await readFile10(bundledSkill.path, "utf-8");
381766
382191
  return { content, path: bundledSkill.path };
381767
382192
  } catch {}
381768
382193
  }
381769
382194
  try {
381770
382195
  const bundledSkillsDir = join34(process.cwd(), "skills", "skills");
381771
382196
  const bundledSkillPath = join34(bundledSkillsDir, skillId, "SKILL.md");
381772
- const content = await readFile11(bundledSkillPath, "utf-8");
382197
+ const content = await readFile10(bundledSkillPath, "utf-8");
381773
382198
  return { content, path: bundledSkillPath };
381774
382199
  } catch {
381775
382200
  throw new Error(`Skill "${skillId}" not found. Check that the skill name is correct and that it appears in the available skills list.`);
@@ -383124,7 +383549,11 @@ function buildSubagentArgs(type3, config3, model, userPrompt, existingAgentId, e
383124
383549
  args.push("--agent", existingAgentId, "--new");
383125
383550
  }
383126
383551
  } else {
383127
- args.push("--new-agent", "--system", type3);
383552
+ if (options3.systemPromptOverride) {
383553
+ args.push("--new-agent", "--system-custom", options3.systemPromptOverride);
383554
+ } else {
383555
+ args.push("--new-agent", "--system", type3);
383556
+ }
383128
383557
  const subagentTags = [`type:${type3}`];
383129
383558
  if (options3.parentAgentId) {
383130
383559
  subagentTags.push(`parent:${options3.parentAgentId}`);
@@ -383171,7 +383600,7 @@ function buildSubagentArgs(type3, config3, model, userPrompt, existingAgentId, e
383171
383600
  }
383172
383601
  return args;
383173
383602
  }
383174
- async function executeSubagent(type3, config3, model, userPrompt, baseURL, subagentId, isRetry = false, signal, existingAgentId, existingConversationId, maxTurns, parentAgentIdOverride, transcriptPath, memoryScope) {
383603
+ async function executeSubagent(type3, config3, model, userPrompt, subagentId, isRetry = false, signal, existingAgentId, existingConversationId, maxTurns, parentAgentIdOverride, transcriptPath, memoryScope, systemPromptOverride) {
383175
383604
  if (signal?.aborted) {
383176
383605
  return {
383177
383606
  agentId: "",
@@ -383199,7 +383628,8 @@ async function executeSubagent(type3, config3, model, userPrompt, baseURL, subag
383199
383628
  backendMode,
383200
383629
  promptTransport: "stdin",
383201
383630
  parentAgentId,
383202
- extraTools: config3.fork && inheritedChannelContext ? ["MessageChannel"] : undefined
383631
+ extraTools: config3.fork && inheritedChannelContext ? ["MessageChannel"] : undefined,
383632
+ systemPromptOverride
383203
383633
  });
383204
383634
  const launcher = resolveSubagentLauncher(cliArgs);
383205
383635
  const settings3 = await settingsManager.getSettingsWithSecureTokens();
@@ -383314,7 +383744,7 @@ async function executeSubagent(type3, config3, model, userPrompt, baseURL, subag
383314
383744
  agentId: parentAgentIdOverride
383315
383745
  });
383316
383746
  if (primaryModel) {
383317
- return executeSubagent(type3, config3, primaryModel, userPrompt, baseURL, subagentId, true, signal, undefined, undefined, maxTurns, parentAgentIdOverride, transcriptPath);
383747
+ return executeSubagent(type3, config3, primaryModel, userPrompt, subagentId, true, signal, undefined, undefined, maxTurns, parentAgentIdOverride, transcriptPath);
383318
383748
  }
383319
383749
  }
383320
383750
  const propagatedError = state.finalError?.trim();
@@ -383369,14 +383799,6 @@ async function executeSubagent(type3, config3, model, userPrompt, baseURL, subag
383369
383799
  };
383370
383800
  }
383371
383801
  }
383372
- function getBaseURL() {
383373
- const settings3 = settingsManager.getSettings();
383374
- const baseURL = process.env.LETTA_BASE_URL || settings3.env?.LETTA_BASE_URL || "https://api.letta.com";
383375
- if (baseURL === "https://api.letta.com") {
383376
- return "https://app.letta.com";
383377
- }
383378
- return baseURL;
383379
- }
383380
383802
  function buildDeploySystemReminder(senderAgentName, senderAgentId) {
383381
383803
  return `${SYSTEM_REMINDER_OPEN}
383382
383804
  This task is from "${senderAgentName}" (agent ID: ${senderAgentId}), which deployed you as a subagent inside the Letta Code CLI (docs.letta.com/letta-code).
@@ -383420,7 +383842,7 @@ ${SYSTEM_REMINDER_CLOSE}
383420
383842
 
383421
383843
  `;
383422
383844
  }
383423
- async function spawnSubagent(type3, prompt, userModel, subagentId, signal, existingAgentId, existingConversationId, maxTurns, forkedContext, parentAgentId, transcriptPath, parentConversationId, memoryScope) {
383845
+ async function spawnSubagent(type3, prompt, userModel, subagentId, signal, existingAgentId, existingConversationId, maxTurns, forkedContext, parentAgentId, transcriptPath, parentConversationId, memoryScope, systemPromptOverride) {
383424
383846
  const allConfigs = await getAllSubagentConfigs();
383425
383847
  const config3 = allConfigs[type3];
383426
383848
  if (!config3) {
@@ -383459,7 +383881,6 @@ async function spawnSubagent(type3, prompt, userModel, subagentId, signal, exist
383459
383881
  subagentType: type3,
383460
383882
  backendMode
383461
383883
  });
383462
- const baseURL = getBaseURL();
383463
383884
  let finalPrompt = prompt;
383464
383885
  if (isDeployingExisting && resolvedParentAgentId) {
383465
383886
  try {
@@ -383479,7 +383900,7 @@ async function spawnSubagent(type3, prompt, userModel, subagentId, signal, exist
383479
383900
  });
383480
383901
  updateSubagent(subagentId, { agentURL: forkAgentURL });
383481
383902
  }
383482
- const result = await executeSubagent(type3, config3, model, finalPrompt, baseURL, subagentId, false, signal, existingAgentId, existingConversationId, maxTurns, resolvedParentAgentId, transcriptPath, memoryScope);
383903
+ const result = await executeSubagent(type3, config3, model, finalPrompt, subagentId, false, signal, existingAgentId, existingConversationId, maxTurns, resolvedParentAgentId, transcriptPath, memoryScope, systemPromptOverride);
383483
383904
  return result;
383484
383905
  }
383485
383906
  var NO_BASE_TOOL_SUBAGENT_TYPES;
@@ -383734,6 +384155,7 @@ function spawnBackgroundSubagentTask(args) {
383734
384155
  prompt,
383735
384156
  description,
383736
384157
  model,
384158
+ systemPromptOverride,
383737
384159
  toolCallId,
383738
384160
  existingAgentId,
383739
384161
  existingConversationId,
@@ -383776,7 +384198,7 @@ function spawnBackgroundSubagentTask(args) {
383776
384198
  backgroundTasks.set(taskId, bgTask);
383777
384199
  writeTaskTranscriptStart(outputFile, description, subagentType);
383778
384200
  const parentAgentIdForSpawn = resolvedParentScope?.agentId;
383779
- spawnSubagentFn(subagentType, prompt, model, subagentId, abortController.signal, existingAgentId, existingConversationId, maxTurns, forkedContext, parentAgentIdForSpawn, transcriptPath, resolvedParentScope?.conversationId, memoryScope).then(async (result) => {
384201
+ spawnSubagentFn(subagentType, prompt, model, subagentId, abortController.signal, existingAgentId, existingConversationId, maxTurns, forkedContext, parentAgentIdForSpawn, transcriptPath, resolvedParentScope?.conversationId, memoryScope, systemPromptOverride).then(async (result) => {
383780
384202
  bgTask.status = result.success ? "completed" : "failed";
383781
384203
  if (result.error) {
383782
384204
  bgTask.error = result.error;
@@ -387667,8 +388089,8 @@ globstar while`, file3, fr, pattern4, pr, swallowee);
387667
388089
  });
387668
388090
  return pp.filter((p) => p !== GLOBSTAR2).join("/");
387669
388091
  }).join("|");
387670
- const [open2, close] = set3.length > 1 ? ["(?:", ")"] : ["", ""];
387671
- re = "^" + open2 + re + close + "$";
388092
+ const [open3, close] = set3.length > 1 ? ["(?:", ")"] : ["", ""];
388093
+ re = "^" + open3 + re + close + "$";
387672
388094
  if (this.negate)
387673
388095
  re = "^(?!" + re + ").+$";
387674
388096
  try {
@@ -393736,16 +394158,16 @@ function toolResultToStoredReturnValue(message) {
393736
394158
  };
393737
394159
  });
393738
394160
  }
393739
- function projectThinkingContent(message, content, contentIndex, date6, agentId, conversationId) {
393740
- if (content.thinking.length === 0)
394161
+ function projectThinkingContent(message, reasoning, contentStartIndex, date6, agentId, conversationId) {
394162
+ if (reasoning.length === 0)
393741
394163
  return;
393742
394164
  return {
393743
- id: `${message.id}:reasoning:${contentIndex}`,
394165
+ id: `${message.id}:reasoning:${contentStartIndex}`,
393744
394166
  date: date6,
393745
394167
  agent_id: agentId,
393746
394168
  conversation_id: conversationId,
393747
394169
  message_type: "reasoning_message",
393748
- reasoning: content.thinking
394170
+ reasoning
393749
394171
  };
393750
394172
  }
393751
394173
  function projectToolCallContent(message, content, date6, agentId, conversationId) {
@@ -393815,6 +394237,8 @@ function projectLocalMessageToStoredMessages(message, fallbackAgentId, fallbackC
393815
394237
  const messages = [];
393816
394238
  let pendingTextContent = [];
393817
394239
  let pendingTextStartIndex = -1;
394240
+ let pendingReasoningContent = [];
394241
+ let pendingReasoningStartIndex = -1;
393818
394242
  const flushPendingText = () => {
393819
394243
  if (pendingTextContent.length === 0)
393820
394244
  return;
@@ -393831,29 +394255,46 @@ function projectLocalMessageToStoredMessages(message, fallbackAgentId, fallbackC
393831
394255
  pendingTextContent = [];
393832
394256
  pendingTextStartIndex = -1;
393833
394257
  };
394258
+ const flushPendingReasoning = () => {
394259
+ if (pendingReasoningContent.length > 0) {
394260
+ const reasoningMessage = projectThinkingContent(message, pendingReasoningContent.join(`
394261
+
394262
+ `), pendingReasoningStartIndex, date6, agentId, conversationId);
394263
+ if (reasoningMessage)
394264
+ messages.push(reasoningMessage);
394265
+ }
394266
+ pendingReasoningContent = [];
394267
+ pendingReasoningStartIndex = -1;
394268
+ };
393834
394269
  for (let contentIndex = 0;contentIndex < message.content.length; contentIndex++) {
393835
394270
  const content = message.content[contentIndex];
393836
394271
  if (!content)
393837
394272
  continue;
393838
394273
  if (isThinkingContent(content)) {
393839
394274
  flushPendingText();
393840
- const reasoningMessage = projectThinkingContent(message, content, contentIndex, date6, agentId, conversationId);
393841
- if (reasoningMessage)
393842
- messages.push(reasoningMessage);
394275
+ if (content.thinking.length > 0) {
394276
+ if (pendingReasoningStartIndex === -1) {
394277
+ pendingReasoningStartIndex = contentIndex;
394278
+ }
394279
+ pendingReasoningContent.push(content.thinking);
394280
+ }
393843
394281
  continue;
393844
394282
  }
393845
394283
  if (isLocalToolCallContent(content)) {
393846
394284
  flushPendingText();
394285
+ flushPendingReasoning();
393847
394286
  messages.push(projectToolCallContent(message, content, date6, agentId, conversationId));
393848
394287
  continue;
393849
394288
  }
393850
394289
  if (isTextContent(content)) {
394290
+ flushPendingReasoning();
393851
394291
  if (pendingTextStartIndex === -1)
393852
394292
  pendingTextStartIndex = contentIndex;
393853
394293
  pendingTextContent.push({ type: "text", text: content.text });
393854
394294
  }
393855
394295
  }
393856
394296
  flushPendingText();
394297
+ flushPendingReasoning();
393857
394298
  return messages;
393858
394299
  }
393859
394300
  function withProjectedMessageDates(messages, _sourceMessageIndex) {
@@ -394937,7 +395378,7 @@ class LocalStore {
394937
395378
  }
394938
395379
  this.ensureAgent(agentId);
394939
395380
  const conversationId = this.nextConversationId();
394940
- const conversation = createLocalConversationRecord(conversationId, agentId, this.conversationSeq, body);
395381
+ const conversation = this.withConversationModelDefaults(createLocalConversationRecord(conversationId, agentId, this.conversationSeq, body));
394941
395382
  const key = this.conversationKey(conversation.id, agentId);
394942
395383
  this.conversations.set(key, conversation);
394943
395384
  this.localMessagesByConversationKey.set(key, []);
@@ -394956,32 +395397,31 @@ class LocalStore {
394956
395397
  }
394957
395398
  const created = this.ensureConversation(conversationId);
394958
395399
  const updated2 = updateLocalConversationRecord(created, body, currentIsoTimestamp());
394959
- const projected = this.applyConversationModelDefaults(updated2, body, created);
395400
+ const projected = this.withConversationModelDefaults(updated2);
394960
395401
  this.conversations.set(this.conversationKey(conversationId, created.agent_id), projected);
394961
395402
  this.persistConversationState(conversationId, created.agent_id);
394962
395403
  return projected;
394963
395404
  }
394964
- const updated = this.applyConversationModelDefaults(updateLocalConversationRecord(current, body, currentIsoTimestamp()), body, current);
395405
+ const updated = this.withConversationModelDefaults(updateLocalConversationRecord(current, body, currentIsoTimestamp()));
394965
395406
  this.conversations.set(this.conversationKey(conversationId, current.agent_id), updated);
394966
395407
  this.persistConversationState(conversationId, current.agent_id);
394967
395408
  return updated;
394968
395409
  }
394969
- applyConversationModelDefaults(conversation, body, previousConversation) {
394970
- const requestedModel = body.model;
395410
+ withConversationModelDefaults(conversation) {
395411
+ const requestedModel = conversation.model;
394971
395412
  if (typeof requestedModel !== "string")
394972
395413
  return conversation;
394973
395414
  const normalizedRequestedModel = normalizeLocalModelHandle(requestedModel, isRecord(conversation.model_settings) ? conversation.model_settings : {});
394974
- if (previousConversation.model === normalizedRequestedModel)
394975
- return conversation;
394976
395415
  const defaults5 = this.modelSettingsDefaultsForModel(normalizedRequestedModel);
394977
395416
  if (!defaults5 || Object.keys(defaults5).length === 0)
394978
395417
  return conversation;
394979
395418
  const existingSettings = isRecord(conversation.model_settings) ? conversation.model_settings : {};
394980
395419
  return {
394981
395420
  ...conversation,
395421
+ model: normalizedRequestedModel,
394982
395422
  model_settings: {
394983
- ...existingSettings,
394984
- ...defaults5
395423
+ ...defaults5,
395424
+ ...existingSettings
394985
395425
  }
394986
395426
  };
394987
395427
  }
@@ -395877,7 +396317,7 @@ class LocalStore {
395877
396317
  const existing = this.conversations.get(key);
395878
396318
  if (existing && options3.forceRefresh !== true)
395879
396319
  return existing;
395880
- const normalizedInput = normalizeStoredLocalModelRecord(input);
396320
+ const normalizedInput = this.withConversationModelDefaults(normalizeStoredLocalModelRecord(input));
395881
396321
  const timing = transcriptTimingForConversationDir(conversationDir);
395882
396322
  const requiresFullTimestampRepair = isSyntheticLocalTimestamp(normalizedInput.created_at) || isSyntheticLocalTimestamp(normalizedInput.updated_at) || isSyntheticLocalTimestamp(normalizedInput.last_message_at);
395883
396323
  const conversation = requiresFullTimestampRepair ? normalizedInput : repairSyntheticConversationTimestamps(normalizedInput, [], timing);
@@ -397075,8 +397515,8 @@ var init_error_formatter = __esm(() => {
397075
397515
  init_app_urls();
397076
397516
  init_error_context();
397077
397517
  init_zai_errors();
397078
- LETTA_USAGE_URL = buildAppUrl("/settings/organization/usage");
397079
- LETTA_AGENTS_URL = buildAppUrl("/projects/default-project/agents");
397518
+ LETTA_USAGE_URL = buildChatWebUrl("/preferences/usage");
397519
+ LETTA_AGENTS_URL = buildPlatformUrl("/projects/default-project/agents");
397080
397520
  CLOUDFLARE_EDGE_5XX_MARKER_PATTERN = /(^|\s)(502|52[0-6])\s*<!doctype html|error code\s*(502|52[0-6])/i;
397081
397521
  CLOUDFLARE_EDGE_5XX_TITLE_PATTERN = /\|\s*(502|52[0-6])\s*:/i;
397082
397522
  CLOUDFLARE_EDGE_5XX_FORMATTED_PATTERN = /\bCloudflare\s+(502|52[0-6])\b/i;
@@ -399020,24 +399460,6 @@ var init_local_model_config = __esm(() => {
399020
399460
  UNSUPPORTED_LOCAL_CHATGPT_OAUTH_MODELS = new Set(["gpt-5.6-luna"]);
399021
399461
  });
399022
399462
 
399023
- // src/backend/dev/pi-output-budget.ts
399024
- function assertViablePiOutputBudget(model, context3, requestedMaxTokens) {
399025
- if (model.provider !== "llama-cpp")
399026
- return;
399027
- const requested = requestedMaxTokens ?? model.maxTokens;
399028
- if (!Number.isFinite(requested) || requested <= 0)
399029
- return;
399030
- const clamped = clampMaxTokensToContext(model, context3, requested);
399031
- const minimum3 = Math.min(requested, MIN_VIABLE_OUTPUT_TOKENS);
399032
- if (clamped >= minimum3)
399033
- return;
399034
- throw new Error(`Context window of ${model.contextWindow} tokens leaves only ${clamped} output token${clamped === 1 ? "" : "s"}; at least ${minimum3} are required. Compact the conversation or increase the context window.`);
399035
- }
399036
- var MIN_VIABLE_OUTPUT_TOKENS = 1024;
399037
- var init_pi_output_budget = __esm(() => {
399038
- init_simple_options();
399039
- });
399040
-
399041
399463
  // src/backend/local/local-context-estimate.ts
399042
399464
  function positiveUsageNumber(value) {
399043
399465
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
@@ -399301,12 +399723,26 @@ function errorFromAssistantEvent(part) {
399301
399723
  return new Error("Unknown provider stream error");
399302
399724
  return new Error(part.error.errorMessage ?? "Unknown local provider error");
399303
399725
  }
399304
- function otidForContentIndex(otids, prefix, contentIndex) {
399305
- const existing = otids.get(contentIndex);
399726
+ function contentMatchesMessageType(content, messageType) {
399727
+ if (!content || typeof content !== "object" || !("type" in content)) {
399728
+ return false;
399729
+ }
399730
+ return messageType === "assistant_message" ? content.type === "text" : content.type === "thinking";
399731
+ }
399732
+ function contiguousContentStartIndex(partial4, contentIndex, messageType) {
399733
+ let startIndex = contentIndex;
399734
+ while (startIndex > 0 && contentMatchesMessageType(partial4.content[startIndex - 1], messageType)) {
399735
+ startIndex -= 1;
399736
+ }
399737
+ return startIndex;
399738
+ }
399739
+ function otidForContentSegment(otids, prefix, contentIndex, partial4, messageType) {
399740
+ const segmentStartIndex = contiguousContentStartIndex(partial4, contentIndex, messageType);
399741
+ const existing = otids.get(segmentStartIndex);
399306
399742
  if (existing)
399307
399743
  return existing;
399308
- const otid = `${prefix}-${contentIndex}-${randomUUID20()}`;
399309
- otids.set(contentIndex, otid);
399744
+ const otid = `${prefix}-${segmentStartIndex}-${randomUUID20()}`;
399745
+ otids.set(segmentStartIndex, otid);
399310
399746
  return otid;
399311
399747
  }
399312
399748
  function createProviderLettaStream(events, contextTokensEstimate) {
@@ -399337,7 +399773,7 @@ function createProviderLettaStream(events, contextTokensEstimate) {
399337
399773
  if (part.type === "text_delta") {
399338
399774
  yield {
399339
399775
  message_type: "assistant_message",
399340
- otid: otidForContentIndex(assistantOtids, "provider-assistant", part.contentIndex),
399776
+ otid: otidForContentSegment(assistantOtids, "provider-assistant", part.contentIndex, part.partial, "assistant_message"),
399341
399777
  content: [{ type: "text", text: part.delta }]
399342
399778
  };
399343
399779
  continue;
@@ -399345,7 +399781,7 @@ function createProviderLettaStream(events, contextTokensEstimate) {
399345
399781
  if (part.type === "thinking_delta") {
399346
399782
  yield {
399347
399783
  message_type: "reasoning_message",
399348
- otid: otidForContentIndex(reasoningOtids, "provider-reasoning", part.contentIndex),
399784
+ otid: otidForContentSegment(reasoningOtids, "provider-reasoning", part.contentIndex, part.partial, "reasoning_message"),
399349
399785
  reasoning: part.delta
399350
399786
  };
399351
399787
  continue;
@@ -399372,7 +399808,7 @@ function createProviderLettaStream(events, contextTokensEstimate) {
399372
399808
  }
399373
399809
  pendingStopReason = {
399374
399810
  message_type: "stop_reason",
399375
- stop_reason: sawToolCall || part.reason === "toolUse" ? "requires_approval" : "end_turn"
399811
+ stop_reason: sawToolCall || part.reason === "toolUse" ? "requires_approval" : part.reason === "length" ? "max_tokens_exceeded" : "end_turn"
399376
399812
  };
399377
399813
  continue;
399378
399814
  }
@@ -399882,7 +400318,6 @@ class PiStreamAdapter {
399882
400318
  messageCount: context3.messages.length,
399883
400319
  contextWindow: resolved.model.contextWindow
399884
400320
  });
399885
- assertViablePiOutputBudget(resolved.model, context3, options3.maxTokens);
399886
400321
  const result = this.runStream(resolved.model, context3, options3);
399887
400322
  let streamError;
399888
400323
  let finalMessage;
@@ -400023,7 +400458,6 @@ var init_pi_stream_adapter = __esm(() => {
400023
400458
  init_context_window_overflow();
400024
400459
  init_local_provider_errors();
400025
400460
  init_pi_model_factory();
400026
- init_pi_output_budget();
400027
400461
  init_provider_turn_executor();
400028
400462
  LOCAL_PROVIDER_ADAPTIVE_IMAGE_ELISION_AFTER_RETRIES = LOCAL_PROVIDER_MAX_RETRIES - 1;
400029
400463
  PiProviderError = class PiProviderError extends Error {
@@ -402208,6 +402642,7 @@ __export(exports_oauth2, {
402208
402642
  requestDeviceCode: () => requestDeviceCode2,
402209
402643
  refreshAccessToken: () => refreshAccessToken3,
402210
402644
  pollForToken: () => pollForToken2,
402645
+ OAuthRefreshError: () => OAuthRefreshError2,
402211
402646
  OAUTH_CONFIG: () => OAUTH_CONFIG2,
402212
402647
  LETTA_CLOUD_API_URL: () => LETTA_CLOUD_API_URL2
402213
402648
  });
@@ -402388,11 +402823,22 @@ async function refreshAccessToken3(refreshToken, deviceId, deviceName) {
402388
402823
  });
402389
402824
  if (!response.ok) {
402390
402825
  const error54 = await response.json();
402391
- throw new Error(`Failed to refresh access token from ${authHost}: ${error54.error_description || error54.error}`);
402826
+ throw new OAuthRefreshError2(`Failed to refresh access token from ${authHost}: ${error54.error_description || error54.error}`, {
402827
+ retryable: response.status === 408 || response.status === 429 || response.status >= 500,
402828
+ status: response.status,
402829
+ oauthCode: error54.error
402830
+ });
402392
402831
  }
402393
402832
  return await response.json();
402394
402833
  } catch (error54) {
402395
- throw toOAuthActionError2("refresh access token", error54);
402834
+ if (error54 instanceof OAuthRefreshError2) {
402835
+ throw error54;
402836
+ }
402837
+ const actionError = toOAuthActionError2("refresh access token", error54);
402838
+ throw new OAuthRefreshError2(actionError.message, {
402839
+ retryable: true,
402840
+ cause: error54
402841
+ });
402396
402842
  }
402397
402843
  }
402398
402844
  async function revokeToken2(refreshToken) {
@@ -402478,7 +402924,7 @@ function classifyCredentialValidationError2(error54) {
402478
402924
  }
402479
402925
  return { ok: false, reason: "unknown", message };
402480
402926
  }
402481
- var LETTA_CLOUD_API_URL2 = "https://api.letta.com", OAUTH_CONFIG2;
402927
+ var LETTA_CLOUD_API_URL2 = "https://api.letta.com", OAUTH_CONFIG2, OAuthRefreshError2;
402482
402928
  var init_oauth4 = __esm(() => {
402483
402929
  init_letta_client();
402484
402930
  init_error();
@@ -402489,6 +402935,18 @@ var init_oauth4 = __esm(() => {
402489
402935
  authBaseUrl: "https://app.letta.com",
402490
402936
  apiBaseUrl: LETTA_CLOUD_API_URL2
402491
402937
  };
402938
+ OAuthRefreshError2 = class OAuthRefreshError2 extends Error {
402939
+ retryable;
402940
+ status;
402941
+ oauthCode;
402942
+ constructor(message, options3) {
402943
+ super(message, { cause: options3.cause });
402944
+ this.name = "OAuthRefreshError";
402945
+ this.retryable = options3.retryable;
402946
+ this.status = options3.status;
402947
+ this.oauthCode = options3.oauthCode;
402948
+ }
402949
+ };
402492
402950
  });
402493
402951
 
402494
402952
  // node_modules/react/cjs/react.development.js
@@ -423490,18 +423948,18 @@ var stdoutColor, stderrColor, GENERATOR, STYLER, IS_EMPTY, levelMapping, styles4
423490
423948
  return getModelAnsi("rgb", level, type3, ...ansi_styles_default2.hexToRgb(...arguments_));
423491
423949
  }
423492
423950
  return ansi_styles_default2[type3][model](...arguments_);
423493
- }, usedModels, proto, createStyler = (open2, close, parent) => {
423951
+ }, usedModels, proto, createStyler = (open3, close, parent) => {
423494
423952
  let openAll;
423495
423953
  let closeAll;
423496
423954
  if (parent === undefined) {
423497
- openAll = open2;
423955
+ openAll = open3;
423498
423956
  closeAll = close;
423499
423957
  } else {
423500
- openAll = parent.openAll + open2;
423958
+ openAll = parent.openAll + open3;
423501
423959
  closeAll = close + parent.closeAll;
423502
423960
  }
423503
423961
  return {
423504
- open: open2,
423962
+ open: open3,
423505
423963
  close,
423506
423964
  openAll,
423507
423965
  closeAll,
@@ -441275,7 +441733,7 @@ var init_conversation_search = __esm(() => {
441275
441733
  });
441276
441734
 
441277
441735
  // src/utils/file-lock.ts
441278
- import { open as open2, readFile as readFile14, stat as stat10, unlink as unlink4 } from "node:fs/promises";
441736
+ import { open as open3, readFile as readFile13, stat as stat10, unlink as unlink4 } from "node:fs/promises";
441279
441737
  async function withFileLock(lockPath, fn, options3) {
441280
441738
  const opts = { ...DEFAULT_OPTIONS, ...options3 };
441281
441739
  const release = await acquireFileLock(lockPath, opts);
@@ -441293,7 +441751,7 @@ async function acquireFileLock(lockPath, opts) {
441293
441751
  });
441294
441752
  while (true) {
441295
441753
  try {
441296
- const handle2 = await open2(lockPath, "wx");
441754
+ const handle2 = await open3(lockPath, "wx");
441297
441755
  try {
441298
441756
  await handle2.writeFile(payload, "utf-8");
441299
441757
  await handle2.close();
@@ -441329,7 +441787,7 @@ async function acquireFileLock(lockPath, opts) {
441329
441787
  async function tryReapStaleLock(lockPath, staleMs) {
441330
441788
  let raw2;
441331
441789
  try {
441332
- raw2 = await readFile14(lockPath, "utf-8");
441790
+ raw2 = await readFile13(lockPath, "utf-8");
441333
441791
  } catch {
441334
441792
  return true;
441335
441793
  }
@@ -441386,9 +441844,9 @@ import {
441386
441844
  appendFile,
441387
441845
  mkdir as mkdir12,
441388
441846
  readdir as readdir8,
441389
- readFile as readFile15,
441847
+ readFile as readFile14,
441390
441848
  stat as stat11,
441391
- writeFile as writeFile13
441849
+ writeFile as writeFile12
441392
441850
  } from "node:fs/promises";
441393
441851
  import { join as join46 } from "node:path";
441394
441852
  function buildReflectionSubagentPrompt(input) {
@@ -441446,7 +441904,7 @@ async function collectParentMemoryFiles(memoryDir) {
441446
441904
  continue;
441447
441905
  }
441448
441906
  try {
441449
- const content = await readFile15(entryPath, "utf-8");
441907
+ const content = await readFile14(entryPath, "utf-8");
441450
441908
  const { frontmatter } = parseFrontmatter(content);
441451
441909
  const description = typeof frontmatter.description === "string" ? frontmatter.description : undefined;
441452
441910
  files.push({
@@ -441775,11 +442233,11 @@ function lineToTranscriptEntry(line, capturedAt) {
441775
442233
  }
441776
442234
  async function ensurePaths(paths) {
441777
442235
  await mkdir12(paths.rootDir, { recursive: true });
441778
- await writeFile13(paths.transcriptPath, "", { encoding: "utf-8", flag: "a" });
442236
+ await writeFile12(paths.transcriptPath, "", { encoding: "utf-8", flag: "a" });
441779
442237
  }
441780
442238
  async function readTranscriptLines(paths) {
441781
442239
  try {
441782
- const raw2 = await readFile15(paths.transcriptPath, "utf-8");
442240
+ const raw2 = await readFile14(paths.transcriptPath, "utf-8");
441783
442241
  return raw2.split(`
441784
442242
  `).map((line) => line.trim()).filter((line) => line.length > 0);
441785
442243
  } catch {
@@ -441852,7 +442310,7 @@ function buildUnreflectedStateFromTranscript(lines) {
441852
442310
  async function readState(paths) {
441853
442311
  let raw2 = null;
441854
442312
  try {
441855
- raw2 = await readFile15(paths.statePath, "utf-8");
442313
+ raw2 = await readFile14(paths.statePath, "utf-8");
441856
442314
  } catch {
441857
442315
  raw2 = null;
441858
442316
  }
@@ -441877,7 +442335,7 @@ async function readState(paths) {
441877
442335
  }
441878
442336
  async function writeState(paths, state) {
441879
442337
  state.steps_since_last_successful_reflection = Math.max(0, state.total_completed_steps - state.reflected_completed_steps);
441880
- await writeFile13(paths.statePath, `${JSON.stringify(state, null, 2)}
442338
+ await writeFile12(paths.statePath, `${JSON.stringify(state, null, 2)}
441881
442339
  `, "utf-8");
441882
442340
  }
441883
442341
  function buildPayloadPath(rootDir, kind) {
@@ -442255,7 +442713,7 @@ async function buildReflectionAutoPayload(options3) {
442255
442713
  candidates: sortedCandidates
442256
442714
  };
442257
442715
  const candidatesPath = buildPayloadPath(payloadRoot, "candidates");
442258
- await writeFile13(candidatesPath, `${JSON.stringify(candidateSet, null, 2)}
442716
+ await writeFile12(candidatesPath, `${JSON.stringify(candidateSet, null, 2)}
442259
442717
  `, "utf-8");
442260
442718
  return { candidatesPath, candidates: candidateSet };
442261
442719
  }
@@ -442263,7 +442721,7 @@ function isReflectionAutoPriority(value) {
442263
442721
  return value === "high" || value === "medium" || value === "low";
442264
442722
  }
442265
442723
  async function readReflectionAutoSelection(options3) {
442266
- const raw2 = options3.selectionReport ?? (options3.selectionOutputPath ? await readFile15(options3.selectionOutputPath, "utf-8") : "");
442724
+ const raw2 = options3.selectionReport ?? (options3.selectionOutputPath ? await readFile14(options3.selectionOutputPath, "utf-8") : "");
442267
442725
  const parsed = parseReflectionAutoSelectionJson(raw2);
442268
442726
  if (!parsed || typeof parsed !== "object") {
442269
442727
  throw new Error("Reflection selector did not return valid JSON.");
@@ -442383,7 +442841,7 @@ async function buildAutoReflectionPayload(agentId, conversationId, systemPrompt)
442383
442841
  return null;
442384
442842
  }
442385
442843
  const payloadPath = buildPayloadPath(paths.rootDir, "auto");
442386
- await writeFile13(payloadPath, transcript, "utf-8");
442844
+ await writeFile12(payloadPath, transcript, "utf-8");
442387
442845
  state.last_reflection_started_at = new Date().toISOString();
442388
442846
  await writeState(paths, state);
442389
442847
  return {
@@ -442472,7 +442930,7 @@ async function buildMultiReflectionPayload(options3) {
442472
442930
  return null;
442473
442931
  }
442474
442932
  const payloadPath2 = buildPayloadPath(payloadRoot, "slice");
442475
- await writeFile13(payloadPath2, transcript, "utf-8");
442933
+ await writeFile12(payloadPath2, transcript, "utf-8");
442476
442934
  state.last_reflection_started_at = new Date().toISOString();
442477
442935
  await writeState(paths, state);
442478
442936
  return {
@@ -442514,7 +442972,7 @@ async function buildMultiReflectionPayload(options3) {
442514
442972
  transcripts
442515
442973
  };
442516
442974
  const payloadPath = buildPayloadPath(payloadRoot, "multi");
442517
- await writeFile13(payloadPath, `${JSON.stringify(manifest, null, 2)}
442975
+ await writeFile12(payloadPath, `${JSON.stringify(manifest, null, 2)}
442518
442976
  `, "utf-8");
442519
442977
  return {
442520
442978
  payloadPath,
@@ -442898,10 +443356,9 @@ async function prepareReflectionMemoryWorktreeLaunch(params) {
442898
443356
  parentMemoryDir: memoryDir
442899
443357
  });
442900
443358
  try {
442901
- const parentMemory = await buildParentMemorySnapshot(worktree.worktreeDir);
442902
- const reflectionPrompt = buildReflectionSubagentPrompt({
443359
+ const reflectionPrompt = params.reflectionPromptOverride ?? buildReflectionSubagentPrompt({
442903
443360
  instruction: params.instruction,
442904
- parentMemory
443361
+ parentMemory: await buildParentMemorySnapshot(worktree.worktreeDir)
442905
443362
  });
442906
443363
  return { worktree, reflectionPrompt };
442907
443364
  } catch (error54) {
@@ -442982,7 +443439,8 @@ async function launchReflectionSubagent(options3) {
442982
443439
  }
442983
443440
  const { worktree, reflectionPrompt } = await prepareReflectionMemoryWorktreeLaunch({
442984
443441
  agentId,
442985
- instruction: options3.instruction
443442
+ instruction: options3.instruction,
443443
+ reflectionPromptOverride: options3.reflectionPromptOverride
442986
443444
  });
442987
443445
  preparedWorktree = worktree;
442988
443446
  const { spawnBackgroundSubagentTask: spawnBackgroundSubagentTask2, waitForBackgroundSubagentAgentId: waitForBackgroundSubagentAgentId2 } = await Promise.resolve().then(() => (init_task(), exports_task));
@@ -443001,6 +443459,7 @@ async function launchReflectionSubagent(options3) {
443001
443459
  prompt: reflectionPrompt,
443002
443460
  description,
443003
443461
  model: options3.model,
443462
+ systemPromptOverride: options3.reflectionSystemPromptOverride,
443004
443463
  silentCompletion: true,
443005
443464
  transcriptPath: autoPayload.payloadPath,
443006
443465
  memoryScope: buildReflectionMemoryScope(worktree),
@@ -443322,19 +443781,18 @@ function getListenerBlockedReason(lifecycle, pendingApprovalsLen) {
443322
443781
  return null;
443323
443782
  }
443324
443783
 
443325
- // src/websocket/listener/queue.ts
443326
- var exports_queue = {};
443327
- __export(exports_queue, {
443328
- shouldQueueInboundMessage: () => shouldQueueInboundMessage,
443329
- shouldProcessInboundMessageDirectly: () => shouldProcessInboundMessageDirectly,
443330
- scheduleQueuePump: () => scheduleQueuePump,
443331
- pickBatchActingUserId: () => pickBatchActingUserId,
443332
- normalizeMessageContentImages: () => normalizeMessageContentImages2,
443333
- normalizeInboundMessages: () => normalizeInboundMessages,
443334
- getQueueItemsScope: () => getQueueItemsScope,
443335
- getQueueItemScope: () => getQueueItemScope,
443336
- consumeQueuedTurn: () => consumeQueuedTurn
443784
+ // src/websocket/listener/image-policy.ts
443785
+ function getInboundImageFailureMode(incoming) {
443786
+ return (incoming?.channelTurnSources?.length ?? 0) > 0 ? "drop" : "strict";
443787
+ }
443788
+ function getInboundImageFailureModes(incoming) {
443789
+ return getInboundImageFailureMode(incoming) === "drop" ? buildImageFailureModesByMessageOtid(incoming.messages, "drop") : undefined;
443790
+ }
443791
+ var init_image_policy = __esm(async () => {
443792
+ await init_message_image_normalization();
443337
443793
  });
443794
+
443795
+ // src/websocket/listener/queue.ts
443338
443796
  function getQueueItemScope(item) {
443339
443797
  if (!item) {
443340
443798
  return {};
@@ -443438,27 +443896,6 @@ async function buildChannelTurnProgressBuilder(agentId) {
443438
443896
  return createChannelTurnProgressBuilder();
443439
443897
  }
443440
443898
  }
443441
- async function normalizeMessageContentImages2(content, resize = resizeImageIfNeeded3, failureMode = "strict") {
443442
- return await normalizeMessageContentImages(content, resize, failureMode);
443443
- }
443444
- async function normalizeInboundMessages(messages, resize = resizeImageIfNeeded3, options3 = {}) {
443445
- let didChange = false;
443446
- const normalizedMessages = await Promise.all(messages.map(async (message) => {
443447
- if (!("content" in message)) {
443448
- return message;
443449
- }
443450
- const normalizedContent = await normalizeMessageContentImages2(message.content, resize, options3.imageFailureMode ?? "strict");
443451
- if (normalizedContent !== message.content) {
443452
- didChange = true;
443453
- return {
443454
- ...message,
443455
- content: normalizedContent
443456
- };
443457
- }
443458
- return message;
443459
- }));
443460
- return didChange ? normalizedMessages : messages;
443461
- }
443462
443899
  function getPrimaryQueueMessageItem(items3) {
443463
443900
  for (const item of items3) {
443464
443901
  if (item.kind === "message") {
@@ -443559,10 +443996,18 @@ function consumeQueuedTurn(runtime) {
443559
443996
  let hasTaskNotification = false;
443560
443997
  let hasCronPrompt = false;
443561
443998
  let hasModContinue = false;
443999
+ let batchImageFailureMode = null;
443562
444000
  for (const item of queuedItems) {
443563
444001
  if (!isCoalescable(item.kind) || !hasSameQueueScope(firstQueuedItem, item)) {
443564
444002
  break;
443565
444003
  }
444004
+ if (item.kind === "message") {
444005
+ const itemImageFailureMode = getInboundImageFailureMode(runtime.queuedMessagesByItemId.get(item.id));
444006
+ if (batchImageFailureMode !== null && itemImageFailureMode !== batchImageFailureMode) {
444007
+ break;
444008
+ }
444009
+ batchImageFailureMode = itemImageFailureMode;
444010
+ }
443566
444011
  queueLen += 1;
443567
444012
  if (item.kind === "message") {
443568
444013
  hasMessage = true;
@@ -443695,8 +444140,7 @@ var init_queue = __esm(async () => {
443695
444140
  init_runtime6();
443696
444141
  await __promiseAll([
443697
444142
  init_progress_builder(),
443698
- init_image_resize(),
443699
- init_message_image_normalization(),
444143
+ init_image_policy(),
443700
444144
  init_protocol_outbound()
443701
444145
  ]);
443702
444146
  });
@@ -445421,6 +445865,303 @@ var init_approval = __esm(async () => {
445421
445865
  ]);
445422
445866
  });
445423
445867
 
445868
+ // src/websocket/listen-register.ts
445869
+ import { createHash as createHash6 } from "node:crypto";
445870
+ function deriveListenerInstanceId(surface, connectionName) {
445871
+ const nameHash = createHash6("sha256").update(connectionName).digest("hex").slice(0, 16);
445872
+ return `${surface}-${nameHash}`;
445873
+ }
445874
+ function isTransientRegistrationError(error54) {
445875
+ if (error54 instanceof RegistrationError) {
445876
+ return error54.statusCode === 0 || error54.statusCode === 429 || error54.statusCode >= 500;
445877
+ }
445878
+ return true;
445879
+ }
445880
+ function parseRetryAfterMs(value) {
445881
+ if (!value) {
445882
+ return;
445883
+ }
445884
+ const seconds = Number(value);
445885
+ if (Number.isFinite(seconds) && seconds >= 0) {
445886
+ return seconds * 1000;
445887
+ }
445888
+ const dateMs = Date.parse(value);
445889
+ if (Number.isFinite(dateMs)) {
445890
+ return Math.max(0, dateMs - Date.now());
445891
+ }
445892
+ return;
445893
+ }
445894
+ async function registerWithCloud(opts, fetchImpl = fetch) {
445895
+ const registerUrl = `${opts.serverUrl}/v1/environments/register`;
445896
+ const response = await fetchImpl(registerUrl, {
445897
+ method: "POST",
445898
+ headers: {
445899
+ "Content-Type": "application/json",
445900
+ Authorization: `Bearer ${opts.apiKey}`,
445901
+ "X-Letta-Source": "letta-code"
445902
+ },
445903
+ body: JSON.stringify({
445904
+ deviceId: opts.deviceId,
445905
+ ...opts.listenerInstanceId ? { listenerInstanceId: opts.listenerInstanceId } : {},
445906
+ connectionName: opts.connectionName,
445907
+ metadata: {
445908
+ lettaCodeVersion: getVersion(),
445909
+ os: process.platform,
445910
+ nodeVersion: process.version,
445911
+ environmentMessageProtocol: "v2-input",
445912
+ supported_commands: SUPPORTED_REMOTE_COMMANDS,
445913
+ self_update: getSelfUpdateStatus()
445914
+ }
445915
+ })
445916
+ }).catch((fetchError) => {
445917
+ const msg = fetchError instanceof Error ? fetchError.message : String(fetchError);
445918
+ throw new RegistrationError(`Network error: ${msg}`, 0);
445919
+ });
445920
+ if (!response.ok) {
445921
+ let detail = `HTTP ${response.status}`;
445922
+ const retryAfterMs = parseRetryAfterMs(response.headers.get("Retry-After"));
445923
+ const text2 = await response.text().catch(() => "");
445924
+ if (text2) {
445925
+ try {
445926
+ const parsed = JSON.parse(text2);
445927
+ if (parsed.message) {
445928
+ detail = parsed.message;
445929
+ } else if (parsed.error) {
445930
+ detail = `HTTP ${response.status}: ${parsed.error}`;
445931
+ if (parsed.errorCode) {
445932
+ detail += ` (${parsed.errorCode})`;
445933
+ }
445934
+ } else {
445935
+ detail += `: ${text2.slice(0, 200)}`;
445936
+ }
445937
+ } catch {
445938
+ detail += `: ${text2.slice(0, 200)}`;
445939
+ }
445940
+ }
445941
+ throw new RegistrationError(detail, response.status, retryAfterMs);
445942
+ }
445943
+ let body3;
445944
+ try {
445945
+ body3 = await response.json();
445946
+ } catch {
445947
+ throw new RegistrationError("Server returned non-JSON response — is the server running?", response.status);
445948
+ }
445949
+ const result = body3;
445950
+ if (typeof result.connectionId !== "string" || typeof result.wsUrl !== "string") {
445951
+ throw new RegistrationError("Server returned unexpected response shape (missing connectionId or wsUrl)", response.status);
445952
+ }
445953
+ return {
445954
+ connectionId: result.connectionId,
445955
+ wsUrl: result.wsUrl,
445956
+ supportsSplitStatusChannels: result.supportsSplitStatusChannels === true
445957
+ };
445958
+ }
445959
+ async function registerWithCloudRetry(opts, callbacks) {
445960
+ const startTime = Date.now();
445961
+ const maxDurationMs = callbacks?.maxDurationMs ?? REGISTER_MAX_DURATION_MS;
445962
+ const sleep9 = callbacks?.sleep ?? ((delayMs) => new Promise((resolve31) => setTimeout(resolve31, delayMs)));
445963
+ let attempt = 0;
445964
+ for (;; ) {
445965
+ try {
445966
+ return await registerWithCloud(opts, callbacks?.fetchImpl);
445967
+ } catch (error54) {
445968
+ const elapsed = Date.now() - startTime;
445969
+ if (!isTransientRegistrationError(error54) || elapsed >= maxDurationMs) {
445970
+ throw error54;
445971
+ }
445972
+ attempt++;
445973
+ const backoffDelay = Math.min(REGISTER_INITIAL_DELAY_MS * 2 ** (attempt - 1), REGISTER_MAX_DELAY_MS);
445974
+ const delay2 = error54 instanceof RegistrationError && error54.retryAfterMs !== undefined ? Math.max(error54.retryAfterMs, backoffDelay) : backoffDelay;
445975
+ const jitterWindow = Math.min(REGISTER_MAX_JITTER_MS, Math.floor(delay2 * REGISTER_JITTER_RATIO));
445976
+ const jitter = jitterWindow > 0 ? Math.floor((callbacks?.random ?? Math.random)() * jitterWindow) : 0;
445977
+ const retryDelay = delay2 + jitter;
445978
+ if (error54 instanceof Error) {
445979
+ callbacks?.onRetry?.(attempt, retryDelay, error54);
445980
+ }
445981
+ await sleep9(retryDelay);
445982
+ }
445983
+ }
445984
+ }
445985
+ var RegistrationError, REGISTER_INITIAL_DELAY_MS = 1000, REGISTER_MAX_DELAY_MS = 30000, REGISTER_MAX_DURATION_MS, REGISTER_MAX_JITTER_MS = 1000, REGISTER_JITTER_RATIO = 0.25;
445986
+ var init_listen_register = __esm(() => {
445987
+ init_auto_update();
445988
+ init_version();
445989
+ init_listener_constants();
445990
+ RegistrationError = class RegistrationError extends Error {
445991
+ statusCode;
445992
+ retryAfterMs;
445993
+ constructor(message, statusCode2, retryAfterMs) {
445994
+ super(message);
445995
+ this.name = "RegistrationError";
445996
+ this.statusCode = statusCode2;
445997
+ this.retryAfterMs = retryAfterMs;
445998
+ }
445999
+ };
446000
+ REGISTER_MAX_DURATION_MS = 2 * 60 * 1000;
446001
+ });
446002
+
446003
+ // src/websocket/listener/auth.ts
446004
+ function getListenerOAuthDeps() {
446005
+ return listenerOAuthDepsOverride ?? defaultListenerOAuthDeps;
446006
+ }
446007
+ function errorMessage(error54) {
446008
+ return error54 instanceof Error ? error54.message : String(error54);
446009
+ }
446010
+ function getListenerServerUrl(settings3) {
446011
+ return process.env.LETTA_BASE_URL || settings3.env?.LETTA_BASE_URL || getListenerOAuthDeps().LETTA_CLOUD_API_URL;
446012
+ }
446013
+ function normalizeListenerBaseUrl(url2) {
446014
+ return url2.trim().replace(/\/+$/, "");
446015
+ }
446016
+ function isCloudListenerServerUrl(serverUrl) {
446017
+ return normalizeListenerBaseUrl(serverUrl) === normalizeListenerBaseUrl(getListenerOAuthDeps().LETTA_CLOUD_API_URL);
446018
+ }
446019
+ function shouldRefreshListenerAccessToken(settings3, apiKey) {
446020
+ if (!settings3.refreshToken) {
446021
+ return false;
446022
+ }
446023
+ if (!apiKey) {
446024
+ return true;
446025
+ }
446026
+ return settings3.tokenExpiresAt !== undefined && Date.now() >= settings3.tokenExpiresAt - LISTENER_TOKEN_REFRESH_WINDOW_MS;
446027
+ }
446028
+ function isAccessTokenStillValid(settings3, apiKey) {
446029
+ return Boolean(apiKey && (settings3.tokenExpiresAt === undefined || Date.now() < settings3.tokenExpiresAt));
446030
+ }
446031
+ async function refreshListenerAccessToken(settings3, deviceId, connectionName) {
446032
+ if (!settings3.refreshToken) {
446033
+ throw new MissingListenerApiKeyError;
446034
+ }
446035
+ const now = Date.now();
446036
+ console.log("Access token expired, refreshing...");
446037
+ const tokens = await refreshAccessTokenSingleFlight(settings3.refreshToken, deviceId, connectionName, getListenerOAuthDeps().refreshAccessToken);
446038
+ settingsManager.updateSettings({
446039
+ env: { LETTA_API_KEY: tokens.access_token },
446040
+ refreshToken: tokens.refresh_token ?? settings3.refreshToken,
446041
+ tokenExpiresAt: now + tokens.expires_in * 1000
446042
+ });
446043
+ await settingsManager.flush();
446044
+ console.log("Token refreshed successfully.");
446045
+ return tokens.access_token;
446046
+ }
446047
+ async function runListenerOAuthLogin(deviceId, connectionName) {
446048
+ const oauthDeps = getListenerOAuthDeps();
446049
+ console.log(`No API key found. Starting OAuth login...
446050
+ `);
446051
+ const deviceData = await oauthDeps.requestDeviceCode();
446052
+ console.log(`To authenticate, visit: ${deviceData.verification_uri_complete}`);
446053
+ console.log(`Your code: ${deviceData.user_code}
446054
+ `);
446055
+ console.log(`Waiting for authorization...
446056
+ `);
446057
+ const tokens = await oauthDeps.pollForToken(deviceData.device_code, deviceData.interval, deviceData.expires_in, deviceId, connectionName);
446058
+ const now = Date.now();
446059
+ settingsManager.updateSettings({
446060
+ env: { LETTA_API_KEY: tokens.access_token },
446061
+ ...tokens.refresh_token ? { refreshToken: tokens.refresh_token } : {},
446062
+ tokenExpiresAt: now + tokens.expires_in * 1000
446063
+ });
446064
+ await settingsManager.flush();
446065
+ console.log(`Authenticated successfully.
446066
+ `);
446067
+ return tokens.access_token;
446068
+ }
446069
+ async function resolveListenerAuth(deviceId, connectionName, options3) {
446070
+ const allowInteractiveOAuth = options3.allowInteractiveOAuth ?? true;
446071
+ const settings3 = await settingsManager.getSettingsWithSecureTokens();
446072
+ const serverUrl = getListenerServerUrl(settings3);
446073
+ const envApiKey = process.env.LETTA_API_KEY;
446074
+ if (envApiKey) {
446075
+ return { serverUrl, apiKey: envApiKey };
446076
+ }
446077
+ let apiKey = settings3.env?.LETTA_API_KEY;
446078
+ if (!isCloudListenerServerUrl(serverUrl)) {
446079
+ if (!apiKey) {
446080
+ throw new MissingListenerApiKeyError;
446081
+ }
446082
+ return { serverUrl, apiKey };
446083
+ }
446084
+ if (shouldRefreshListenerAccessToken(settings3, apiKey)) {
446085
+ try {
446086
+ apiKey = await refreshListenerAccessToken(settings3, deviceId, connectionName);
446087
+ } catch (refreshError) {
446088
+ const retryable = !(refreshError instanceof OAuthRefreshError) || refreshError.retryable;
446089
+ if (retryable && isAccessTokenStillValid(settings3, apiKey)) {
446090
+ console.warn(`Token refresh failed; using the current access token: ${errorMessage(refreshError)}`);
446091
+ return { serverUrl, apiKey };
446092
+ }
446093
+ if (retryable) {
446094
+ throw new ListenerAuthRetryableError(refreshError);
446095
+ }
446096
+ if (!allowInteractiveOAuth) {
446097
+ throw new ListenerReauthenticationRequiredError(refreshError);
446098
+ }
446099
+ console.warn(`Token refresh failed: ${errorMessage(refreshError)}`);
446100
+ apiKey = undefined;
446101
+ }
446102
+ }
446103
+ if (!apiKey) {
446104
+ if (!allowInteractiveOAuth) {
446105
+ throw new ListenerReauthenticationRequiredError;
446106
+ }
446107
+ apiKey = await runListenerOAuthLogin(deviceId, connectionName);
446108
+ }
446109
+ return { serverUrl, apiKey };
446110
+ }
446111
+ async function resolveListenerRegistrationOptions(deviceId, connectionName, options3 = {}) {
446112
+ const auth = await resolveListenerAuth(deviceId, connectionName, options3);
446113
+ return {
446114
+ ...auth,
446115
+ deviceId,
446116
+ connectionName,
446117
+ listenerInstanceId: deriveListenerInstanceId(options3.surface ?? "server", connectionName)
446118
+ };
446119
+ }
446120
+ async function resolveListenerReconnectAuth(options3) {
446121
+ try {
446122
+ const auth = await resolveListenerAuth(options3.deviceId, options3.connectionName, { allowInteractiveOAuth: false });
446123
+ return { kind: "ready", apiKey: auth.apiKey };
446124
+ } catch (error54) {
446125
+ if (error54 instanceof ListenerAuthRetryableError) {
446126
+ return { kind: "retry" };
446127
+ }
446128
+ throw error54;
446129
+ }
446130
+ }
446131
+ var LISTENER_TOKEN_REFRESH_WINDOW_MS, defaultListenerOAuthDeps, listenerOAuthDepsOverride = null, MissingListenerApiKeyError, ListenerAuthRetryableError, ListenerReauthenticationRequiredError;
446132
+ var init_auth = __esm(() => {
446133
+ init_oauth();
446134
+ init_oauth_refresh();
446135
+ init_settings_manager();
446136
+ init_listen_register();
446137
+ LISTENER_TOKEN_REFRESH_WINDOW_MS = 5 * 60 * 1000;
446138
+ defaultListenerOAuthDeps = {
446139
+ LETTA_CLOUD_API_URL,
446140
+ pollForToken,
446141
+ refreshAccessToken,
446142
+ requestDeviceCode
446143
+ };
446144
+ MissingListenerApiKeyError = class MissingListenerApiKeyError extends Error {
446145
+ constructor() {
446146
+ super("LETTA_API_KEY not found");
446147
+ this.name = "MissingListenerApiKeyError";
446148
+ }
446149
+ };
446150
+ ListenerAuthRetryableError = class ListenerAuthRetryableError extends Error {
446151
+ constructor(refreshError) {
446152
+ super(`Could not refresh listener credentials: ${errorMessage(refreshError)}`);
446153
+ this.name = "ListenerAuthRetryableError";
446154
+ }
446155
+ };
446156
+ ListenerReauthenticationRequiredError = class ListenerReauthenticationRequiredError extends Error {
446157
+ constructor(refreshError) {
446158
+ const detail = refreshError ? `: ${errorMessage(refreshError)}` : "";
446159
+ super(`Saved Letta API credentials require reauthentication${detail}. Run letta to sign in again, or set LETTA_API_KEY.`);
446160
+ this.name = "ListenerReauthenticationRequiredError";
446161
+ }
446162
+ };
446163
+ });
446164
+
445424
446165
  // src/agent/acting-user.ts
445425
446166
  function actingUserRequestOptions(actingUserId) {
445426
446167
  if (!actingUserId) {
@@ -445792,7 +446533,7 @@ __export(exports_custom, {
445792
446533
  COMMANDS_DIR: () => COMMANDS_DIR
445793
446534
  });
445794
446535
  import { existsSync as existsSync43 } from "node:fs";
445795
- import { readdir as readdir9, readFile as readFile16 } from "node:fs/promises";
446536
+ import { readdir as readdir9, readFile as readFile15 } from "node:fs/promises";
445796
446537
  import { basename as basename20, dirname as dirname24, join as join48 } from "node:path";
445797
446538
  async function getCustomCommands() {
445798
446539
  if (cachedCommands !== null) {
@@ -445851,7 +446592,7 @@ async function findCommandFiles(currentPath, rootPath, commands, source2) {
445851
446592
  } catch (_error) {}
445852
446593
  }
445853
446594
  async function parseCommandFile(filePath, rootPath, source2) {
445854
- const content = await readFile16(filePath, "utf-8");
446595
+ const content = await readFile15(filePath, "utf-8");
445855
446596
  const { frontmatter, body: body3 } = parseFrontmatter(content);
445856
446597
  const id2 = basename20(filePath, ".md");
445857
446598
  const relativePath = dirname24(filePath).slice(rootPath.length);
@@ -446010,7 +446751,7 @@ var init_init_command = __esm(() => {
446010
446751
  });
446011
446752
 
446012
446753
  // src/cli/helpers/skill-name-frontmatter-repair.ts
446013
- import { readdir as readdir10, readFile as readFile17, stat as stat12, writeFile as writeFile14 } from "node:fs/promises";
446754
+ import { readdir as readdir10, readFile as readFile16, stat as stat12, writeFile as writeFile13 } from "node:fs/promises";
446014
446755
  import { basename as basename21, dirname as dirname25, join as join49, relative as relative8 } from "node:path";
446015
446756
  async function pathExists(path32) {
446016
446757
  try {
@@ -446117,7 +446858,7 @@ async function repairMissingSkillNameFrontmatter(memoryDir) {
446117
446858
  const displayPath = relative8(memoryDir, skillFile).replace(/\\/g, "/");
446118
446859
  result.scanned++;
446119
446860
  try {
446120
- const content = await readFile17(skillFile, "utf8");
446861
+ const content = await readFile16(skillFile, "utf8");
446121
446862
  const repair3 = repairSkillNameFrontmatterContent(content, basename21(dirname25(skillFile)));
446122
446863
  if (repair3.reason) {
446123
446864
  result.skipped.push({ path: displayPath, reason: repair3.reason });
@@ -446126,7 +446867,7 @@ async function repairMissingSkillNameFrontmatter(memoryDir) {
446126
446867
  if (!repair3.changed) {
446127
446868
  continue;
446128
446869
  }
446129
- await writeFile14(skillFile, repair3.content, "utf8");
446870
+ await writeFile13(skillFile, repair3.content, "utf8");
446130
446871
  result.repaired.push(displayPath);
446131
446872
  } catch (error54) {
446132
446873
  result.skipped.push({
@@ -447215,7 +447956,7 @@ function isCreateAgentCommand(value) {
447215
447956
  if (!value || typeof value !== "object")
447216
447957
  return false;
447217
447958
  const c = value;
447218
- return c.type === "create_agent" && typeof c.request_id === "string" && (c.personality === "memo" || c.personality === "blank" || c.personality === "tutorial" || c.personality === "linus" || c.personality === "kawaii") && (c.model === undefined || typeof c.model === "string") && (c.pin_global === undefined || typeof c.pin_global === "boolean");
447959
+ return c.type === "create_agent" && typeof c.request_id === "string" && (c.personality === "memo" || c.personality === "blank" || c.personality === "tutorial" || c.personality === "linus" || c.personality === "kawaii") && (c.model === undefined || typeof c.model === "string") && (c.tags === undefined || isStringArray6(c.tags)) && (c.pin_global === undefined || typeof c.pin_global === "boolean");
447219
447960
  }
447220
447961
  function isAgentListCommand(value) {
447221
447962
  if (!value || typeof value !== "object")
@@ -449685,9 +450426,9 @@ async function drainStream(stream12, buffers, refresh, abortSignal, onFirstMessa
449685
450426
  }
449686
450427
  }
449687
450428
  } catch (e2) {
449688
- const errorMessage = e2 instanceof Error ? e2.message : String(e2);
450429
+ const errorMessage2 = e2 instanceof Error ? e2.message : String(e2);
449689
450430
  const sdkDiagnostic = consumeLastSDKDiagnostic();
449690
- const errorMessageWithDiagnostic = sdkDiagnostic ? `${errorMessage} [${sdkDiagnostic}]` : errorMessage;
450431
+ const errorMessageWithDiagnostic = sdkDiagnostic ? `${errorMessage2} [${sdkDiagnostic}]` : errorMessage2;
449691
450432
  debugWarn("drainStream", "Stream error caught: %s last_chunk=%s stream=%s", errorMessageWithDiagnostic, lastChunkDebugSummary, summarizeStreamForDebug(stream12));
449692
450433
  if (e2 instanceof Error && e2.stack) {
449693
450434
  debugWarn("drainStream", "Stream error stack: %s", e2.stack);
@@ -450697,6 +451438,19 @@ function emitToolExecutionFinishedEvents(socket, runtime, params) {
450697
451438
  });
450698
451439
  }
450699
451440
  }
451441
+ function emitToolExecutionAbortedEvents(socket, runtime, params) {
451442
+ for (const toolCallId of params.toolCallIds) {
451443
+ const delta2 = {
451444
+ ...createLifecycleMessageBase("client_tool_end", params.runId),
451445
+ tool_call_id: toolCallId,
451446
+ status: "error"
451447
+ };
451448
+ emitCanonicalMessageDelta(socket, runtime, delta2, {
451449
+ agent_id: params.agentId,
451450
+ conversation_id: params.conversationId
451451
+ });
451452
+ }
451453
+ }
450700
451454
  function createToolExecutionOutputEmitter(socket, runtime, params) {
450701
451455
  const outputByToolCallId = new Map;
450702
451456
  const emitToolOutput = (toolCallId, outputState) => {
@@ -451287,6 +452041,17 @@ var init_approval_suggestions = __esm(async () => {
451287
452041
  ]);
451288
452042
  });
451289
452043
 
452044
+ // src/websocket/listener/continuation-input.ts
452045
+ function appendQueuedTurnToInput(input, queuedTurn) {
452046
+ return {
452047
+ input: [...input, ...queuedTurn.messages],
452048
+ imageFailureModesByMessageOtid: getInboundImageFailureModes(queuedTurn)
452049
+ };
452050
+ }
452051
+ var init_continuation_input = __esm(async () => {
452052
+ await init_image_policy();
452053
+ });
452054
+
451290
452055
  // src/websocket/listener/turn-terminal.ts
451291
452056
  function finishListenerTurn(runtime, lease, options3) {
451292
452057
  const transition = runtime.turnLifecycle.finish(lease, options3.stopReason);
@@ -451711,25 +452476,25 @@ async function resolveRecoveredApprovalResponse(runtime, socket, response, proce
451711
452476
  conversationId: recovered.conversationId,
451712
452477
  shouldEmit: () => runtime.turnLifecycle.isCurrent(recoveryLease)
451713
452478
  });
451714
- await ensureSecretsHydrated(runtime.listener, recovered.agentId);
451715
- if (!runtime.turnLifecycle.isCurrent(recoveryLease)) {
451716
- return true;
451717
- }
451718
- const preparedToolContext = await prepareToolExecutionContext({
451719
- agentId: recovered.agentId,
451720
- conversationId: recovered.conversationId,
451721
- workingDirectory,
451722
- permissionModeState: getOrCreateConversationPermissionModeStateRef(runtime.listener, recovered.agentId, recovered.conversationId),
451723
- modEvents: ensureListenerModAdapter(runtime.listener).events
451724
- });
451725
- if (!runtime.turnLifecycle.isCurrent(recoveryLease)) {
451726
- return true;
451727
- }
451728
- runtime.currentToolset = preparedToolContext.toolset;
451729
- runtime.currentToolsetPreference = preparedToolContext.toolsetPreference;
451730
- runtime.currentLoadedTools = preparedToolContext.preparedToolContext.loadedToolNames;
451731
452479
  let approvalResults;
451732
452480
  try {
452481
+ await ensureSecretsHydrated(runtime.listener, recovered.agentId);
452482
+ if (!runtime.turnLifecycle.isCurrent(recoveryLease)) {
452483
+ return true;
452484
+ }
452485
+ const preparedToolContext = await prepareToolExecutionContext({
452486
+ agentId: recovered.agentId,
452487
+ conversationId: recovered.conversationId,
452488
+ workingDirectory,
452489
+ permissionModeState: getOrCreateConversationPermissionModeStateRef(runtime.listener, recovered.agentId, recovered.conversationId),
452490
+ modEvents: ensureListenerModAdapter(runtime.listener).events
452491
+ });
452492
+ if (!runtime.turnLifecycle.isCurrent(recoveryLease)) {
452493
+ return true;
452494
+ }
452495
+ runtime.currentToolset = preparedToolContext.toolset;
452496
+ runtime.currentToolsetPreference = preparedToolContext.toolsetPreference;
452497
+ runtime.currentLoadedTools = preparedToolContext.preparedToolContext.loadedToolNames;
451733
452498
  approvalResults = await executeApprovals(decisions, undefined, {
451734
452499
  abortSignal: recoveryLease.signal,
451735
452500
  onStreamingOutput: emitToolExecutionOutput,
@@ -451741,6 +452506,17 @@ async function resolveRecoveredApprovalResponse(runtime, socket, response, proce
451741
452506
  } : undefined,
451742
452507
  channelTurnSources: executionChannelTurnSources
451743
452508
  });
452509
+ } catch (error54) {
452510
+ emitToolExecutionOutput.flush();
452511
+ if (runtime.turnLifecycle.isCurrent(recoveryLease)) {
452512
+ emitToolExecutionAbortedEvents(socket, runtime, {
452513
+ toolCallIds: approvedToolCallIds,
452514
+ runId: executionRunId,
452515
+ agentId: recovered.agentId,
452516
+ conversationId: recovered.conversationId
452517
+ });
452518
+ }
452519
+ throw error54;
451744
452520
  } finally {
451745
452521
  emitToolExecutionOutput.flush();
451746
452522
  }
@@ -451760,7 +452536,7 @@ async function resolveRecoveredApprovalResponse(runtime, socket, response, proce
451760
452536
  return true;
451761
452537
  }
451762
452538
  emitRuntimeStateUpdates(runtime, scope);
451763
- const continuationMessages = [
452539
+ let continuationMessages = [
451764
452540
  {
451765
452541
  type: "approval",
451766
452542
  approvals: approvalResults,
@@ -451768,11 +452544,13 @@ async function resolveRecoveredApprovalResponse(runtime, socket, response, proce
451768
452544
  }
451769
452545
  ];
451770
452546
  let continuationBatchId = `batch-recovered-${crypto.randomUUID()}`;
452547
+ let queuedChannelTurnSources;
451771
452548
  const consumedQueuedTurn = consumeQueuedTurn(runtime);
451772
452549
  if (consumedQueuedTurn) {
451773
452550
  const { dequeuedBatch, queuedTurn } = consumedQueuedTurn;
451774
452551
  continuationBatchId = dequeuedBatch.batchId;
451775
- continuationMessages.push(...queuedTurn.messages);
452552
+ continuationMessages = appendQueuedTurnToInput(continuationMessages, queuedTurn).input;
452553
+ queuedChannelTurnSources = queuedTurn.channelTurnSources;
451776
452554
  emitDequeuedUserMessage(socket, runtime, queuedTurn, dequeuedBatch);
451777
452555
  }
451778
452556
  if (!runtime.turnLifecycle.isCurrent(recoveryLease)) {
@@ -451782,7 +452560,8 @@ async function resolveRecoveredApprovalResponse(runtime, socket, response, proce
451782
452560
  type: "message",
451783
452561
  agentId: recovered.agentId,
451784
452562
  conversationId: recovered.conversationId,
451785
- messages: continuationMessages
452563
+ messages: continuationMessages,
452564
+ ...queuedChannelTurnSources?.length ? { channelTurnSources: queuedChannelTurnSources } : {}
451786
452565
  }, socket, runtime, opts?.onStatusChange, opts?.connectionId, continuationBatchId, recoveryLease);
451787
452566
  if (runtime.turnLifecycle.isCurrent(recoveryLease)) {
451788
452567
  throw new Error("Recovered continuation returned without finalizing");
@@ -451853,6 +452632,7 @@ var init_recovery = __esm(async () => {
451853
452632
  init_stream(),
451854
452633
  init_toolset(),
451855
452634
  init_approval_suggestions(),
452635
+ init_continuation_input(),
451856
452636
  init_interrupts(),
451857
452637
  init_mod_adapter2(),
451858
452638
  init_protocol_outbound(),
@@ -452082,17 +452862,20 @@ async function resolveStaleApprovals(runtime, socket, turnLease, deps = {}) {
452082
452862
  return null;
452083
452863
  }
452084
452864
  try {
452085
- const continuationMessages = [
452865
+ let continuationMessages = [
452086
452866
  {
452087
452867
  type: "approval",
452088
452868
  approvals: approvalResults,
452089
452869
  otid: crypto.randomUUID()
452090
452870
  }
452091
452871
  ];
452872
+ let queuedImageFailureModes;
452092
452873
  const consumedQueuedTurn = consumeQueuedTurn(runtime);
452093
452874
  if (consumedQueuedTurn) {
452094
452875
  const { dequeuedBatch, queuedTurn } = consumedQueuedTurn;
452095
- continuationMessages.push(...queuedTurn.messages);
452876
+ const appended = appendQueuedTurnToInput(continuationMessages, queuedTurn);
452877
+ continuationMessages = appended.input;
452878
+ queuedImageFailureModes = appended.imageFailureModesByMessageOtid;
452096
452879
  emitDequeuedUserMessage(socket, runtime, queuedTurn, dequeuedBatch);
452097
452880
  }
452098
452881
  const continuationMessagesWithSkillContent = injectQueuedSkillContent(continuationMessages);
@@ -452101,7 +452884,8 @@ async function resolveStaleApprovals(runtime, socket, turnLease, deps = {}) {
452101
452884
  streamTokens: true,
452102
452885
  background: true,
452103
452886
  workingDirectory: recoveryWorkingDirectory,
452104
- preparedToolContext: preparedToolContext.preparedToolContext
452887
+ preparedToolContext: preparedToolContext.preparedToolContext,
452888
+ ...queuedImageFailureModes ? { imageFailureModesByMessageOtid: queuedImageFailureModes } : {}
452105
452889
  }, socket, runtime, turnLease, { allowApprovalRecovery: false });
452106
452890
  assertCurrentTurnLease();
452107
452891
  if (recoverySendResult.kind !== "stream") {
@@ -452442,6 +453226,7 @@ var init_send = __esm(async () => {
452442
453226
  init_message(),
452443
453227
  init_toolset(),
452444
453228
  init_approval(),
453229
+ init_continuation_input(),
452445
453230
  init_mod_adapter2(),
452446
453231
  init_protocol_outbound(),
452447
453232
  init_queue(),
@@ -454823,6 +455608,17 @@ async function handleApprovalStop(params) {
454823
455608
  channelTurnSources: runtime.activeChannelTurn?.sources,
454824
455609
  onFileWrite
454825
455610
  });
455611
+ } catch (error54) {
455612
+ emitToolExecutionOutput.flush();
455613
+ if (!shouldInterrupt()) {
455614
+ emitToolExecutionAbortedEvents(socket, runtime, {
455615
+ toolCallIds: lastExecutingToolCallIds,
455616
+ runId: executionRunId,
455617
+ agentId,
455618
+ conversationId
455619
+ });
455620
+ }
455621
+ throw error54;
454826
455622
  } finally {
454827
455623
  emitToolExecutionOutput.flush();
454828
455624
  }
@@ -454846,7 +455642,7 @@ async function handleApprovalStop(params) {
454846
455642
  if (shouldInterrupt()) {
454847
455643
  return interruptTermination();
454848
455644
  }
454849
- const nextInput = [
455645
+ let nextInput = [
454850
455646
  {
454851
455647
  type: "approval",
454852
455648
  approvals: persistedExecutionResults,
@@ -454854,11 +455650,14 @@ async function handleApprovalStop(params) {
454854
455650
  }
454855
455651
  ];
454856
455652
  let continuationBatchId = dequeuedBatchId;
455653
+ let queuedImageFailureModes;
454857
455654
  const consumedQueuedTurn = consumeQueuedTurn(runtime);
454858
455655
  if (consumedQueuedTurn) {
454859
455656
  const { dequeuedBatch, queuedTurn } = consumedQueuedTurn;
454860
455657
  continuationBatchId = dequeuedBatch.batchId;
454861
- nextInput.push(...queuedTurn.messages);
455658
+ const appended = appendQueuedTurnToInput(nextInput, queuedTurn);
455659
+ nextInput = appended.input;
455660
+ queuedImageFailureModes = appended.imageFailureModesByMessageOtid;
454862
455661
  emitDequeuedUserMessage(socket, runtime, queuedTurn, dequeuedBatch);
454863
455662
  }
454864
455663
  const nextInputWithSkillContent = injectQueuedSkillContent(nextInput);
@@ -454871,8 +455670,11 @@ async function handleApprovalStop(params) {
454871
455670
  });
454872
455671
  let sendResult;
454873
455672
  try {
455673
+ const sendOptions = buildSendOptions() ?? {};
455674
+ const imageFailureModesByMessageOtid = mergeImageFailureModesByMessageOtid(sendOptions.imageFailureModesByMessageOtid, queuedImageFailureModes);
454874
455675
  sendResult = await sendApprovalContinuationWithRetry(conversationId, nextInputWithSkillContent, {
454875
- ...buildSendOptions(),
455676
+ ...sendOptions,
455677
+ ...imageFailureModesByMessageOtid ? { imageFailureModesByMessageOtid } : {},
454876
455678
  ...continuationWasFullyAutoHandled ? { allowResponseStateReuse: true } : {}
454877
455679
  }, socket, runtime, turnLease, { providerFallback });
454878
455680
  } catch (error54) {
@@ -454940,8 +455742,10 @@ var init_turn_approval = __esm(async () => {
454940
455742
  init_transport();
454941
455743
  await __promiseAll([
454942
455744
  init_approval_execution(),
455745
+ init_message_image_normalization(),
454943
455746
  init_approval(),
454944
455747
  init_approval_suggestions(),
455748
+ init_continuation_input(),
454945
455749
  init_interrupts(),
454946
455750
  init_protocol_outbound(),
454947
455751
  init_queue(),
@@ -456110,14 +456914,7 @@ async function prepareListenerTurn(params) {
456110
456914
  if (connectionId) {
456111
456915
  onStatusChange?.("processing", connectionId);
456112
456916
  }
456113
- const { normalizeInboundMessages: normalizeInboundMessages2 } = await init_queue().then(() => exports_queue);
456114
- const normalizedMessages = await normalizeInboundMessages2(msg.messages, undefined, {
456115
- imageFailureMode: (msg.channelTurnSources?.length ?? 0) > 0 ? "drop" : "strict"
456116
- });
456117
- if (isInterrupted()) {
456118
- return { kind: "interrupted" };
456119
- }
456120
- trackListenerUserInput(normalizedMessages, "unknown");
456917
+ trackListenerUserInput(msg.messages, "unknown");
456121
456918
  const messagesToSend = [];
456122
456919
  let queuedInterruptedToolCallIds = [];
456123
456920
  const consumed = consumeInterruptQueue(runtime, agentId, conversationId);
@@ -456125,12 +456922,16 @@ async function prepareListenerTurn(params) {
456125
456922
  messagesToSend.push(consumed.approvalMessage);
456126
456923
  queuedInterruptedToolCallIds = consumed.interruptedToolCallIds;
456127
456924
  }
456128
- messagesToSend.push(...normalizedMessages.map((message) => ("content" in message) && !message.otid ? {
456925
+ messagesToSend.push(...msg.messages.map((message) => ("content" in message) && !message.otid ? {
456129
456926
  ...message,
456130
456927
  otid: "client_message_id" in message && typeof message.client_message_id === "string" ? message.client_message_id : crypto.randomUUID()
456131
456928
  } : message));
456929
+ const imageFailureModesByMessageOtid = getInboundImageFailureModes({
456930
+ channelTurnSources: msg.channelTurnSources,
456931
+ messages: messagesToSend
456932
+ });
456132
456933
  let inboundUserTranscriptLines = buildInboundUserTranscriptLines(messagesToSend);
456133
- const firstMessage = normalizedMessages[0];
456934
+ const firstMessage = msg.messages[0];
456134
456935
  const isApprovalMessage = firstMessage && "type" in firstMessage && firstMessage.type === "approval" && "approvals" in firstMessage;
456135
456936
  let cachedAgent = null;
456136
456937
  if (!isApprovalMessage) {
@@ -456242,6 +457043,7 @@ async function prepareListenerTurn(params) {
456242
457043
  kind: "ready",
456243
457044
  getCachedAgent: () => cachedAgent,
456244
457045
  currentInput,
457046
+ imageFailureModesByMessageOtid,
456245
457047
  inboundUserTranscriptLines,
456246
457048
  pendingNormalizationInterruptedToolCallIds: [
456247
457049
  ...queuedInterruptedToolCallIds
@@ -456260,6 +457062,7 @@ var init_turn_setup = __esm(async () => {
456260
457062
  init_warmup();
456261
457063
  await __promiseAll([
456262
457064
  init_toolset(),
457065
+ init_image_policy(),
456263
457066
  init_interrupts(),
456264
457067
  init_mod_adapter2(),
456265
457068
  init_turn_events()
@@ -456394,7 +457197,9 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
456394
457197
  workingDirectory: turnWorkingDirectory,
456395
457198
  permissionModeState: turnPermissionModeState,
456396
457199
  preparedToolContext: preparedToolContext.preparedToolContext,
456397
- skipImageNormalization: true,
457200
+ ...setup.imageFailureModesByMessageOtid ? {
457201
+ imageFailureModesByMessageOtid: setup.imageFailureModesByMessageOtid
457202
+ } : {},
456398
457203
  ...providerFallback.overrideModel ? { overrideModel: providerFallback.overrideModel } : {},
456399
457204
  ...msg.actingUserId ? { actingUserId: msg.actingUserId } : {},
456400
457205
  ...pendingNormalizationInterruptedToolCallIds.length > 0 ? {
@@ -456706,7 +457511,7 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
456706
457511
  });
456707
457512
  break;
456708
457513
  }
456709
- const errorMessage = errorDetail || `Unexpected stop reason: ${stopReason}`;
457514
+ const errorMessage2 = errorDetail || `Unexpected stop reason: ${stopReason}`;
456710
457515
  const terminalRunId = runId || runtime.activeRunId || runErrorInfo2?.run_id;
456711
457516
  const transition = finishTurn({
456712
457517
  stopReason: effectiveStopReason,
@@ -456717,7 +457522,7 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
456717
457522
  break;
456718
457523
  }
456719
457524
  const formattedError = emitLoopErrorNotice(socket, runtime, {
456720
- message: errorMessage,
457525
+ message: errorMessage2,
456721
457526
  stopReason: effectiveStopReason,
456722
457527
  isTerminal: true,
456723
457528
  runId: terminalRunId,
@@ -456727,7 +457532,7 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
456727
457532
  cancelRequested: turnAbortSignal.aborted,
456728
457533
  abortSignal: turnAbortSignal
456729
457534
  });
456730
- runtime.lastTerminalLoopErrorMessage = formattedError ?? errorMessage;
457535
+ runtime.lastTerminalLoopErrorMessage = formattedError ?? errorMessage2;
456731
457536
  runtime.lastTerminalLoopErrorRunId = terminalRunId ?? null;
456732
457537
  break;
456733
457538
  }
@@ -456849,7 +457654,7 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
456849
457654
  });
456850
457655
  return;
456851
457656
  }
456852
- const errorMessage = error54 instanceof Error ? error54.message : String(error54);
457657
+ const errorMessage2 = error54 instanceof Error ? error54.message : String(error54);
456853
457658
  const terminalRunId = runtime.activeRunId;
456854
457659
  const transition = finishTurn({
456855
457660
  stopReason: "error",
@@ -456860,7 +457665,7 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
456860
457665
  return;
456861
457666
  }
456862
457667
  const formattedError = emitLoopErrorNotice(socket, runtime, {
456863
- message: errorMessage,
457668
+ message: errorMessage2,
456864
457669
  stopReason: "error",
456865
457670
  isTerminal: true,
456866
457671
  runId: terminalRunId,
@@ -456870,7 +457675,7 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
456870
457675
  cancelRequested: turnAbortSignal.aborted,
456871
457676
  abortSignal: turnAbortSignal
456872
457677
  });
456873
- runtime.lastTerminalLoopErrorMessage = formattedError ?? errorMessage;
457678
+ runtime.lastTerminalLoopErrorMessage = formattedError ?? errorMessage2;
456874
457679
  runtime.lastTerminalLoopErrorRunId = terminalRunId ?? null;
456875
457680
  if (isDebugEnabled()) {
456876
457681
  console.error("[Listen] Error handling message:", error54);
@@ -458343,11 +459148,11 @@ async function handleExecuteCommand(command, socket, conversationRuntime, opts)
458343
459148
  error: error54,
458344
459149
  context: "listener_command_execution"
458345
459150
  });
458346
- const errorMessage = error54 instanceof Error ? error54.message : String(error54);
459151
+ const errorMessage2 = error54 instanceof Error ? error54.message : String(error54);
458347
459152
  emitSlashCommandEnd(socket, conversationRuntime, scope, {
458348
459153
  command_id: command.command_id,
458349
459154
  input,
458350
- output: `Failed: ${errorMessage}`,
459155
+ output: `Failed: ${errorMessage2}`,
458351
459156
  success: false
458352
459157
  });
458353
459158
  }
@@ -460748,7 +461553,7 @@ var init_grep_in_files = __esm(() => {
460748
461553
  });
460749
461554
 
460750
461555
  // src/websocket/listener/file-commands.ts
460751
- import { readdir as readdir11, readFile as readFile18 } from "node:fs/promises";
461556
+ import { readdir as readdir11, readFile as readFile17 } from "node:fs/promises";
460752
461557
  import { homedir as homedir30 } from "node:os";
460753
461558
  import path35 from "node:path";
460754
461559
  function trackListenerError2(errorType, error54, context4) {
@@ -460803,7 +461608,7 @@ async function getIgnoreConfig(root2) {
460803
461608
  return cached3;
460804
461609
  let patterns2 = [];
460805
461610
  try {
460806
- const content = await readFile18(path35.join(absRoot, ".letta", ".lettaignore"), "utf-8");
461611
+ const content = await readFile17(path35.join(absRoot, ".letta", ".lettaignore"), "utf-8");
460807
461612
  patterns2 = parseLettaIgnore(content);
460808
461613
  } catch {
460809
461614
  patterns2 = [];
@@ -463546,7 +464351,7 @@ function handleMemoryProtocolCommand(parsed, context4) {
463546
464351
  ensureLocalMemfsCheckout: ensureLocalMemfsCheckout2,
463547
464352
  isMemfsEnabledOnServer: isMemfsEnabledOnServer2
463548
464353
  } = await Promise.resolve().then(() => (init_memory_filesystem2(), exports_memory_filesystem));
463549
- const { readFile: readFile19 } = await import("node:fs/promises");
464354
+ const { readFile: readFile18 } = await import("node:fs/promises");
463550
464355
  const { existsSync: existsSync46 } = await import("node:fs");
463551
464356
  const { isAbsolute: isAbsolute24, join: join52, normalize: normalize5, relative: relative13, sep: sep6 } = await import("node:path");
463552
464357
  if (isAbsolute24(parsed.path) || parsed.path.length === 0) {
@@ -463572,7 +464377,7 @@ function handleMemoryProtocolCommand(parsed, context4) {
463572
464377
  return;
463573
464378
  }
463574
464379
  }
463575
- const buffer = await readFile19(absolutePath);
464380
+ const buffer = await readFile18(absolutePath);
463576
464381
  const content = encoding === "base64" ? buffer.toString("base64") : buffer.toString("utf-8");
463577
464382
  const pathspec = rel.split(sep6).join("/");
463578
464383
  safeSocketSend(socket, {
@@ -463612,7 +464417,7 @@ function handleMemoryProtocolCommand(parsed, context4) {
463612
464417
  isMemfsEnabledOnServer: isMemfsEnabledOnServer2
463613
464418
  } = await Promise.resolve().then(() => (init_memory_filesystem2(), exports_memory_filesystem));
463614
464419
  const { commitMemoryWrite: commitMemoryWrite2 } = await Promise.resolve().then(() => (init_memory_git(), exports_memory_git));
463615
- const { writeFile: writeFile15, mkdir: mkdir13 } = await import("node:fs/promises");
464420
+ const { writeFile: writeFile14, mkdir: mkdir13 } = await import("node:fs/promises");
463616
464421
  const { existsSync: existsSync46 } = await import("node:fs");
463617
464422
  const { dirname: dirname27, isAbsolute: isAbsolute24, join: join52, normalize: normalize5, relative: relative13, sep: sep6 } = await import("node:path");
463618
464423
  if (isAbsolute24(parsed.path) || parsed.path.length === 0) {
@@ -463640,7 +464445,7 @@ function handleMemoryProtocolCommand(parsed, context4) {
463640
464445
  }
463641
464446
  const buffer = encoding === "base64" ? Buffer.from(parsed.content, "base64") : Buffer.from(parsed.content, "utf-8");
463642
464447
  await mkdir13(dirname27(absolutePath), { recursive: true });
463643
- await writeFile15(absolutePath, buffer);
464448
+ await writeFile14(absolutePath, buffer);
463644
464449
  const { getBackend: getBackend2 } = await Promise.resolve().then(() => (init_backend2(), exports_backend));
463645
464450
  const backend4 = getBackend2();
463646
464451
  const memorySyncMode = backend4.capabilities.localMemfs && !backend4.capabilities.remoteMemfs ? "local" : undefined;
@@ -464280,7 +465085,8 @@ async function handleCreateAgentCommand(parsed, socket, safeSocketSend) {
464280
465085
  const { createAgentForPersonality: createAgentForPersonality2 } = await Promise.resolve().then(() => (init_personality(), exports_personality));
464281
465086
  const result = await createAgentForPersonality2({
464282
465087
  personalityId: parsed.personality,
464283
- model: parsed.model
465088
+ model: parsed.model,
465089
+ ...parsed.tags && { tags: parsed.tags }
464284
465090
  });
464285
465091
  if (parsed.pin_global !== false) {
464286
465092
  settingsManager.pinAgent(result.agent.id);
@@ -465803,11 +466609,13 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
465803
466609
  if (attempt === 0) {
465804
466610
  await loadTools();
465805
466611
  }
465806
- const settings3 = await settingsManager.getSettingsWithSecureTokens();
465807
- const apiKey = process.env.LETTA_API_KEY || settings3.env?.LETTA_API_KEY;
465808
- if (!apiKey) {
465809
- throw new Error("Missing LETTA_API_KEY");
466612
+ const auth = await resolveListenerReconnectAuth(opts);
466613
+ if (auth.kind === "retry")
466614
+ return connectWithRetry(runtime, opts, attempt + 1, startTime);
466615
+ if (runtime !== getActiveRuntime() || runtime.intentionallyClosed) {
466616
+ return;
465810
466617
  }
466618
+ const apiKey = auth.apiKey;
465811
466619
  const url2 = new URL(opts.wsUrl);
465812
466620
  url2.searchParams.set("deviceId", opts.deviceId);
465813
466621
  url2.searchParams.set("connectionName", opts.connectionName);
@@ -465994,6 +466802,7 @@ var init_lifecycle = __esm(async () => {
465994
466802
  init_debug();
465995
466803
  init_message_queue_bridge();
465996
466804
  init_terminal_handler();
466805
+ init_auth();
465997
466806
  init_channel_turn_session();
465998
466807
  init_constants3();
465999
466808
  init_cwd();
@@ -466766,7 +467575,7 @@ var execFile15, __dirname2, localXdgOpenPath, platform8, arch3, pTryEach = async
466766
467575
  }
466767
467576
  subprocess.unref();
466768
467577
  return subprocess;
466769
- }, open3 = (target2, options3) => {
467578
+ }, open4 = (target2, options3) => {
466770
467579
  if (typeof target2 !== "string") {
466771
467580
  throw new TypeError("Expected a `target`");
466772
467581
  }
@@ -466835,14 +467644,14 @@ var init_open = __esm(() => {
466835
467644
  }));
466836
467645
  defineLazyProperty(apps, "browser", () => "browser");
466837
467646
  defineLazyProperty(apps, "browserPrivate", () => "browserPrivate");
466838
- open_default = open3;
467647
+ open_default = open4;
466839
467648
  });
466840
467649
 
466841
467650
  // src/cli/commands/connect-oauth-core.ts
466842
467651
  async function openOAuthBrowser(authorizationUrl) {
466843
467652
  try {
466844
- const { default: open4 } = await Promise.resolve().then(() => (init_open(), exports_open));
466845
- const subprocess = await open4(authorizationUrl, { wait: false });
467653
+ const { default: open5 } = await Promise.resolve().then(() => (init_open(), exports_open));
467654
+ const subprocess = await open5(authorizationUrl, { wait: false });
466846
467655
  subprocess.on("error", () => {});
466847
467656
  } catch {}
466848
467657
  }
@@ -468857,141 +469666,6 @@ var init_startup = __esm(() => {
468857
469666
  init_mode();
468858
469667
  });
468859
469668
 
468860
- // src/websocket/listen-register.ts
468861
- import { createHash as createHash7 } from "node:crypto";
468862
- function deriveListenerInstanceId(surface, connectionName) {
468863
- const nameHash = createHash7("sha256").update(connectionName).digest("hex").slice(0, 16);
468864
- return `${surface}-${nameHash}`;
468865
- }
468866
- function isTransientRegistrationError(error54) {
468867
- if (error54 instanceof RegistrationError) {
468868
- return error54.statusCode === 0 || error54.statusCode === 429 || error54.statusCode >= 500;
468869
- }
468870
- return true;
468871
- }
468872
- function parseRetryAfterMs(value) {
468873
- if (!value) {
468874
- return;
468875
- }
468876
- const seconds = Number(value);
468877
- if (Number.isFinite(seconds) && seconds >= 0) {
468878
- return seconds * 1000;
468879
- }
468880
- const dateMs = Date.parse(value);
468881
- if (Number.isFinite(dateMs)) {
468882
- return Math.max(0, dateMs - Date.now());
468883
- }
468884
- return;
468885
- }
468886
- async function registerWithCloud(opts, fetchImpl = fetch) {
468887
- const registerUrl = `${opts.serverUrl}/v1/environments/register`;
468888
- const response = await fetchImpl(registerUrl, {
468889
- method: "POST",
468890
- headers: {
468891
- "Content-Type": "application/json",
468892
- Authorization: `Bearer ${opts.apiKey}`,
468893
- "X-Letta-Source": "letta-code"
468894
- },
468895
- body: JSON.stringify({
468896
- deviceId: opts.deviceId,
468897
- ...opts.listenerInstanceId ? { listenerInstanceId: opts.listenerInstanceId } : {},
468898
- connectionName: opts.connectionName,
468899
- metadata: {
468900
- lettaCodeVersion: getVersion(),
468901
- os: process.platform,
468902
- nodeVersion: process.version,
468903
- environmentMessageProtocol: "v2-input",
468904
- supported_commands: SUPPORTED_REMOTE_COMMANDS,
468905
- self_update: getSelfUpdateStatus()
468906
- }
468907
- })
468908
- }).catch((fetchError) => {
468909
- const msg = fetchError instanceof Error ? fetchError.message : String(fetchError);
468910
- throw new RegistrationError(`Network error: ${msg}`, 0);
468911
- });
468912
- if (!response.ok) {
468913
- let detail = `HTTP ${response.status}`;
468914
- const retryAfterMs2 = parseRetryAfterMs(response.headers.get("Retry-After"));
468915
- const text2 = await response.text().catch(() => "");
468916
- if (text2) {
468917
- try {
468918
- const parsed = JSON.parse(text2);
468919
- if (parsed.message) {
468920
- detail = parsed.message;
468921
- } else if (parsed.error) {
468922
- detail = `HTTP ${response.status}: ${parsed.error}`;
468923
- if (parsed.errorCode) {
468924
- detail += ` (${parsed.errorCode})`;
468925
- }
468926
- } else {
468927
- detail += `: ${text2.slice(0, 200)}`;
468928
- }
468929
- } catch {
468930
- detail += `: ${text2.slice(0, 200)}`;
468931
- }
468932
- }
468933
- throw new RegistrationError(detail, response.status, retryAfterMs2);
468934
- }
468935
- let body3;
468936
- try {
468937
- body3 = await response.json();
468938
- } catch {
468939
- throw new RegistrationError("Server returned non-JSON response — is the server running?", response.status);
468940
- }
468941
- const result = body3;
468942
- if (typeof result.connectionId !== "string" || typeof result.wsUrl !== "string") {
468943
- throw new RegistrationError("Server returned unexpected response shape (missing connectionId or wsUrl)", response.status);
468944
- }
468945
- return {
468946
- connectionId: result.connectionId,
468947
- wsUrl: result.wsUrl,
468948
- supportsSplitStatusChannels: result.supportsSplitStatusChannels === true
468949
- };
468950
- }
468951
- async function registerWithCloudRetry(opts, callbacks) {
468952
- const startTime = Date.now();
468953
- const maxDurationMs = callbacks?.maxDurationMs ?? REGISTER_MAX_DURATION_MS;
468954
- const sleep9 = callbacks?.sleep ?? ((delayMs) => new Promise((resolve31) => setTimeout(resolve31, delayMs)));
468955
- let attempt = 0;
468956
- for (;; ) {
468957
- try {
468958
- return await registerWithCloud(opts, callbacks?.fetchImpl);
468959
- } catch (error54) {
468960
- const elapsed = Date.now() - startTime;
468961
- if (!isTransientRegistrationError(error54) || elapsed >= maxDurationMs) {
468962
- throw error54;
468963
- }
468964
- attempt++;
468965
- const backoffDelay = Math.min(REGISTER_INITIAL_DELAY_MS * 2 ** (attempt - 1), REGISTER_MAX_DELAY_MS);
468966
- const delay2 = error54 instanceof RegistrationError && error54.retryAfterMs !== undefined ? Math.max(error54.retryAfterMs, backoffDelay) : backoffDelay;
468967
- const jitterWindow = Math.min(REGISTER_MAX_JITTER_MS, Math.floor(delay2 * REGISTER_JITTER_RATIO));
468968
- const jitter = jitterWindow > 0 ? Math.floor((callbacks?.random ?? Math.random)() * jitterWindow) : 0;
468969
- const retryDelay = delay2 + jitter;
468970
- if (error54 instanceof Error) {
468971
- callbacks?.onRetry?.(attempt, retryDelay, error54);
468972
- }
468973
- await sleep9(retryDelay);
468974
- }
468975
- }
468976
- }
468977
- var RegistrationError, REGISTER_INITIAL_DELAY_MS = 1000, REGISTER_MAX_DELAY_MS = 30000, REGISTER_MAX_DURATION_MS, REGISTER_MAX_JITTER_MS = 1000, REGISTER_JITTER_RATIO = 0.25;
468978
- var init_listen_register = __esm(() => {
468979
- init_auto_update();
468980
- init_version();
468981
- init_listener_constants();
468982
- RegistrationError = class RegistrationError extends Error {
468983
- statusCode;
468984
- retryAfterMs;
468985
- constructor(message, statusCode2, retryAfterMs2) {
468986
- super(message);
468987
- this.name = "RegistrationError";
468988
- this.statusCode = statusCode2;
468989
- this.retryAfterMs = retryAfterMs2;
468990
- }
468991
- };
468992
- REGISTER_MAX_DURATION_MS = 2 * 60 * 1000;
468993
- });
468994
-
468995
469669
  // src/websocket/listener/client.ts
468996
469670
  function asListenerRuntimeForTests(runtime) {
468997
469671
  return "listener" in runtime ? runtime.listener : runtime;
@@ -469284,8 +469958,6 @@ var init_client7 = __esm(async () => {
469284
469958
  shouldAttemptPostStopApprovalRecovery,
469285
469959
  markAwaitingAcceptedApprovalContinuationRunId,
469286
469960
  resolveStaleApprovals,
469287
- normalizeMessageContentImages: normalizeMessageContentImages2,
469288
- normalizeInboundMessages,
469289
469961
  consumeQueuedTurn,
469290
469962
  handleIncomingMessage,
469291
469963
  handleApprovalResponseInput,
@@ -469729,7 +470401,7 @@ function ConstellationLoginView({
469729
470401
  const deviceData = await requestDeviceCode();
469730
470402
  setUserCode(deviceData.user_code);
469731
470403
  setVerificationUri(deviceData.verification_uri_complete);
469732
- Promise.resolve().then(() => (init_open(), exports_open)).then(({ default: open4 }) => open4(deviceData.verification_uri_complete, { wait: false })).then((subprocess) => {
470404
+ Promise.resolve().then(() => (init_open(), exports_open)).then(({ default: open5 }) => open5(deviceData.verification_uri_complete, { wait: false })).then((subprocess) => {
469733
470405
  subprocess.on("error", () => {});
469734
470406
  }).catch(() => {});
469735
470407
  const deviceId = settingsManager.getOrCreateDeviceId();
@@ -471654,7 +472326,7 @@ __export(exports_skills3, {
471654
472326
  GLOBAL_SKILLS_DIR: () => GLOBAL_SKILLS_DIR2
471655
472327
  });
471656
472328
  import { existsSync as existsSync54 } from "node:fs";
471657
- import { readdir as readdir15, readFile as readFile22, realpath as realpath6, stat as stat15 } from "node:fs/promises";
472329
+ import { readdir as readdir15, readFile as readFile21, realpath as realpath6, stat as stat15 } from "node:fs/promises";
471658
472330
  import { dirname as dirname31, join as join62 } from "node:path";
471659
472331
  import { fileURLToPath as fileURLToPath8 } from "node:url";
471660
472332
  function getBundledSkillsPath2() {
@@ -471826,7 +472498,7 @@ async function findSkillFiles2(currentPath, rootPath, skills, errors7, source2,
471826
472498
  }
471827
472499
  }
471828
472500
  async function parseSkillFile2(filePath, rootPath, source2) {
471829
- const content = await readFile22(filePath, "utf-8");
472501
+ const content = await readFile21(filePath, "utf-8");
471830
472502
  const { frontmatter, body: body3 } = parseFrontmatter(content);
471831
472503
  const normalizedRoot = rootPath.endsWith("/") ? rootPath.slice(0, -1) : rootPath;
471832
472504
  const relativePath = filePath.slice(normalizedRoot.length + 1);
@@ -471886,9 +472558,9 @@ var init_skills4 = __esm(() => {
471886
472558
  var exports_fs = {};
471887
472559
  __export(exports_fs, {
471888
472560
  writeJsonFile: () => writeJsonFile2,
471889
- writeFile: () => writeFile17,
472561
+ writeFile: () => writeFile16,
471890
472562
  readJsonFile: () => readJsonFile4,
471891
- readFile: () => readFile23,
472563
+ readFile: () => readFile22,
471892
472564
  mkdir: () => mkdir15,
471893
472565
  exists: () => exists2
471894
472566
  });
@@ -471899,10 +472571,10 @@ import {
471899
472571
  mkdirSync as mkdirSync38
471900
472572
  } from "node:fs";
471901
472573
  import { dirname as dirname32 } from "node:path";
471902
- async function readFile23(path39) {
472574
+ async function readFile22(path39) {
471903
472575
  return fsReadFileSync2(path39, { encoding: "utf-8" });
471904
472576
  }
471905
- async function writeFile17(path39, content) {
472577
+ async function writeFile16(path39, content) {
471906
472578
  const dir = dirname32(path39);
471907
472579
  if (!existsSync55(dir)) {
471908
472580
  mkdirSync38(dir, { recursive: true });
@@ -471916,14 +472588,14 @@ async function mkdir15(path39, options3) {
471916
472588
  mkdirSync38(path39, options3);
471917
472589
  }
471918
472590
  async function readJsonFile4(path39) {
471919
- const text2 = await readFile23(path39);
472591
+ const text2 = await readFile22(path39);
471920
472592
  return JSON.parse(text2);
471921
472593
  }
471922
472594
  async function writeJsonFile2(path39, data, options3) {
471923
472595
  const indent = options3?.indent ?? 2;
471924
472596
  const content = `${JSON.stringify(data, null, indent)}
471925
472597
  `;
471926
- await writeFile17(path39, content);
472598
+ await writeFile16(path39, content);
471927
472599
  }
471928
472600
  var init_fs2 = () => {};
471929
472601
 
@@ -472122,7 +472794,7 @@ import {
472122
472794
  execFile as execFile17
472123
472795
  } from "node:child_process";
472124
472796
  import { accessSync as accessSync2, constants as constants4, realpathSync as realpathSync6 } from "node:fs";
472125
- import { readdir as readdir16, rm as rm6 } from "node:fs/promises";
472797
+ import { readdir as readdir16, rm as rm7 } from "node:fs/promises";
472126
472798
  import { dirname as dirname33, join as join63 } from "node:path";
472127
472799
  import { promisify as promisify15 } from "node:util";
472128
472800
  function debugLog4(...args) {
@@ -472354,7 +473026,7 @@ async function cleanupOrphanedDirs2(globalPath) {
472354
473026
  if (entry.startsWith(".letta-code-")) {
472355
473027
  const orphanPath = join63(lettaAiDir, entry);
472356
473028
  debugLog4("Cleaning orphaned temp directory:", orphanPath);
472357
- await rm6(orphanPath, { recursive: true, force: true });
473029
+ await rm7(orphanPath, { recursive: true, force: true });
472358
473030
  }
472359
473031
  }
472360
473032
  } catch {}
@@ -473822,7 +474494,7 @@ __export(exports_import, {
473822
474494
  extractSkillsFromAf: () => extractSkillsFromAf
473823
474495
  });
473824
474496
  import { createReadStream as createReadStream2 } from "node:fs";
473825
- import { access as access2, chmod, mkdir as mkdir16, readFile as readFile24, writeFile as writeFile18 } from "node:fs/promises";
474497
+ import { access as access2, chmod, mkdir as mkdir16, readFile as readFile23, writeFile as writeFile17 } from "node:fs/promises";
473826
474498
  import { dirname as dirname34, isAbsolute as isAbsolute25, relative as relative13, resolve as resolve36, sep as sep7, win32 as win325 } from "node:path";
473827
474499
  function validateImportedSkillName(name) {
473828
474500
  const trimmedName = name.trim();
@@ -473923,7 +474595,7 @@ async function importAgentFromFile(options3) {
473923
474595
  }
473924
474596
  async function extractSkillsFromAf(afPath, destDir) {
473925
474597
  const extracted = [];
473926
- const content = await readFile24(afPath, "utf-8");
474598
+ const content = await readFile23(afPath, "utf-8");
473927
474599
  const afData = JSON.parse(content);
473928
474600
  if (!afData.skills || !Array.isArray(afData.skills)) {
473929
474601
  return [];
@@ -473952,7 +474624,7 @@ async function writeSkillFiles(skillDir, files) {
473952
474624
  async function writeSkillFile(skillDir, filePath, content) {
473953
474625
  const fullPath = resolveImportedSkillFilePath(skillDir, filePath);
473954
474626
  await mkdir16(dirname34(fullPath), { recursive: true });
473955
- await writeFile18(fullPath, content, "utf-8");
474627
+ await writeFile17(fullPath, content, "utf-8");
473956
474628
  const isScript = filePath.startsWith("scripts/") || content.trimStart().startsWith("#!");
473957
474629
  if (isScript) {
473958
474630
  try {
@@ -474005,7 +474677,7 @@ function parseRegistryHandle(handle2) {
474005
474677
  async function importAgentFromRegistry(options3) {
474006
474678
  const { tmpdir: tmpdir10 } = await import("node:os");
474007
474679
  const { join: join68 } = await import("node:path");
474008
- const { writeFile: writeFile19, unlink: unlink5 } = await import("node:fs/promises");
474680
+ const { writeFile: writeFile18, unlink: unlink5 } = await import("node:fs/promises");
474009
474681
  const { author, name } = parseRegistryHandle(options3.handle);
474010
474682
  const rawUrl = `https://raw.githubusercontent.com/${AGENT_REGISTRY_OWNER}/${AGENT_REGISTRY_REPO}/refs/heads/${AGENT_REGISTRY_BRANCH}/agents/@${author}/${name}/${name}.af`;
474011
474683
  const response = await fetch(rawUrl);
@@ -474017,7 +474689,7 @@ async function importAgentFromRegistry(options3) {
474017
474689
  }
474018
474690
  const afContent = await response.text();
474019
474691
  const tempPath = join68(tmpdir10(), `letta-import-${author}-${name}-${Date.now()}.af`);
474020
- await writeFile19(tempPath, afContent, "utf-8");
474692
+ await writeFile18(tempPath, afContent, "utf-8");
474021
474693
  try {
474022
474694
  const result = await importAgentFromFile({
474023
474695
  filePath: tempPath,
@@ -474125,7 +474797,8 @@ async function ensureDefaultAgents(backend4, options3) {
474125
474797
  const createOptions = personality === "tutorial" ? {
474126
474798
  ...await buildCreateAgentOptionsForPersonality2({
474127
474799
  personalityId: "tutorial",
474128
- model
474800
+ model,
474801
+ tags: [ONBOARDING_ORIGIN_TAG]
474129
474802
  }),
474130
474803
  memoryPromptMode
474131
474804
  } : {
@@ -476245,7 +476918,7 @@ ${loadedContents.join(`
476245
476918
  markIncompleteToolsAsCancelled(buffers, true, "stream_error");
476246
476919
  const errorLines = toLines(buffers).filter((line) => line.kind === "error");
476247
476920
  const errorMessages2 = errorLines.map((line) => ("text" in line) ? line.text : "").filter(Boolean);
476248
- let errorMessage = errorMessages2.length > 0 ? errorMessages2.join("; ") : `Unexpected stop reason: ${stopReason}`;
476921
+ let errorMessage2 = errorMessages2.length > 0 ? errorMessages2.join("; ") : `Unexpected stop reason: ${stopReason}`;
476249
476922
  if (lastRunId && errorMessages2.length === 0) {
476250
476923
  try {
476251
476924
  const run = await getBackend().retrieveRun(lastRunId);
@@ -476257,18 +476930,18 @@ ${loadedContents.join(`
476257
476930
  run_id: lastRunId
476258
476931
  }
476259
476932
  };
476260
- errorMessage = formatErrorDetails2(errorObject, agent2.id);
476933
+ errorMessage2 = formatErrorDetails2(errorObject, agent2.id);
476261
476934
  }
476262
476935
  } catch (_e) {
476263
- errorMessage = `${errorMessage}
476936
+ errorMessage2 = `${errorMessage2}
476264
476937
  (Unable to fetch additional error details from server)`;
476265
476938
  }
476266
476939
  }
476267
- trackHeadlessBoundaryError("headless_turn_failed", errorMessage, "headless_turn_execution");
476940
+ trackHeadlessBoundaryError("headless_turn_failed", errorMessage2, "headless_turn_execution");
476268
476941
  if (outputFormat === "stream-json") {
476269
476942
  const errorMsg = {
476270
476943
  type: "error",
476271
- message: errorMessage,
476944
+ message: errorMessage2,
476272
476945
  stop_reason: stopReason,
476273
476946
  run_id: lastRunId ?? undefined,
476274
476947
  session_id: sessionId,
@@ -476276,7 +476949,7 @@ ${loadedContents.join(`
476276
476949
  };
476277
476950
  await writeWireMessageAsync(errorMsg);
476278
476951
  } else {
476279
- console.error(`Error: ${errorMessage}`);
476952
+ console.error(`Error: ${errorMessage2}`);
476280
476953
  }
476281
476954
  await exitHeadless(1, "headless_stop_reason_error");
476282
476955
  }
@@ -478832,7 +479505,7 @@ var init_queued_message_parts = __esm(() => {
478832
479505
  // src/cli/helpers/reflection-arena-hf-upload.ts
478833
479506
  import { execFile as execFileCb5 } from "node:child_process";
478834
479507
  import { existsSync as existsSync60 } from "node:fs";
478835
- import { appendFile as appendFile2, chmod as chmod2, mkdir as mkdir17, writeFile as writeFile19 } from "node:fs/promises";
479508
+ import { appendFile as appendFile2, chmod as chmod2, mkdir as mkdir17, writeFile as writeFile18 } from "node:fs/promises";
478836
479509
  import { homedir as homedir38 } from "node:os";
478837
479510
  import { join as join69 } from "node:path";
478838
479511
  import { promisify as promisify16 } from "node:util";
@@ -478871,7 +479544,7 @@ async function runGit6(cwd2, args, env5) {
478871
479544
  }
478872
479545
  async function writeGitAskpass(repoRoot) {
478873
479546
  const askpassPath = join69(repoRoot, "hf-askpass.sh");
478874
- await writeFile19(askpassPath, [
479547
+ await writeFile18(askpassPath, [
478875
479548
  "#!/bin/sh",
478876
479549
  'case "$1" in',
478877
479550
  " *Username*) printf '%s\\n' 'hf_user' ;;",
@@ -478956,7 +479629,7 @@ var init_reflection_arena_hf_upload = __esm(() => {
478956
479629
  // src/cli/helpers/reflection-arena.ts
478957
479630
  import { execFile as execFileCb6 } from "node:child_process";
478958
479631
  import { randomInt, randomUUID as randomUUID30 } from "node:crypto";
478959
- import { appendFile as appendFile3, mkdir as mkdir18, readFile as readFile25, writeFile as writeFile20 } from "node:fs/promises";
479632
+ import { appendFile as appendFile3, mkdir as mkdir18, readFile as readFile24, writeFile as writeFile19 } from "node:fs/promises";
478960
479633
  import { homedir as homedir39 } from "node:os";
478961
479634
  import { join as join70 } from "node:path";
478962
479635
  import { promisify as promisify17 } from "node:util";
@@ -479001,11 +479674,11 @@ function getReflectionArenaRunPath(runId) {
479001
479674
  }
479002
479675
  async function saveReflectionArenaRun(run) {
479003
479676
  await mkdir18(getReflectionArenaRunsDir(), { recursive: true });
479004
- await writeFile20(getReflectionArenaRunPath(run.runId), `${JSON.stringify(run, null, 2)}
479677
+ await writeFile19(getReflectionArenaRunPath(run.runId), `${JSON.stringify(run, null, 2)}
479005
479678
  `, "utf-8");
479006
479679
  }
479007
479680
  async function loadReflectionArenaRun(runId) {
479008
- const raw2 = await readFile25(getReflectionArenaRunPath(runId), "utf-8");
479681
+ const raw2 = await readFile24(getReflectionArenaRunPath(runId), "utf-8");
479009
479682
  return JSON.parse(raw2);
479010
479683
  }
479011
479684
  async function updateReflectionArenaRun(runId, update2) {
@@ -479197,7 +479870,7 @@ async function appendChoiceRecord(run) {
479197
479870
  }
479198
479871
  async function readTranscriptPayloadForTelemetry(payloadPath) {
479199
479872
  try {
479200
- const transcript = await readFile25(payloadPath, "utf-8");
479873
+ const transcript = await readFile24(payloadPath, "utf-8");
479201
479874
  return {
479202
479875
  transcriptPayload: transcript.slice(0, REFLECTION_ARENA_TELEMETRY_TRANSCRIPT_MAX_CHARS),
479203
479876
  transcriptPayloadChars: transcript.length,
@@ -489773,7 +490446,7 @@ var init_AgentInfoBar = __esm(async () => {
489773
490446
  const isLocalAgent = agentId ? isLocalAgentId2(agentId) : false;
489774
490447
  const showCloudLinks = Boolean(isCloudUser && agentId && !isLocalAgent);
489775
490448
  const adeConversationUrl = showCloudLinks && agentId && agentId !== "loading" ? buildChatUrl(agentId, { conversationId }) : "";
489776
- const usageUrl = buildAppUrl("/settings/organization/usage");
490449
+ const usageUrl = buildChatWebUrl("/preferences/usage");
489777
490450
  const showBottomBar = agentId && agentId !== "loading";
489778
490451
  const reasoningLabel = shouldHideReasoningForModelDisplay(currentModel) ? null : formatReasoningLabel(currentReasoningEffort);
489779
490452
  const modelLine = currentModel ? `${currentModel}${reasoningLabel ? ` (${reasoningLabel})` : ""}` : null;
@@ -493294,7 +493967,7 @@ function buildInstallPrBody(workflowPath) {
493294
493967
  "",
493295
493968
  "You can also specify a particular agent: `@letta-code [--agent agent-xxx]`",
493296
493969
  "",
493297
- "View agent runs and conversations at [app.letta.com](https://app.letta.com).",
493970
+ "Chat with your agent at [chat.letta.com](https://chat.letta.com).",
493298
493971
  "",
493299
493972
  "### Important Notes",
493300
493973
  "",
@@ -493615,7 +494288,7 @@ var init_InstallGithubAppFlow = __esm(async () => {
493615
494288
  const solidLine = SOLID_LINE12.repeat(Math.max(terminalWidth, 10));
493616
494289
  const [step, setStep] = import_react87.useState("checking");
493617
494290
  const [status, setStatus] = import_react87.useState("Checking GitHub CLI prerequisites...");
493618
- const [errorMessage, setErrorMessage] = import_react87.useState("");
494291
+ const [errorMessage2, setErrorMessage] = import_react87.useState("");
493619
494292
  const [currentRepo, setCurrentRepo] = import_react87.useState(null);
493620
494293
  const [repoChoiceIndex, setRepoChoiceIndex] = import_react87.useState(0);
493621
494294
  const [repoInput, setRepoInput] = import_react87.useState("");
@@ -494183,14 +494856,14 @@ var init_InstallGithubAppFlow = __esm(async () => {
494183
494856
  color: "red",
494184
494857
  children: [
494185
494858
  "Error: ",
494186
- errorMessage.split(`
494859
+ errorMessage2.split(`
494187
494860
  `)[0] || "Unknown error"
494188
494861
  ]
494189
494862
  }, undefined, true, undefined, this),
494190
494863
  /* @__PURE__ */ jsx_dev_runtime61.jsxDEV(Box_default, {
494191
494864
  height: 1
494192
494865
  }, undefined, false, undefined, this),
494193
- errorMessage.split(`
494866
+ errorMessage2.split(`
494194
494867
  `).slice(1).filter((line) => line.trim().length > 0).map((line, idx) => /* @__PURE__ */ jsx_dev_runtime61.jsxDEV(Text2, {
494195
494868
  dimColor: true,
494196
494869
  children: line
@@ -494435,7 +495108,7 @@ var init_McpConnectFlow = __esm(async () => {
494435
495108
  const authorizationUrl = event2.url;
494436
495109
  setAuthUrl(authorizationUrl);
494437
495110
  setConnectionStatus("Opening browser for authorization...");
494438
- Promise.resolve().then(() => (init_open(), exports_open)).then(({ default: open4 }) => open4(authorizationUrl)).catch(() => {});
495111
+ Promise.resolve().then(() => (init_open(), exports_open)).then(({ default: open5 }) => open5(authorizationUrl)).catch(() => {});
494439
495112
  }
494440
495113
  break;
494441
495114
  case "waiting_for_auth" /* WAITING_FOR_AUTH */:
@@ -496859,13 +497532,16 @@ html.dark .warning-badge { background: hsl(42, 30%, 18%); color: hsl(42, 80%, 70
496859
497532
  var agentIdEl = document.getElementById('agent-id');
496860
497533
  agentIdEl.textContent = agentId;
496861
497534
  if (agentId) {
496862
- var adeBase;
496863
- if (serverUrl) {
496864
- adeBase = serverUrl.replace(/\\/v1\\/?$/, '').replace('api.letta.com', 'app.letta.com');
497535
+ var chatBase;
497536
+ var normalizedServerUrl = serverUrl.replace(/\\/+$/, '');
497537
+ if (normalizedServerUrl === 'https://api.letta.com' || normalizedServerUrl === 'https://api.letta.com/v1') {
497538
+ chatBase = 'https://chat.letta.com';
497539
+ } else if (normalizedServerUrl) {
497540
+ chatBase = normalizedServerUrl.replace(/\\/v1$/, '');
496865
497541
  } else {
496866
- adeBase = 'https://app.letta.com';
497542
+ chatBase = 'https://chat.letta.com';
496867
497543
  }
496868
- agentIdEl.href = adeBase + '/chat/' + encodeURIComponent(agentId);
497544
+ agentIdEl.href = chatBase + '/chat/' + encodeURIComponent(agentId);
496869
497545
  agentIdEl.target = '_blank';
496870
497546
  }
496871
497547
  document.getElementById('generated-at').textContent = 'Generated ' + new Date(DATA.generatedAt).toLocaleString();
@@ -500626,7 +501302,7 @@ var init_PersonalitySelector = __esm(async () => {
500626
501302
  });
500627
501303
 
500628
501304
  // src/utils/aws-credentials.ts
500629
- import { readFile as readFile26 } from "node:fs/promises";
501305
+ import { readFile as readFile25 } from "node:fs/promises";
500630
501306
  import { homedir as homedir45 } from "node:os";
500631
501307
  import { join as join77 } from "node:path";
500632
501308
  async function parseAwsCredentials() {
@@ -500634,11 +501310,11 @@ async function parseAwsCredentials() {
500634
501310
  const configPath = join77(homedir45(), ".aws", "config");
500635
501311
  const profiles = new Map;
500636
501312
  try {
500637
- const content = await readFile26(credentialsPath, "utf-8");
501313
+ const content = await readFile25(credentialsPath, "utf-8");
500638
501314
  parseIniFile(content, profiles, false);
500639
501315
  } catch {}
500640
501316
  try {
500641
- const content = await readFile26(configPath, "utf-8");
501317
+ const content = await readFile25(configPath, "utf-8");
500642
501318
  parseIniFile(content, profiles, true);
500643
501319
  } catch {}
500644
501320
  return Array.from(profiles.values());
@@ -505910,7 +506586,7 @@ function formatUsageStats({
505910
506586
  const monthlyCredits = Math.round(balance.monthly_credit_balance);
505911
506587
  const purchasedCredits = Math.round(balance.purchased_credit_balance);
505912
506588
  const toDollars = (credits) => (credits / 1000).toFixed(2);
505913
- outputLines.push(`Plan: [${balance.billing_tier}]`, buildAppUrl("/settings/organization/usage"), "", `Available credits: ◎${formatNumber2(totalCredits)} ($${toDollars(totalCredits)})`, `Monthly credits: ◎${formatNumber2(monthlyCredits)} ($${toDollars(monthlyCredits)})`, `Purchased credits: ◎${formatNumber2(purchasedCredits)} ($${toDollars(purchasedCredits)})`);
506589
+ outputLines.push(`Plan: [${balance.billing_tier}]`, buildChatWebUrl("/preferences/usage"), "", `Available credits: ◎${formatNumber2(totalCredits)} ($${toDollars(totalCredits)})`, `Monthly credits: ◎${formatNumber2(monthlyCredits)} ($${toDollars(monthlyCredits)})`, `Purchased credits: ◎${formatNumber2(purchasedCredits)} ($${toDollars(purchasedCredits)})`);
505914
506590
  }
505915
506591
  return outputLines.join(`
505916
506592
  `);
@@ -516961,13 +517637,13 @@ var init_cleanLastNewline = () => {};
516961
517637
  function processLine(node, line, state) {
516962
517638
  const lineInfo = typeof state.lineInfo === "function" ? state.lineInfo(line) : state.lineInfo[line - 1];
516963
517639
  if (lineInfo == null) {
516964
- const errorMessage = `processLine: line ${line}, contains no state.lineInfo`;
516965
- console.error(errorMessage, {
517640
+ const errorMessage2 = `processLine: line ${line}, contains no state.lineInfo`;
517641
+ console.error(errorMessage2, {
516966
517642
  node,
516967
517643
  line,
516968
517644
  state
516969
517645
  });
516970
- throw new Error(errorMessage);
517646
+ throw new Error(errorMessage2);
516971
517647
  }
516972
517648
  node.tagName = "div";
516973
517649
  node.properties["data-line"] = lineInfo.lineNumber;
@@ -520439,9 +521115,9 @@ var instanceId = -1, DiffHunksRenderer = class {
520439
521115
  let deletionLineContent = deletionLine != null ? deletionLines[deletionLine.lineIndex] : undefined;
520440
521116
  let additionLineContent = additionLine != null ? additionLines[additionLine.lineIndex] : undefined;
520441
521117
  if (deletionLineContent == null && additionLineContent == null) {
520442
- const errorMessage = "DiffHunksRenderer.processDiffResult: deletionLine and additionLine are null, something is wrong";
520443
- console.error(errorMessage, { file: fileDiff.name });
520444
- throw new Error(errorMessage);
521118
+ const errorMessage2 = "DiffHunksRenderer.processDiffResult: deletionLine and additionLine are null, something is wrong";
521119
+ console.error(errorMessage2, { file: fileDiff.name });
521120
+ throw new Error(errorMessage2);
520445
521121
  }
520446
521122
  const lineType = type3 === "change" ? additionLine != null ? "change-addition" : "change-deletion" : type3;
520447
521123
  const lineDecoration = this.getUnifiedLineDecoration({
@@ -520483,9 +521159,9 @@ var instanceId = -1, DiffHunksRenderer = class {
520483
521159
  lineIndex: additionLine?.lineIndex
520484
521160
  });
520485
521161
  if (deletionLineContent == null && additionLineContent == null) {
520486
- const errorMessage = "DiffHunksRenderer.processDiffResult: deletionLine and additionLine are null, something is wrong";
520487
- console.error(errorMessage, { file: fileDiff.name });
520488
- throw new Error(errorMessage);
521162
+ const errorMessage2 = "DiffHunksRenderer.processDiffResult: deletionLine and additionLine are null, something is wrong";
521163
+ console.error(errorMessage2, { file: fileDiff.name });
521164
+ throw new Error(errorMessage2);
520489
521165
  }
520490
521166
  const missingSide = (() => {
520491
521167
  if (type3 === "change") {
@@ -528012,7 +528688,7 @@ The deployment target for this project is CITATION-DOGFOOD-OK.
528012
528688
 
528013
528689
  // src/mods/learning-harness.ts
528014
528690
  import { spawn as spawn13 } from "node:child_process";
528015
- import { access as access3, copyFile as copyFile2, mkdir as mkdir19, readFile as readFile27, writeFile as writeFile21 } from "node:fs/promises";
528691
+ import { access as access3, copyFile as copyFile2, mkdir as mkdir19, readFile as readFile26, writeFile as writeFile20 } from "node:fs/promises";
528016
528692
  import path42 from "node:path";
528017
528693
  function slugify2(value) {
528018
528694
  const slug = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
@@ -528260,14 +528936,14 @@ async function existingPath(filePath) {
528260
528936
  return await fileExists(filePath) ? filePath : undefined;
528261
528937
  }
528262
528938
  async function writeJsonArtifact(filePath, value) {
528263
- await writeFile21(filePath, `${JSON.stringify(value, null, 2)}
528939
+ await writeFile20(filePath, `${JSON.stringify(value, null, 2)}
528264
528940
  `, "utf8");
528265
528941
  }
528266
528942
  async function writeCommandArtifacts(prefix, command, args, result) {
528267
- await writeFile21(`${prefix}.command.txt`, `${renderCommand(command, args)}
528943
+ await writeFile20(`${prefix}.command.txt`, `${renderCommand(command, args)}
528268
528944
  `, "utf8");
528269
- await writeFile21(`${prefix}.stdout`, result.stdout, "utf8");
528270
- await writeFile21(`${prefix}.stderr`, result.stderr, "utf8");
528945
+ await writeFile20(`${prefix}.stdout`, result.stdout, "utf8");
528946
+ await writeFile20(`${prefix}.stderr`, result.stderr, "utf8");
528271
528947
  await writeJsonArtifact(`${prefix}.result.json`, result);
528272
528948
  }
528273
528949
  async function prepareMemoryFiles(memoryDir, memoryFiles) {
@@ -528275,7 +528951,7 @@ async function prepareMemoryFiles(memoryDir, memoryFiles) {
528275
528951
  for (const [relativePath, content] of Object.entries(memoryFiles ?? {})) {
528276
528952
  const filePath = safeJoin(memoryDir, relativePath);
528277
528953
  await mkdir19(path42.dirname(filePath), { recursive: true });
528278
- await writeFile21(filePath, content, "utf8");
528954
+ await writeFile20(filePath, content, "utf8");
528279
528955
  }
528280
528956
  }
528281
528957
  function renderEvaluationPrompt(prompt, memoryDir) {
@@ -528938,7 +529614,7 @@ function renderProposerGuide(params) {
528938
529614
  `;
528939
529615
  }
528940
529616
  async function writeHistoryArtifacts(params) {
528941
- await writeFile21(params.historyPath, renderHistoryIndex({
529617
+ await writeFile20(params.historyPath, renderHistoryIndex({
528942
529618
  attempts: params.attempts,
528943
529619
  historyManifestPath: params.historyManifestPath,
528944
529620
  proposerGuidePath: params.proposerGuidePath,
@@ -528946,7 +529622,7 @@ async function writeHistoryArtifacts(params) {
528946
529622
  spec: params.spec
528947
529623
  }), "utf8");
528948
529624
  await writeJsonArtifact(params.historyManifestPath, buildHistoryManifest(params));
528949
- await writeFile21(params.proposerGuidePath, renderProposerGuide(params), "utf8");
529625
+ await writeFile20(params.proposerGuidePath, renderProposerGuide(params), "utf8");
528950
529626
  }
528951
529627
  async function defaultCommandRunner(command, args, options3) {
528952
529628
  const startedAt = Date.now();
@@ -529037,7 +529713,7 @@ function createScenarioSuiteEvaluator(params) {
529037
529713
  outputFormat
529038
529714
  })
529039
529715
  ];
529040
- await writeFile21(hasConfiguredScenarios ? path42.join(scenarioDir, "prompt.md") : path42.join(context4.runDir, "eval-prompt.md"), evalPrompt, "utf8");
529716
+ await writeFile20(hasConfiguredScenarios ? path42.join(scenarioDir, "prompt.md") : path42.join(context4.runDir, "eval-prompt.md"), evalPrompt, "utf8");
529041
529717
  const scenarioEvalResult = await context4.runner(context4.cliCommand, evalArgs, {
529042
529718
  cwd: context4.repoRoot,
529043
529719
  env: {
@@ -529229,7 +529905,7 @@ async function runModLearningCandidate(params) {
529229
529905
  outputFormat: "json"
529230
529906
  })
529231
529907
  ];
529232
- await writeFile21(path42.join(runDir, "generation-prompt.md"), generationPrompt, "utf8");
529908
+ await writeFile20(path42.join(runDir, "generation-prompt.md"), generationPrompt, "utf8");
529233
529909
  generationResult = await params.runner(params.cliCommand, generationArgs, {
529234
529910
  cwd: repoRoot,
529235
529911
  env: {
@@ -529308,7 +529984,7 @@ async function runModLearningCandidate(params) {
529308
529984
  spec: options3.spec
529309
529985
  };
529310
529986
  await writeJsonArtifact(path42.join(runDir, "report.json"), report);
529311
- await writeFile21(reportPath, renderMarkdownReport(report), "utf8");
529987
+ await writeFile20(reportPath, renderMarkdownReport(report), "utf8");
529312
529988
  await writeCandidateManifest(report);
529313
529989
  emitProgress("done", params.candidateCount > 1 ? `Optimization iteration ${params.candidateIndex}/${params.candidateCount} complete` : "mod optimization complete", {
529314
529990
  attempts: [...params.previousAttempts, summarizeAttempt(report)],
@@ -529486,7 +530162,7 @@ async function runModLearning(options3) {
529486
530162
  spec: normalizedOptions.spec
529487
530163
  });
529488
530164
  await writeJsonArtifact(path42.join(runDir, "report.json"), report);
529489
- await writeFile21(reportPath, renderMarkdownReport(report), "utf8");
530165
+ await writeFile20(reportPath, renderMarkdownReport(report), "utf8");
529490
530166
  normalizedOptions.onProgress?.({
529491
530167
  candidateCount,
529492
530168
  candidateIndex: selectedCandidateIndex,
@@ -529504,7 +530180,7 @@ async function runModLearning(options3) {
529504
530180
  return report;
529505
530181
  }
529506
530182
  async function readModLearningEnv(envPath) {
529507
- return JSON.parse(await readFile27(envPath, "utf8"));
530183
+ return JSON.parse(await readFile26(envPath, "utf8"));
529508
530184
  }
529509
530185
  var init_learning_harness = __esm(async () => {
529510
530186
  await init_mod_engine();
@@ -530589,19 +531265,12 @@ Messages will be executed locally using your letta-code environment.`, true);
530589
531265
  const deviceName = hostname8();
530590
531266
  updateCommandResult3(ctx.buffersRef, ctx.refreshDerived, cmdId, msg, `Registering listener "${connectionName}"...
530591
531267
  Device: ${deviceName} (${deviceId.slice(0, 8)}...)`, true, "running");
530592
- const serverUrl = getServerUrl();
530593
- const settings3 = await settingsManager.getSettingsWithSecureTokens();
530594
- const apiKey = process.env.LETTA_API_KEY || settings3.env?.LETTA_API_KEY;
530595
- if (!apiKey) {
530596
- throw new Error("Missing LETTA_API_KEY");
530597
- }
530598
- const { connectionId, wsUrl, supportsSplitStatusChannels } = await registerWithCloudRetry({
530599
- serverUrl,
530600
- apiKey,
530601
- deviceId,
530602
- connectionName,
530603
- listenerInstanceId: deriveListenerInstanceId("listen", connectionName)
530604
- }, {
531268
+ const resolveRegisterOptions = () => resolveListenerRegistrationOptions(deviceId, connectionName, {
531269
+ allowInteractiveOAuth: false,
531270
+ surface: "listen"
531271
+ });
531272
+ const registerOptions = await resolveRegisterOptions();
531273
+ const { connectionId, wsUrl, supportsSplitStatusChannels } = await registerWithCloudRetry(registerOptions, {
530605
531274
  onRetry: (attempt, delayMs, error54) => {
530606
531275
  updateCommandResult3(ctx.buffersRef, ctx.refreshDerived, cmdId, msg, `Registering listener "${connectionName}"...
530607
531276
  Retry ${attempt} in ${Math.round(delayMs / 1000)}s: ${error54.message}`, true, "running");
@@ -530655,13 +531324,8 @@ Awaiting instructions${urlText}`, true, "finished");
530655
531324
  onNeedsReregister: async () => {
530656
531325
  updateCommandResult3(ctx.buffersRef, ctx.refreshDerived, cmdId, msg, `Environment expired, re-registering "${connectionName}"...`, true, "running");
530657
531326
  try {
530658
- const reregisterResult = await registerWithCloudRetry({
530659
- serverUrl,
530660
- apiKey,
530661
- deviceId,
530662
- connectionName,
530663
- listenerInstanceId: deriveListenerInstanceId("listen", connectionName)
530664
- }, {
531327
+ const nextRegisterOptions = await resolveRegisterOptions();
531328
+ const reregisterResult = await registerWithCloudRetry(nextRegisterOptions, {
530665
531329
  maxDurationMs: Infinity,
530666
531330
  onRetry: (attempt, delayMs, error54) => {
530667
531331
  updateCommandResult3(ctx.buffersRef, ctx.refreshDerived, cmdId, msg, `Re-registering "${connectionName}"...
@@ -530694,10 +531358,10 @@ Retry ${attempt} in ${Math.round(delayMs / 1000)}s: ${error54.message}`, true, "
530694
531358
  }
530695
531359
  var activeCommandId3 = null;
530696
531360
  var init_listen = __esm(() => {
530697
- init_client2();
530698
531361
  init_app_urls();
530699
531362
  init_settings_manager();
530700
531363
  init_listen_register();
531364
+ init_auth();
530701
531365
  });
530702
531366
 
530703
531367
  // src/cli/app/submit-connection-commands.ts
@@ -531896,7 +532560,7 @@ var exports_export = {};
531896
532560
  __export(exports_export, {
531897
532561
  packageSkills: () => packageSkills
531898
532562
  });
531899
- import { readdir as readdir17, readFile as readFile28 } from "node:fs/promises";
532563
+ import { readdir as readdir17, readFile as readFile27 } from "node:fs/promises";
531900
532564
  import { relative as relative16, resolve as resolve40 } from "node:path";
531901
532565
  async function packageSkills(agentId, skillsDir) {
531902
532566
  const skills = [];
@@ -531917,7 +532581,7 @@ async function packageSkills(agentId, skillsDir) {
531917
532581
  const skillDir = resolve40(baseDir, entry.name);
531918
532582
  const skillMdPath = resolve40(skillDir, "SKILL.md");
531919
532583
  try {
531920
- await readFile28(skillMdPath, "utf-8");
532584
+ await readFile27(skillMdPath, "utf-8");
531921
532585
  } catch {
531922
532586
  console.warn(`Skipping invalid skill ${entry.name}: missing SKILL.md`);
531923
532587
  continue;
@@ -531949,7 +532613,7 @@ async function readSkillFiles(skillDir) {
531949
532613
  if (entry.isDirectory()) {
531950
532614
  await walk(fullPath);
531951
532615
  } else {
531952
- const content = await readFile28(fullPath, "utf-8");
532616
+ const content = await readFile27(fullPath, "utf-8");
531953
532617
  const relativePath = relative16(skillDir, fullPath).replace(/\\/g, "/");
531954
532618
  files[relativePath] = content;
531955
532619
  }
@@ -532768,7 +533432,7 @@ ${SYSTEM_REMINDER_CLOSE}`),
532768
533432
  const adeUrl = buildChatUrl(agentId, {
532769
533433
  conversationId: conversationIdRef.current
532770
533434
  });
532771
- Promise.resolve().then(() => (init_open(), exports_open)).then(({ default: open4 }) => open4(adeUrl, { wait: false })).catch(() => {});
533435
+ Promise.resolve().then(() => (init_open(), exports_open)).then(({ default: open5 }) => open5(adeUrl, { wait: false })).catch(() => {});
532772
533436
  cmd.finish(`Opening ADE...
532773
533437
  → ${adeUrl}`, true);
532774
533438
  return { submitted: true };
@@ -538727,7 +539391,8 @@ async function ensureDefaultAgents2(backend4, options3) {
538727
539391
  const createOptions = personality === "tutorial" ? {
538728
539392
  ...await buildCreateAgentOptionsForPersonality2({
538729
539393
  personalityId: "tutorial",
538730
- model
539394
+ model,
539395
+ tags: [ONBOARDING_ORIGIN_TAG]
538731
539396
  }),
538732
539397
  memoryPromptMode
538733
539398
  } : {
@@ -538975,7 +539640,7 @@ __export(exports_import2, {
538975
539640
  extractSkillsFromAf: () => extractSkillsFromAf2
538976
539641
  });
538977
539642
  import { createReadStream as createReadStream3 } from "node:fs";
538978
- import { access as access4, chmod as chmod3, mkdir as mkdir20, readFile as readFile29, writeFile as writeFile22 } from "node:fs/promises";
539643
+ import { access as access4, chmod as chmod3, mkdir as mkdir20, readFile as readFile28, writeFile as writeFile21 } from "node:fs/promises";
538979
539644
  import { dirname as dirname38, isAbsolute as isAbsolute27, relative as relative17, resolve as resolve41, sep as sep8, win32 as win326 } from "node:path";
538980
539645
  function validateImportedSkillName2(name) {
538981
539646
  const trimmedName = name.trim();
@@ -539076,7 +539741,7 @@ async function importAgentFromFile2(options3) {
539076
539741
  }
539077
539742
  async function extractSkillsFromAf2(afPath, destDir) {
539078
539743
  const extracted = [];
539079
- const content = await readFile29(afPath, "utf-8");
539744
+ const content = await readFile28(afPath, "utf-8");
539080
539745
  const afData = JSON.parse(content);
539081
539746
  if (!afData.skills || !Array.isArray(afData.skills)) {
539082
539747
  return [];
@@ -539105,7 +539770,7 @@ async function writeSkillFiles2(skillDir, files) {
539105
539770
  async function writeSkillFile2(skillDir, filePath, content) {
539106
539771
  const fullPath = resolveImportedSkillFilePath2(skillDir, filePath);
539107
539772
  await mkdir20(dirname38(fullPath), { recursive: true });
539108
- await writeFile22(fullPath, content, "utf-8");
539773
+ await writeFile21(fullPath, content, "utf-8");
539109
539774
  const isScript = filePath.startsWith("scripts/") || content.trimStart().startsWith("#!");
539110
539775
  if (isScript) {
539111
539776
  try {
@@ -539158,7 +539823,7 @@ function parseRegistryHandle2(handle2) {
539158
539823
  async function importAgentFromRegistry2(options3) {
539159
539824
  const { tmpdir: tmpdir12 } = await import("node:os");
539160
539825
  const { join: join84 } = await import("node:path");
539161
- const { writeFile: writeFile23, unlink: unlink5 } = await import("node:fs/promises");
539826
+ const { writeFile: writeFile22, unlink: unlink5 } = await import("node:fs/promises");
539162
539827
  const { author, name } = parseRegistryHandle2(options3.handle);
539163
539828
  const rawUrl = `https://raw.githubusercontent.com/${AGENT_REGISTRY_OWNER2}/${AGENT_REGISTRY_REPO2}/refs/heads/${AGENT_REGISTRY_BRANCH2}/agents/@${author}/${name}/${name}.af`;
539164
539829
  const response = await fetch(rawUrl);
@@ -539170,7 +539835,7 @@ async function importAgentFromRegistry2(options3) {
539170
539835
  }
539171
539836
  const afContent = await response.text();
539172
539837
  const tempPath = join84(tmpdir12(), `letta-import-${author}-${name}-${Date.now()}.af`);
539173
- await writeFile23(tempPath, afContent, "utf-8");
539838
+ await writeFile22(tempPath, afContent, "utf-8");
539174
539839
  try {
539175
539840
  const result = await importAgentFromFile2({
539176
539841
  filePath: tempPath,
@@ -542506,14 +543171,18 @@ function resolveImportFlagAlias(options3) {
542506
543171
  return options3.importFlagValue ?? options3.fromAfFlagValue;
542507
543172
  }
542508
543173
 
543174
+ // src/cli/helpers/app-urls.ts
543175
+ var CHAT_BASE2 = "https://chat.letta.com";
543176
+ var LETTA_CHAT_API_KEYS_URL2 = `${CHAT_BASE2}/preferences/api-keys`;
543177
+
542509
543178
  // src/cli/helpers/error-formatter.ts
542510
543179
  init_error();
542511
543180
  init_conversation_busy_error();
542512
543181
  init_app_urls();
542513
543182
  init_error_context();
542514
543183
  init_zai_errors();
542515
- var LETTA_USAGE_URL2 = buildAppUrl("/settings/organization/usage");
542516
- var LETTA_AGENTS_URL2 = buildAppUrl("/projects/default-project/agents");
543184
+ var LETTA_USAGE_URL2 = buildChatWebUrl("/preferences/usage");
543185
+ var LETTA_AGENTS_URL2 = buildPlatformUrl("/projects/default-project/agents");
542517
543186
  function formatConversationBusyErrorForDisplay2(errorText, runId, options3) {
542518
543187
  if (!isConversationBusyErrorText(errorText))
542519
543188
  return null;
@@ -544256,7 +544925,7 @@ import WebSocket7, { WebSocketServer } from "ws";
544256
544925
 
544257
544926
  // src/websocket/app-server-auth.ts
544258
544927
  import { createHash as createHash5, createHmac, timingSafeEqual } from "node:crypto";
544259
- import { readFile as readFile13 } from "node:fs/promises";
544928
+ import { readFile as readFile12 } from "node:fs/promises";
544260
544929
  import { isIP as isIP2 } from "node:net";
544261
544930
  import { isAbsolute as isAbsolute22 } from "node:path";
544262
544931
  var INVALID_AUTHORIZATION_HEADER_MESSAGE = "invalid authorization header";
@@ -544500,7 +545169,7 @@ function bearerTokenFromHeaders(headers) {
544500
545169
  async function readTrimmedSecret(path31) {
544501
545170
  let raw2;
544502
545171
  try {
544503
- raw2 = await readFile13(path31, "utf8");
545172
+ raw2 = await readFile12(path31, "utf8");
544504
545173
  } catch (error54) {
544505
545174
  const message = error54 instanceof Error ? error54.message : String(error54);
544506
545175
  throw new Error(`failed to read websocket auth secret ${path31}: ${message}`);
@@ -546350,10 +547019,10 @@ import { parseArgs as parseArgs7 } from "node:util";
546350
547019
 
546351
547020
  // src/cli/subcommands/dream-sources/index.ts
546352
547021
  init_reflection_transcript();
546353
- import { createHash as createHash6 } from "node:crypto";
547022
+ import { createHash as createHash7 } from "node:crypto";
546354
547023
 
546355
547024
  // src/cli/subcommands/dream-sources/openhands.ts
546356
- import { readdir as readdir12, readFile as readFile19, stat as stat14 } from "node:fs/promises";
547025
+ import { readdir as readdir12, readFile as readFile18, stat as stat14 } from "node:fs/promises";
546357
547026
  import { join as join52 } from "node:path";
546358
547027
  var RESULT_TEXT_TRUNCATE_LIMIT = 4000;
546359
547028
  function joinTextContent(content) {
@@ -546480,7 +547149,7 @@ async function readOpenHandsEventDir(dir) {
546480
547149
  }
546481
547150
  const events = [];
546482
547151
  for (const name of fileNames) {
546483
- const raw2 = await readFile19(join52(eventsDir, name), "utf-8");
547152
+ const raw2 = await readFile18(join52(eventsDir, name), "utf-8");
546484
547153
  const event2 = safeJsonParseOr(raw2, null);
546485
547154
  if (event2 && typeof event2 === "object") {
546486
547155
  events.push(event2);
@@ -546495,17 +547164,17 @@ var openHandsAdapter = {
546495
547164
  if (info?.isDirectory()) {
546496
547165
  return convertOpenHandsEvents(await readOpenHandsEventDir(locator));
546497
547166
  }
546498
- const raw2 = await readFile19(locator, "utf-8");
547167
+ const raw2 = await readFile18(locator, "utf-8");
546499
547168
  return convertOpenHandsEvents(parseOpenHandsEventsFile(raw2, locator));
546500
547169
  }
546501
547170
  };
546502
547171
 
546503
547172
  // src/cli/subcommands/dream-sources/transcript.ts
546504
- import { readFile as readFile20 } from "node:fs/promises";
547173
+ import { readFile as readFile19 } from "node:fs/promises";
546505
547174
  var transcriptAdapter = {
546506
547175
  type: "transcript",
546507
547176
  async convert(locator) {
546508
- const raw2 = await readFile20(locator, "utf-8");
547177
+ const raw2 = await readFile19(locator, "utf-8");
546509
547178
  const entries = [];
546510
547179
  for (const line of raw2.split(`
546511
547180
  `)) {
@@ -546543,7 +547212,7 @@ function parseFromSource(spec) {
546543
547212
  return { adapter, locator };
546544
547213
  }
546545
547214
  function conversationIdForSource(parsed) {
546546
- const hash4 = createHash6("sha1").update(`${parsed.adapter.type}:${parsed.locator}`).digest("hex").slice(0, 12);
547215
+ const hash4 = createHash7("sha1").update(`${parsed.adapter.type}:${parsed.locator}`).digest("hex").slice(0, 12);
546547
547216
  return `from-${parsed.adapter.type}-${hash4}`;
546548
547217
  }
546549
547218
  async function stageFromSource(agentId, parsed) {
@@ -546557,7 +547226,7 @@ async function stageFromSource(agentId, parsed) {
546557
547226
  init_memory_filesystem2();
546558
547227
  init_memory_git();
546559
547228
  import { execFileSync as execFileSync7 } from "node:child_process";
546560
- import { mkdir as mkdir13, readFile as readFile21, rm as rm5, writeFile as writeFile15 } from "node:fs/promises";
547229
+ import { mkdir as mkdir13, readFile as readFile20, rm as rm6, writeFile as writeFile14 } from "node:fs/promises";
546561
547230
  import { basename as basename25, dirname as dirname27, join as join53 } from "node:path";
546562
547231
  function resolveDreamTarget(spec) {
546563
547232
  const fileName = basename25(spec);
@@ -546633,7 +547302,7 @@ function buildTargetInstruction(target2) {
546633
547302
  }
546634
547303
  async function readExistingTarget(target2) {
546635
547304
  try {
546636
- return await readFile21(target2.path, "utf-8");
547305
+ return await readFile20(target2.path, "utf-8");
546637
547306
  } catch {
546638
547307
  return null;
546639
547308
  }
@@ -546661,7 +547330,7 @@ async function syncTargetIntoMemory(agentId, target2, content) {
546661
547330
  }
546662
547331
  const absPath = join53(memoryDir, relPath);
546663
547332
  await mkdir13(dirname27(absPath), { recursive: true });
546664
- await writeFile15(absPath, addManagedFrontmatter(content, target2.kind), "utf-8");
547333
+ await writeFile14(absPath, addManagedFrontmatter(content, target2.kind), "utf-8");
546665
547334
  try {
546666
547335
  const { getBackend: getBackend2 } = await Promise.resolve().then(() => (init_backend2(), exports_backend));
546667
547336
  const syncMode = getBackend2().capabilities.localMemfs ? "local" : "remote";
@@ -546678,7 +547347,7 @@ async function syncTargetIntoMemory(agentId, target2, content) {
546678
547347
  });
546679
547348
  return { synced: result.committed };
546680
547349
  } catch (error54) {
546681
- await rm5(absPath, { force: true });
547350
+ await rm6(absPath, { force: true });
546682
547351
  throw error54;
546683
547352
  }
546684
547353
  }
@@ -546694,7 +547363,7 @@ function readTargetFromMemory(agentId, target2) {
546694
547363
  }
546695
547364
  async function writeTarget(target2, content) {
546696
547365
  await mkdir13(dirname27(target2.path), { recursive: true });
546697
- await writeFile15(target2.path, content, "utf-8");
547366
+ await writeFile14(target2.path, content, "utf-8");
546698
547367
  }
546699
547368
 
546700
547369
  // src/cli/subcommands/dream.ts
@@ -546721,6 +547390,11 @@ Options:
546721
547390
  --timeout <seconds> Fail if the reflection pass has not completed
546722
547391
  in this many seconds (default: 1500)
546723
547392
  -i, --instruction <text> Additional instruction for the reflection pass
547393
+ --prompt <text> Advanced: replace the reflection task prompt
547394
+ entirely (you supply the transcript/memory
547395
+ mechanics via $TRANSCRIPT_PATH and $MEMORY_DIR)
547396
+ --system <text> Advanced: replace the reflection subagent's
547397
+ system prompt
546724
547398
  --json Emit machine-readable JSON output
546725
547399
  -h, --help Show this help
546726
547400
 
@@ -546739,6 +547413,8 @@ var DREAM_OPTIONS = {
546739
547413
  effort: { type: "string" },
546740
547414
  timeout: { type: "string" },
546741
547415
  instruction: { type: "string", short: "i" },
547416
+ prompt: { type: "string" },
547417
+ system: { type: "string" },
546742
547418
  json: { type: "boolean" }
546743
547419
  };
546744
547420
  var DEFAULT_TIMEOUT_SECONDS = 1500;
@@ -546871,6 +547547,8 @@ ${targetInstruction}` : targetInstruction;
546871
547547
  description: "Reflect on recent conversations",
546872
547548
  model: parsed.values.model,
546873
547549
  instruction,
547550
+ reflectionPromptOverride: parsed.values.prompt,
547551
+ reflectionSystemPromptOverride: parsed.values.system,
546874
547552
  recompileByConversation: new Map,
546875
547553
  recompileQueuedByConversation: new Set,
546876
547554
  onCompletionMessage: (message, completionResult) => {
@@ -547125,7 +547803,6 @@ async function runEnvironmentsSubcommand(argv, deps = {}) {
547125
547803
  }
547126
547804
 
547127
547805
  // src/cli/subcommands/listen.tsx
547128
- init_oauth();
547129
547806
  init_paths();
547130
547807
  init_restore_scope();
547131
547808
  await __promiseAll([
@@ -547298,26 +547975,9 @@ class RemoteSessionLog {
547298
547975
 
547299
547976
  // src/cli/subcommands/listen.tsx
547300
547977
  init_listen_register();
547978
+ init_auth();
547301
547979
  var jsx_dev_runtime12 = __toESM(require_jsx_dev_runtime(), 1);
547302
- var LISTENER_TOKEN_REFRESH_WINDOW_MS = 5 * 60 * 1000;
547303
547980
  var activeListenerProcessAnchors = new Set;
547304
- var defaultListenerOAuthDeps = {
547305
- LETTA_CLOUD_API_URL,
547306
- pollForToken,
547307
- refreshAccessToken,
547308
- requestDeviceCode
547309
- };
547310
- var listenerOAuthDepsOverride = null;
547311
- function getListenerOAuthDeps() {
547312
- return listenerOAuthDepsOverride ?? defaultListenerOAuthDeps;
547313
- }
547314
-
547315
- class MissingListenerApiKeyError extends Error {
547316
- constructor() {
547317
- super("LETTA_API_KEY not found");
547318
- this.name = "MissingListenerApiKeyError";
547319
- }
547320
- }
547321
547981
  function PromptEnvName(props) {
547322
547982
  const [value, setValue] = import_react33.useState("");
547323
547983
  return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
@@ -547367,16 +548027,6 @@ async function flushListenerTelemetryEnd(exitReason) {
547367
548027
  await telemetry.flush();
547368
548028
  } catch {}
547369
548029
  }
547370
- function getListenerServerUrl(settings3) {
547371
- const oauthDeps = getListenerOAuthDeps();
547372
- return process.env.LETTA_BASE_URL || settings3.env?.LETTA_BASE_URL || oauthDeps.LETTA_CLOUD_API_URL;
547373
- }
547374
- function normalizeListenerBaseUrl(url2) {
547375
- return url2.trim().replace(/\/+$/, "");
547376
- }
547377
- function isCloudListenerServerUrl(serverUrl) {
547378
- return normalizeListenerBaseUrl(serverUrl) === normalizeListenerBaseUrl(getListenerOAuthDeps().LETTA_CLOUD_API_URL);
547379
- }
547380
548030
  async function resolveListenerStartupMode(channelNames) {
547381
548031
  const settings3 = await settingsManager.getSettingsWithSecureTokens();
547382
548032
  const serverUrl = getListenerServerUrl(settings3);
@@ -547404,84 +548054,6 @@ function getScopedChannelRestoreAgentScope() {
547404
548054
  function resolveChannelRestoreAgentScope(scopedRestoreAgentScope) {
547405
548055
  return scopedRestoreAgentScope ?? parseChannelRestoreAgentScope(process.env[RESTORE_CHANNEL_AGENT_SCOPE_ENV]) ?? (isLocalBackendEnvEnabled() ? "local" : "cloud");
547406
548056
  }
547407
- async function refreshListenerAccessToken(settings3, deviceId, connectionName) {
547408
- const oauthDeps = getListenerOAuthDeps();
547409
- const now = Date.now();
547410
- console.log("Access token expired, refreshing...");
547411
- const tokens = await oauthDeps.refreshAccessToken(settings3.refreshToken, deviceId, connectionName);
547412
- settingsManager.updateSettings({
547413
- env: { LETTA_API_KEY: tokens.access_token },
547414
- tokenExpiresAt: now + tokens.expires_in * 1000,
547415
- ...tokens.refresh_token ? { refreshToken: tokens.refresh_token } : {}
547416
- });
547417
- await settingsManager.flush();
547418
- console.log("Token refreshed successfully.");
547419
- return tokens.access_token;
547420
- }
547421
- async function runListenerOAuthLogin(deviceId, connectionName) {
547422
- const oauthDeps = getListenerOAuthDeps();
547423
- console.log(`No API key found. Starting OAuth login...
547424
- `);
547425
- const deviceData = await oauthDeps.requestDeviceCode();
547426
- console.log(`To authenticate, visit: ${deviceData.verification_uri_complete}`);
547427
- console.log(`Your code: ${deviceData.user_code}
547428
- `);
547429
- console.log(`Waiting for authorization...
547430
- `);
547431
- const tokens = await oauthDeps.pollForToken(deviceData.device_code, deviceData.interval, deviceData.expires_in, deviceId, connectionName);
547432
- const now = Date.now();
547433
- settingsManager.updateSettings({
547434
- env: { LETTA_API_KEY: tokens.access_token },
547435
- tokenExpiresAt: now + tokens.expires_in * 1000,
547436
- ...tokens.refresh_token ? { refreshToken: tokens.refresh_token } : {}
547437
- });
547438
- await settingsManager.flush();
547439
- console.log(`Authenticated successfully.
547440
- `);
547441
- return tokens.access_token;
547442
- }
547443
- async function resolveListenerRegistrationOptions(deviceId, connectionName) {
547444
- const settings3 = await settingsManager.getSettingsWithSecureTokens();
547445
- const serverUrl = getListenerServerUrl(settings3);
547446
- const envApiKey = process.env.LETTA_API_KEY;
547447
- if (envApiKey) {
547448
- return {
547449
- serverUrl,
547450
- apiKey: envApiKey,
547451
- deviceId,
547452
- connectionName,
547453
- listenerInstanceId: deriveListenerInstanceId("server", connectionName)
547454
- };
547455
- }
547456
- let apiKey = settings3.env?.LETTA_API_KEY;
547457
- if (isCloudListenerServerUrl(serverUrl)) {
547458
- const expiresAt = settings3.tokenExpiresAt;
547459
- if (settings3.refreshToken && expiresAt) {
547460
- const now = Date.now();
547461
- if (!apiKey || now >= expiresAt - LISTENER_TOKEN_REFRESH_WINDOW_MS) {
547462
- try {
547463
- apiKey = await refreshListenerAccessToken(settings3, deviceId, connectionName);
547464
- } catch (refreshErr) {
547465
- console.warn("Token refresh failed:", refreshErr instanceof Error ? refreshErr.message : String(refreshErr));
547466
- apiKey = undefined;
547467
- }
547468
- }
547469
- }
547470
- if (!apiKey) {
547471
- apiKey = await runListenerOAuthLogin(deviceId, connectionName);
547472
- }
547473
- }
547474
- if (!apiKey) {
547475
- throw new MissingListenerApiKeyError;
547476
- }
547477
- return {
547478
- serverUrl,
547479
- apiKey,
547480
- deviceId,
547481
- connectionName,
547482
- listenerInstanceId: deriveListenerInstanceId("server", connectionName)
547483
- };
547484
- }
547485
548057
  var LISTEN_OPTIONS = {
547486
548058
  "env-name": { type: "string" },
547487
548059
  channels: { type: "string" },
@@ -548686,7 +549258,7 @@ async function runMemorySubcommand(argv) {
548686
549258
  init_backend2();
548687
549259
  init_message_search();
548688
549260
  init_settings_manager();
548689
- import { writeFile as writeFile16 } from "node:fs/promises";
549261
+ import { writeFile as writeFile15 } from "node:fs/promises";
548690
549262
  import { resolve as resolve31 } from "node:path";
548691
549263
  import { parseArgs as parseArgs12 } from "node:util";
548692
549264
  function printUsage9() {
@@ -549031,7 +549603,7 @@ async function runMessagesSubcommand(argv, deps = {}) {
549031
549603
  `).trim();
549032
549604
  if (outputPathRaw && typeof outputPathRaw === "string") {
549033
549605
  const outputPath = resolve31(process.cwd(), outputPathRaw);
549034
- await writeFile16(outputPath, `${transcript}
549606
+ await writeFile15(outputPath, `${transcript}
549035
549607
  `, "utf-8");
549036
549608
  console.log(JSON.stringify({
549037
549609
  conversation_id: conversationId,
@@ -551719,14 +552291,13 @@ class SettingsManager2 {
551719
552291
  }
551720
552292
  async getSettingsWithSecureTokens() {
551721
552293
  const baseSettings = this.getSettings();
551722
- let secureTokens = { ...this.secureTokensCache };
552294
+ const secureTokens = { ...this.secureTokensCache };
551723
552295
  if (!getRuntimeContext()) {
551724
552296
  const secretsAvailable2 = await this.isKeychainAvailable();
551725
552297
  if (secretsAvailable2) {
551726
- secureTokens = {
551727
- ...secureTokens,
551728
- ...await this.getSecureTokens()
551729
- };
552298
+ const storedTokens = await this.getSecureTokens();
552299
+ secureTokens.apiKey = storedTokens.apiKey ?? secureTokens.apiKey;
552300
+ secureTokens.refreshToken = storedTokens.refreshToken ?? secureTokens.refreshToken;
551730
552301
  }
551731
552302
  }
551732
552303
  const fallbackRefreshToken = !secureTokens.refreshToken && baseSettings.refreshToken ? baseSettings.refreshToken : secureTokens.refreshToken;
@@ -553786,7 +554357,7 @@ Note: Flags should use double dashes for full names (e.g., --yolo, not -yolo)`);
553786
554357
  if (isHeadless && baseURL === LETTA_CLOUD_API_URL2 && !process.env.LETTA_API_KEY) {
553787
554358
  console.error("Missing LETTA_API_KEY");
553788
554359
  console.error("Headless mode requires an API key set via the LETTA_API_KEY environment variable.");
553789
- console.error("Get an API key at https://app.letta.com/api-keys");
554360
+ console.error(`Get an API key at ${LETTA_CHAT_API_KEYS_URL2}`);
553790
554361
  process.exit(1);
553791
554362
  }
553792
554363
  if (!isHeadless && baseURL === LETTA_CLOUD_API_URL2 && !settings3.refreshToken && !apiKey) {
@@ -554793,4 +555364,4 @@ Error during initialization: ${message}`);
554793
555364
  }
554794
555365
  main2();
554795
555366
 
554796
- //# debugId=EA7311931B35FB9664756E2164756E21
555367
+ //# debugId=8DBFF6903CD7B1D064756E2164756E21