@letta-ai/letta-code 0.23.4 → 0.23.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/letta.js +909 -213
  2. package/package.json +1 -1
package/letta.js CHANGED
@@ -3269,7 +3269,7 @@ var package_default;
3269
3269
  var init_package = __esm(() => {
3270
3270
  package_default = {
3271
3271
  name: "@letta-ai/letta-code",
3272
- version: "0.23.4",
3272
+ version: "0.23.5",
3273
3273
  description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
3274
3274
  type: "module",
3275
3275
  bin: {
@@ -8394,7 +8394,7 @@ var init_models2 = __esm(() => {
8394
8394
  id: "opus",
8395
8395
  handle: "anthropic/claude-opus-4-6",
8396
8396
  label: "Opus 4.6",
8397
- description: "Anthropic's best model (high reasoning)",
8397
+ description: "Anthropic's (legacy) best model (high reasoning)",
8398
8398
  isFeatured: true,
8399
8399
  updateArgs: {
8400
8400
  context_window: 200000,
@@ -8458,6 +8458,88 @@ var init_models2 = __esm(() => {
8458
8458
  parallel_tool_calls: true
8459
8459
  }
8460
8460
  },
8461
+ {
8462
+ id: "opus-4.7",
8463
+ handle: "anthropic/claude-opus-4-7",
8464
+ label: "Opus 4.7",
8465
+ description: "Anthropic's best model (med reasoning)",
8466
+ isFeatured: true,
8467
+ updateArgs: {
8468
+ context_window: 200000,
8469
+ max_output_tokens: 128000,
8470
+ reasoning_effort: "medium",
8471
+ enable_reasoner: true,
8472
+ max_reasoning_tokens: 12000,
8473
+ parallel_tool_calls: true
8474
+ }
8475
+ },
8476
+ {
8477
+ id: "opus-4.7-low",
8478
+ handle: "anthropic/claude-opus-4-7",
8479
+ label: "Opus 4.7",
8480
+ description: "Opus 4.7 (low reasoning)",
8481
+ updateArgs: {
8482
+ context_window: 200000,
8483
+ max_output_tokens: 128000,
8484
+ reasoning_effort: "low",
8485
+ enable_reasoner: true,
8486
+ max_reasoning_tokens: 4000,
8487
+ parallel_tool_calls: true
8488
+ }
8489
+ },
8490
+ {
8491
+ id: "opus-4.7-medium",
8492
+ handle: "anthropic/claude-opus-4-7",
8493
+ label: "Opus 4.7",
8494
+ description: "Opus 4.7 (med reasoning)",
8495
+ updateArgs: {
8496
+ context_window: 200000,
8497
+ max_output_tokens: 128000,
8498
+ reasoning_effort: "medium",
8499
+ enable_reasoner: true,
8500
+ max_reasoning_tokens: 12000,
8501
+ parallel_tool_calls: true
8502
+ }
8503
+ },
8504
+ {
8505
+ id: "opus-4.7-high",
8506
+ handle: "anthropic/claude-opus-4-7",
8507
+ label: "Opus 4.7",
8508
+ description: "Opus 4.7 (high reasoning)",
8509
+ updateArgs: {
8510
+ context_window: 200000,
8511
+ max_output_tokens: 128000,
8512
+ reasoning_effort: "high",
8513
+ enable_reasoner: true,
8514
+ parallel_tool_calls: true
8515
+ }
8516
+ },
8517
+ {
8518
+ id: "opus-4.7-xhigh",
8519
+ handle: "anthropic/claude-opus-4-7",
8520
+ label: "Opus 4.7",
8521
+ description: "Opus 4.7 (extra-high reasoning)",
8522
+ updateArgs: {
8523
+ context_window: 200000,
8524
+ max_output_tokens: 128000,
8525
+ reasoning_effort: "xhigh",
8526
+ enable_reasoner: true,
8527
+ parallel_tool_calls: true
8528
+ }
8529
+ },
8530
+ {
8531
+ id: "opus-4.7-max",
8532
+ handle: "anthropic/claude-opus-4-7",
8533
+ label: "Opus 4.7",
8534
+ description: "Opus 4.7 (max reasoning)",
8535
+ updateArgs: {
8536
+ context_window: 200000,
8537
+ max_output_tokens: 128000,
8538
+ reasoning_effort: "max",
8539
+ enable_reasoner: true,
8540
+ parallel_tool_calls: true
8541
+ }
8542
+ },
8461
8543
  {
8462
8544
  id: "opus-1m",
8463
8545
  handle: "anthropic/claude-opus-4-6",
@@ -41245,6 +41327,18 @@ function normalizeSlackAttachmentLike(value) {
41245
41327
  files
41246
41328
  };
41247
41329
  }
41330
+ function resolveSlackThreadMessageText(message) {
41331
+ const text = typeof message.text === "string" ? message.text.trim() : "";
41332
+ if (text) {
41333
+ return text;
41334
+ }
41335
+ const files = Array.isArray(message.files) ? message.files.map((entry) => normalizeSlackFileLike(entry)).filter((entry) => Boolean(entry)) : [];
41336
+ if (files.length === 0) {
41337
+ return "";
41338
+ }
41339
+ const fileNames = files.map((file) => file.name ?? "file").join(", ");
41340
+ return `[attached: ${fileNames}]`;
41341
+ }
41248
41342
  function isAllowedSlackHostname(hostname3) {
41249
41343
  const normalized = hostname3.trim().toLowerCase();
41250
41344
  return ALLOWED_SLACK_HOST_SUFFIXES.some((suffix) => normalized === suffix || normalized.endsWith(`.${suffix}`));
@@ -41413,6 +41507,78 @@ async function resolveSlackInboundAttachments(params) {
41413
41507
  }).catch(() => null)));
41414
41508
  return resolved.filter((attachment) => Boolean(attachment));
41415
41509
  }
41510
+ async function resolveSlackThreadStarter(params) {
41511
+ try {
41512
+ const response = await params.client.conversations.replies({
41513
+ channel: params.channelId,
41514
+ ts: params.threadTs,
41515
+ limit: 1,
41516
+ inclusive: true
41517
+ });
41518
+ const message = response.messages?.[0];
41519
+ if (!message) {
41520
+ return null;
41521
+ }
41522
+ const text = resolveSlackThreadMessageText(message);
41523
+ if (!text) {
41524
+ return null;
41525
+ }
41526
+ return {
41527
+ text,
41528
+ userId: isNonEmptyString(message.user) ? message.user : undefined,
41529
+ botId: isNonEmptyString(message.bot_id) ? message.bot_id : undefined,
41530
+ ts: isNonEmptyString(message.ts) ? message.ts : undefined
41531
+ };
41532
+ } catch {
41533
+ return null;
41534
+ }
41535
+ }
41536
+ async function resolveSlackThreadHistory(params) {
41537
+ const maxMessages = params.limit ?? 20;
41538
+ if (!Number.isFinite(maxMessages) || maxMessages <= 0) {
41539
+ return [];
41540
+ }
41541
+ const fetchLimit = 200;
41542
+ const retained = [];
41543
+ let cursor;
41544
+ try {
41545
+ do {
41546
+ const response = await params.client.conversations.replies({
41547
+ channel: params.channelId,
41548
+ ts: params.threadTs,
41549
+ limit: fetchLimit,
41550
+ inclusive: true,
41551
+ ...cursor ? { cursor } : {}
41552
+ });
41553
+ for (const message of response.messages ?? []) {
41554
+ const text = resolveSlackThreadMessageText(message);
41555
+ if (!text) {
41556
+ continue;
41557
+ }
41558
+ if (params.currentMessageTs && message.ts === params.currentMessageTs) {
41559
+ continue;
41560
+ }
41561
+ if (message.ts === params.threadTs) {
41562
+ continue;
41563
+ }
41564
+ retained.push(message);
41565
+ if (retained.length > maxMessages) {
41566
+ retained.shift();
41567
+ }
41568
+ }
41569
+ const nextCursor = response.response_metadata?.next_cursor;
41570
+ cursor = typeof nextCursor === "string" && nextCursor.trim().length > 0 ? nextCursor.trim() : undefined;
41571
+ } while (cursor);
41572
+ return retained.map((message) => ({
41573
+ text: resolveSlackThreadMessageText(message),
41574
+ userId: isNonEmptyString(message.user) ? message.user : undefined,
41575
+ botId: isNonEmptyString(message.bot_id) ? message.bot_id : undefined,
41576
+ ts: isNonEmptyString(message.ts) ? message.ts : undefined
41577
+ }));
41578
+ } catch {
41579
+ return [];
41580
+ }
41581
+ }
41416
41582
  var MAX_SLACK_ATTACHMENTS = 8, MAX_SLACK_ATTACHMENT_BYTES, ALLOWED_SLACK_HOST_SUFFIXES;
41417
41583
  var init_media2 = __esm(() => {
41418
41584
  init_config();
@@ -41428,6 +41594,9 @@ var init_media2 = __esm(() => {
41428
41594
  async function loadSlackBoltModule() {
41429
41595
  return loadChannelRuntimeModule("slack", "@slack/bolt");
41430
41596
  }
41597
+ async function loadSlackWebApiModule() {
41598
+ return loadChannelRuntimeModule("slack", "@slack/web-api");
41599
+ }
41431
41600
  async function ensureSlackRuntimeInstalled() {
41432
41601
  return ensureChannelRuntimeInstalled("slack");
41433
41602
  }
@@ -41457,6 +41626,22 @@ function resolveSlackAppConstructor(mod) {
41457
41626
  }
41458
41627
  return App2;
41459
41628
  }
41629
+ function resolveSlackWebClientModule(value) {
41630
+ if (!value || typeof value !== "object") {
41631
+ return null;
41632
+ }
41633
+ const webClient = Reflect.get(value, "WebClient");
41634
+ return isConstructorFunction(webClient) ? webClient : null;
41635
+ }
41636
+ function resolveSlackWebClientConstructor(mod) {
41637
+ const defaultExport = mod && typeof mod === "object" ? Reflect.get(mod, "default") : undefined;
41638
+ const nestedDefault = defaultExport && typeof defaultExport === "object" ? Reflect.get(defaultExport, "default") : undefined;
41639
+ const WebClient = resolveSlackWebClientModule(mod) ?? resolveSlackWebClientModule(defaultExport) ?? resolveSlackWebClientModule(nestedDefault) ?? (isConstructorFunction(defaultExport) ? defaultExport : null);
41640
+ if (!WebClient) {
41641
+ throw new Error('Installed Slack runtime did not export constructor "WebClient".');
41642
+ }
41643
+ return WebClient;
41644
+ }
41460
41645
  function isNonEmptyString2(value) {
41461
41646
  return typeof value === "string" && value.length > 0;
41462
41647
  }
@@ -41499,7 +41684,7 @@ function resolveUploadMimeType(filePath) {
41499
41684
  return;
41500
41685
  }
41501
41686
  }
41502
- async function uploadSlackFile(slackApp, msg) {
41687
+ async function uploadSlackFile(slackClient, msg) {
41503
41688
  if (!msg.mediaPath) {
41504
41689
  throw new Error("mediaPath is required for Slack file uploads.");
41505
41690
  }
@@ -41507,7 +41692,7 @@ async function uploadSlackFile(slackApp, msg) {
41507
41692
  const uploadFileName = msg.fileName ?? basename4(msg.mediaPath);
41508
41693
  const uploadTitle = msg.title ?? uploadFileName;
41509
41694
  const uploadMimeType = resolveUploadMimeType(uploadFileName);
41510
- const uploadUrlResp = await slackApp.client.files.getUploadURLExternal({
41695
+ const uploadUrlResp = await slackClient.files.getUploadURLExternal({
41511
41696
  filename: uploadFileName,
41512
41697
  length: buffer.length
41513
41698
  });
@@ -41522,7 +41707,7 @@ async function uploadSlackFile(slackApp, msg) {
41522
41707
  if (!uploadResp.ok) {
41523
41708
  throw new Error(`Failed to upload Slack file: HTTP ${uploadResp.status}`);
41524
41709
  }
41525
- const completeResp = await slackApp.client.files.completeUploadExternal({
41710
+ const completeResp = await slackClient.files.completeUploadExternal({
41526
41711
  files: [{ id: uploadUrlResp.file_id, title: uploadTitle }],
41527
41712
  channel_id: msg.chatId,
41528
41713
  ...msg.text.trim() ? { initial_comment: msg.text } : {},
@@ -41538,6 +41723,27 @@ function resolveSlackUserDisplayName(userInfo) {
41538
41723
  const profile = asRecord2(user?.profile);
41539
41724
  return firstNonEmptyString(profile?.display_name, profile?.real_name, user?.name);
41540
41725
  }
41726
+ function truncateSlackThreadLabel(text, maxLength = 80) {
41727
+ const normalized = text.replace(/\s+/g, " ").trim();
41728
+ if (!normalized) {
41729
+ return null;
41730
+ }
41731
+ if (normalized.length <= maxLength) {
41732
+ return normalized;
41733
+ }
41734
+ return `${normalized.slice(0, maxLength - 1).trimEnd()}…`;
41735
+ }
41736
+ function buildSlackThreadLabel(msg, starterText) {
41737
+ if (msg.chatType !== "channel") {
41738
+ return;
41739
+ }
41740
+ const roomLabel = isNonEmptyString2(msg.chatLabel) && msg.chatLabel !== msg.chatId ? ` in ${msg.chatLabel}` : "";
41741
+ const preview = truncateSlackThreadLabel(starterText ?? msg.text);
41742
+ if (preview) {
41743
+ return `Slack thread${roomLabel}: ${preview}`;
41744
+ }
41745
+ return roomLabel ? `Slack thread${roomLabel}` : `Slack thread ${msg.chatId}`;
41746
+ }
41541
41747
  async function resolveSlackAccountDisplayName(botToken, appToken) {
41542
41748
  const bolt = await loadSlackBoltModule();
41543
41749
  const App2 = resolveSlackAppConstructor(bolt);
@@ -41563,11 +41769,14 @@ async function resolveSlackAccountDisplayName(botToken, appToken) {
41563
41769
  }
41564
41770
  function createSlackAdapter(config) {
41565
41771
  let app = null;
41772
+ let writeClient = null;
41566
41773
  let running = false;
41567
41774
  let botUserId = null;
41568
41775
  const knownThreadIdsByMessageId = new Map;
41569
41776
  const knownUserDisplayNames = new Map;
41570
41777
  const seenIngressMessageKeys = new Map;
41778
+ const lifecycleStateByMessageKey = new Map;
41779
+ const lifecycleOperationByMessageKey = new Map;
41571
41780
  function buildIngressMessageKey(channelId, messageId) {
41572
41781
  if (!isNonEmptyString2(channelId) || !isNonEmptyString2(messageId)) {
41573
41782
  return null;
@@ -41592,6 +41801,96 @@ function createSlackAdapter(config) {
41592
41801
  }
41593
41802
  }
41594
41803
  }
41804
+ function getLifecycleMessageKey(source) {
41805
+ if (source.channel !== "slack" || !isNonEmptyString2(source.chatId) || !isNonEmptyString2(source.messageId)) {
41806
+ return null;
41807
+ }
41808
+ return `${source.chatId}:${source.messageId}`;
41809
+ }
41810
+ function pruneLifecycleState(now = Date.now()) {
41811
+ for (const [key, entry] of lifecycleStateByMessageKey) {
41812
+ if (entry.updatedAt + SLACK_LIFECYCLE_STATE_TTL_MS <= now) {
41813
+ lifecycleStateByMessageKey.delete(key);
41814
+ }
41815
+ }
41816
+ if (lifecycleStateByMessageKey.size <= SLACK_LIFECYCLE_STATE_MAX) {
41817
+ return;
41818
+ }
41819
+ const oldestEntries = Array.from(lifecycleStateByMessageKey.entries()).sort((a, b) => a[1].updatedAt - b[1].updatedAt);
41820
+ const overflowCount = lifecycleStateByMessageKey.size - SLACK_LIFECYCLE_STATE_MAX;
41821
+ for (let index = 0;index < overflowCount; index += 1) {
41822
+ const entry = oldestEntries[index];
41823
+ if (entry) {
41824
+ lifecycleStateByMessageKey.delete(entry[0]);
41825
+ }
41826
+ }
41827
+ }
41828
+ async function sendLifecycleReaction(source, emoji, removeReaction = false) {
41829
+ if (!isNonEmptyString2(source.messageId)) {
41830
+ return;
41831
+ }
41832
+ await ensureApp();
41833
+ const slackClient = await ensureWriteClient();
41834
+ if (removeReaction) {
41835
+ await slackClient.reactions.remove({
41836
+ channel: source.chatId,
41837
+ timestamp: source.messageId,
41838
+ name: emoji
41839
+ });
41840
+ return;
41841
+ }
41842
+ await slackClient.reactions.add({
41843
+ channel: source.chatId,
41844
+ timestamp: source.messageId,
41845
+ name: emoji
41846
+ });
41847
+ }
41848
+ function scheduleLifecycleTransition(source, nextState) {
41849
+ const key = getLifecycleMessageKey(source);
41850
+ if (!key) {
41851
+ return null;
41852
+ }
41853
+ const previous = lifecycleOperationByMessageKey.get(key) ?? Promise.resolve();
41854
+ const operation = previous.catch(() => {}).then(async () => {
41855
+ pruneLifecycleState();
41856
+ const currentState = lifecycleStateByMessageKey.get(key)?.state;
41857
+ if (currentState === nextState) {
41858
+ lifecycleStateByMessageKey.set(key, {
41859
+ state: nextState,
41860
+ updatedAt: Date.now()
41861
+ });
41862
+ return;
41863
+ }
41864
+ if (nextState === "queued") {
41865
+ if (!currentState) {
41866
+ await sendLifecycleReaction(source, "eyes");
41867
+ lifecycleStateByMessageKey.set(key, {
41868
+ state: nextState,
41869
+ updatedAt: Date.now()
41870
+ });
41871
+ }
41872
+ return;
41873
+ }
41874
+ if (currentState === "queued") {
41875
+ try {
41876
+ await sendLifecycleReaction(source, "eyes", true);
41877
+ } catch {}
41878
+ }
41879
+ await sendLifecycleReaction(source, nextState === "completed" ? "white_check_mark" : "x");
41880
+ lifecycleStateByMessageKey.set(key, {
41881
+ state: nextState,
41882
+ updatedAt: Date.now()
41883
+ });
41884
+ }).catch((error) => {
41885
+ console.warn(`[Slack] Failed to update lifecycle reaction for ${key}:`, error instanceof Error ? error.message : error);
41886
+ }).finally(() => {
41887
+ if (lifecycleOperationByMessageKey.get(key) === operation) {
41888
+ lifecycleOperationByMessageKey.delete(key);
41889
+ }
41890
+ });
41891
+ lifecycleOperationByMessageKey.set(key, operation);
41892
+ return operation;
41893
+ }
41595
41894
  function markIngressMessageSeen(channelId, messageId) {
41596
41895
  const key = buildIngressMessageKey(channelId, messageId);
41597
41896
  if (!key) {
@@ -41815,6 +42114,19 @@ function createSlackAdapter(config) {
41815
42114
  app = instance;
41816
42115
  return instance;
41817
42116
  }
42117
+ async function ensureWriteClient() {
42118
+ if (writeClient) {
42119
+ return writeClient;
42120
+ }
42121
+ const webApi = await loadSlackWebApiModule();
42122
+ const WebClient = resolveSlackWebClientConstructor(webApi);
42123
+ writeClient = new WebClient(config.botToken, {
42124
+ retryConfig: {
42125
+ retries: 0
42126
+ }
42127
+ });
42128
+ return writeClient;
42129
+ }
41818
42130
  const adapter = {
41819
42131
  id: `slack:${config.accountId}`,
41820
42132
  channelId: "slack",
@@ -41838,15 +42150,33 @@ function createSlackAdapter(config) {
41838
42150
  await app.stop();
41839
42151
  running = false;
41840
42152
  app = null;
42153
+ writeClient = null;
41841
42154
  botUserId = null;
41842
42155
  seenIngressMessageKeys.clear();
42156
+ lifecycleStateByMessageKey.clear();
42157
+ lifecycleOperationByMessageKey.clear();
41843
42158
  console.log("[Slack] App stopped");
41844
42159
  },
41845
42160
  isRunning() {
41846
42161
  return running;
41847
42162
  },
42163
+ async handleTurnLifecycleEvent(event) {
42164
+ if (!running) {
42165
+ return;
42166
+ }
42167
+ if (event.type === "queued") {
42168
+ await scheduleLifecycleTransition(event.source, "queued");
42169
+ return;
42170
+ }
42171
+ if (event.type === "processing") {
42172
+ return;
42173
+ }
42174
+ const nextState = event.outcome === "completed" ? "completed" : event.outcome === "cancelled" ? "cancelled" : "error";
42175
+ await Promise.all(event.sources.map((source) => scheduleLifecycleTransition(source, nextState)));
42176
+ },
41848
42177
  async sendMessage(msg) {
41849
- const slackApp = await ensureApp();
42178
+ await ensureApp();
42179
+ const slackClient = await ensureWriteClient();
41850
42180
  if (msg.reaction) {
41851
42181
  const targetMessageId = msg.targetMessageId ?? msg.replyToMessageId;
41852
42182
  if (!targetMessageId) {
@@ -41857,13 +42187,13 @@ function createSlackAdapter(config) {
41857
42187
  throw new Error("Slack reaction emoji cannot be empty.");
41858
42188
  }
41859
42189
  if (msg.removeReaction) {
41860
- await slackApp.client.reactions.remove({
42190
+ await slackClient.reactions.remove({
41861
42191
  channel: msg.chatId,
41862
42192
  timestamp: targetMessageId,
41863
42193
  name: emoji
41864
42194
  });
41865
42195
  } else {
41866
- await slackApp.client.reactions.add({
42196
+ await slackClient.reactions.add({
41867
42197
  channel: msg.chatId,
41868
42198
  timestamp: targetMessageId,
41869
42199
  name: emoji
@@ -41872,9 +42202,9 @@ function createSlackAdapter(config) {
41872
42202
  return { messageId: targetMessageId };
41873
42203
  }
41874
42204
  if (msg.mediaPath) {
41875
- return uploadSlackFile(slackApp, msg);
42205
+ return uploadSlackFile(slackClient, msg);
41876
42206
  }
41877
- const response = await slackApp.client.chat.postMessage({
42207
+ const response = await slackClient.chat.postMessage({
41878
42208
  channel: msg.chatId,
41879
42209
  text: msg.text,
41880
42210
  ...msg.threadId ?? msg.replyToMessageId ? { thread_ts: msg.threadId ?? msg.replyToMessageId } : {}
@@ -41883,22 +42213,88 @@ function createSlackAdapter(config) {
41883
42213
  return { messageId: response.ts ?? "" };
41884
42214
  },
41885
42215
  async sendDirectReply(chatId, text, options) {
41886
- const slackApp = await ensureApp();
41887
- const response = await slackApp.client.chat.postMessage({
42216
+ await ensureApp();
42217
+ const slackClient = await ensureWriteClient();
42218
+ const response = await slackClient.chat.postMessage({
41888
42219
  channel: chatId,
41889
42220
  text,
41890
42221
  ...options?.replyToMessageId ? { thread_ts: options.replyToMessageId } : {}
41891
42222
  });
41892
42223
  rememberMessageThread(response.ts, options?.replyToMessageId ?? response.ts ?? null);
41893
42224
  },
42225
+ async prepareInboundMessage(msg, options) {
42226
+ if (!options?.isFirstRouteTurn || msg.channel !== "slack" || msg.chatType !== "channel" || !isNonEmptyString2(msg.threadId) || !isNonEmptyString2(msg.messageId) || msg.threadId === msg.messageId) {
42227
+ return msg;
42228
+ }
42229
+ const slackApp = await ensureApp();
42230
+ const starter = await resolveSlackThreadStarter({
42231
+ channelId: msg.chatId,
42232
+ threadTs: msg.threadId,
42233
+ client: slackApp.client
42234
+ });
42235
+ const history = await resolveSlackThreadHistory({
42236
+ channelId: msg.chatId,
42237
+ threadTs: msg.threadId,
42238
+ client: slackApp.client,
42239
+ currentMessageTs: msg.messageId,
42240
+ limit: INITIAL_SLACK_THREAD_HISTORY_LIMIT
42241
+ });
42242
+ if (!starter && history.length === 0) {
42243
+ return msg;
42244
+ }
42245
+ const uniqueUserIds = new Set;
42246
+ if (isNonEmptyString2(starter?.userId)) {
42247
+ uniqueUserIds.add(starter.userId);
42248
+ }
42249
+ for (const entry of history) {
42250
+ if (isNonEmptyString2(entry.userId)) {
42251
+ uniqueUserIds.add(entry.userId);
42252
+ }
42253
+ }
42254
+ await Promise.all(Array.from(uniqueUserIds).map(async (userId) => {
42255
+ await resolveUserName(slackApp, userId);
42256
+ }));
42257
+ const resolveThreadSenderName = (userId, botId) => {
42258
+ if (isNonEmptyString2(userId)) {
42259
+ return knownUserDisplayNames.get(userId) ?? userId;
42260
+ }
42261
+ if (isNonEmptyString2(botId)) {
42262
+ return `Bot (${botId})`;
42263
+ }
42264
+ return;
42265
+ };
42266
+ return {
42267
+ ...msg,
42268
+ threadContext: {
42269
+ label: buildSlackThreadLabel(msg, starter?.text),
42270
+ ...starter ? {
42271
+ starter: {
42272
+ messageId: starter.ts,
42273
+ senderId: starter.userId ?? starter.botId,
42274
+ senderName: resolveThreadSenderName(starter.userId, starter.botId),
42275
+ text: starter.text
42276
+ }
42277
+ } : {},
42278
+ ...history.length > 0 ? {
42279
+ history: history.map((entry) => ({
42280
+ messageId: entry.ts,
42281
+ senderId: entry.userId ?? entry.botId,
42282
+ senderName: resolveThreadSenderName(entry.userId, entry.botId),
42283
+ text: entry.text
42284
+ }))
42285
+ } : {}
42286
+ }
42287
+ };
42288
+ },
41894
42289
  onMessage: undefined
41895
42290
  };
41896
42291
  return adapter;
41897
42292
  }
41898
- var SLACK_INGRESS_DEDUPE_TTL_MS = 60000, SLACK_INGRESS_DEDUPE_MAX = 2000;
42293
+ var INITIAL_SLACK_THREAD_HISTORY_LIMIT = 20, SLACK_INGRESS_DEDUPE_TTL_MS = 60000, SLACK_INGRESS_DEDUPE_MAX = 2000, SLACK_LIFECYCLE_STATE_TTL_MS, SLACK_LIFECYCLE_STATE_MAX = 2000;
41899
42294
  var init_adapter2 = __esm(() => {
41900
42295
  init_media2();
41901
42296
  init_runtime2();
42297
+ SLACK_LIFECYCLE_STATE_TTL_MS = 6 * 60 * 60 * 1000;
41902
42298
  });
41903
42299
 
41904
42300
  // src/channels/slack/messageActions.ts
@@ -42081,8 +42477,8 @@ var init_plugin2 = __esm(() => {
42081
42477
  metadata: {
42082
42478
  id: "slack",
42083
42479
  displayName: "Slack",
42084
- runtimePackages: ["@slack/bolt@4.7.0"],
42085
- runtimeModules: ["@slack/bolt"]
42480
+ runtimePackages: ["@slack/bolt@4.7.0", "@slack/web-api@7.15.0"],
42481
+ runtimeModules: ["@slack/bolt", "@slack/web-api"]
42086
42482
  },
42087
42483
  createAdapter(account) {
42088
42484
  return createSlackAdapter(account);
@@ -42138,8 +42534,8 @@ var init_pluginRegistry = __esm(() => {
42138
42534
  metadata: {
42139
42535
  id: "slack",
42140
42536
  displayName: "Slack",
42141
- runtimePackages: ["@slack/bolt@4.7.0"],
42142
- runtimeModules: ["@slack/bolt"]
42537
+ runtimePackages: ["@slack/bolt@4.7.0", "@slack/web-api@7.15.0"],
42538
+ runtimeModules: ["@slack/bolt", "@slack/web-api"]
42143
42539
  },
42144
42540
  load: async () => {
42145
42541
  const { slackChannelPlugin: slackChannelPlugin2 } = await Promise.resolve().then(() => (init_plugin2(), exports_plugin2));
@@ -42724,6 +43120,47 @@ function buildReactionXml(msg) {
42724
43120
  }
42725
43121
  return `<reaction ${attrs.join(" ")} />`;
42726
43122
  }
43123
+ function buildThreadContextEntryXml(tagName, entry) {
43124
+ const attrs = [];
43125
+ if (entry.senderId) {
43126
+ attrs.push(`sender_id="${escapeXmlAttribute(entry.senderId)}"`);
43127
+ }
43128
+ if (entry.senderName) {
43129
+ attrs.push(`sender_name="${escapeXmlAttribute(entry.senderName)}"`);
43130
+ }
43131
+ if (entry.messageId) {
43132
+ attrs.push(`message_id="${escapeXmlAttribute(entry.messageId)}"`);
43133
+ }
43134
+ const attrString = attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
43135
+ return `<${tagName}${attrString}>
43136
+ ${escapeXmlText(entry.text)}
43137
+ </${tagName}>`;
43138
+ }
43139
+ function buildThreadContextXml(msg) {
43140
+ const threadContext = msg.threadContext;
43141
+ if (!threadContext) {
43142
+ return null;
43143
+ }
43144
+ const parts = [];
43145
+ if (threadContext.starter) {
43146
+ parts.push(buildThreadContextEntryXml("thread-starter", threadContext.starter));
43147
+ }
43148
+ const historyEntries = threadContext.history ?? [];
43149
+ if (historyEntries.length > 0) {
43150
+ parts.push([
43151
+ "<thread-history>",
43152
+ ...historyEntries.map((entry) => buildThreadContextEntryXml("thread-message", entry)),
43153
+ "</thread-history>"
43154
+ ].join(`
43155
+ `));
43156
+ }
43157
+ if (parts.length === 0) {
43158
+ return null;
43159
+ }
43160
+ const attrs = threadContext.label ? ` label="${escapeXmlAttribute(threadContext.label)}"` : "";
43161
+ return [`<thread-context${attrs}>`, ...parts, "</thread-context>"].join(`
43162
+ `);
43163
+ }
42727
43164
  function buildChannelNotificationXml(msg) {
42728
43165
  const attrs = [
42729
43166
  `source="${escapeXmlAttribute(msg.channel)}"`,
@@ -42742,8 +43179,9 @@ function buildChannelNotificationXml(msg) {
42742
43179
  const attrString = attrs.join(" ");
42743
43180
  const escapedText = msg.text ? escapeXmlText(msg.text) : "";
42744
43181
  const reactionXml = buildReactionXml(msg);
43182
+ const threadContextXml = buildThreadContextXml(msg);
42745
43183
  const attachmentXml = (msg.attachments ?? []).map(buildAttachmentXml);
42746
- const body = [reactionXml, ...attachmentXml, escapedText].filter(Boolean).join(`
43184
+ const body = [threadContextXml, reactionXml, ...attachmentXml, escapedText].filter(Boolean).join(`
42747
43185
  `);
42748
43186
  return `<channel-notification ${attrString}>
42749
43187
  ${body}
@@ -42828,6 +43266,18 @@ function buildSlackConversationSummary(msg) {
42828
43266
  }
42829
43267
  return `[Slack] Thread${channelLabel || ` ${msg.chatId}`}`;
42830
43268
  }
43269
+ function buildChannelTurnSource(route, msg) {
43270
+ return {
43271
+ channel: msg.channel,
43272
+ accountId: msg.accountId,
43273
+ chatId: msg.chatId,
43274
+ chatType: msg.chatType,
43275
+ messageId: msg.messageId,
43276
+ threadId: msg.threadId,
43277
+ agentId: route.agentId,
43278
+ conversationId: route.conversationId
43279
+ };
43280
+ }
42831
43281
  function getChannelRegistry() {
42832
43282
  return instance;
42833
43283
  }
@@ -42867,6 +43317,51 @@ class ChannelRegistry {
42867
43317
  getActiveChannelIds() {
42868
43318
  return Array.from(this.adapters.values()).filter((adapter) => adapter.isRunning()).map((adapter) => adapter.channelId ?? adapter.id);
42869
43319
  }
43320
+ async dispatchTurnLifecycleEvent(event) {
43321
+ const groups = new Map;
43322
+ const sources = event.type === "queued" ? [event.source] : event.sources;
43323
+ for (const source of sources) {
43324
+ const adapter = this.getAdapter(source.channel, source.accountId ?? LEGACY_CHANNEL_ACCOUNT_ID);
43325
+ if (!adapter?.handleTurnLifecycleEvent) {
43326
+ continue;
43327
+ }
43328
+ const groupKey = this.getAdapterKey(source.channel, source.accountId ?? LEGACY_CHANNEL_ACCOUNT_ID);
43329
+ const existing = groups.get(groupKey);
43330
+ if (existing) {
43331
+ existing.sources.push(source);
43332
+ continue;
43333
+ }
43334
+ groups.set(groupKey, {
43335
+ adapter,
43336
+ sources: [source]
43337
+ });
43338
+ }
43339
+ for (const { adapter, sources: groupedSources } of groups.values()) {
43340
+ const handleTurnLifecycleEvent = adapter.handleTurnLifecycleEvent;
43341
+ if (!handleTurnLifecycleEvent) {
43342
+ continue;
43343
+ }
43344
+ try {
43345
+ if (event.type === "queued") {
43346
+ const [firstSource] = groupedSources;
43347
+ if (!firstSource) {
43348
+ continue;
43349
+ }
43350
+ await handleTurnLifecycleEvent({
43351
+ type: "queued",
43352
+ source: firstSource
43353
+ });
43354
+ continue;
43355
+ }
43356
+ await handleTurnLifecycleEvent({
43357
+ ...event,
43358
+ sources: groupedSources
43359
+ });
43360
+ } catch (error) {
43361
+ console.error(`[Channels] Failed to handle ${event.type} lifecycle event for ${adapter.channelId ?? adapter.id}/${adapter.accountId ?? LEGACY_CHANNEL_ACCOUNT_ID}:`, error instanceof Error ? error.message : error);
43362
+ }
43363
+ }
43364
+ }
42870
43365
  setMessageHandler(handler) {
42871
43366
  this.messageHandler = handler;
42872
43367
  }
@@ -42985,11 +43480,20 @@ class ChannelRegistry {
42985
43480
  if (!config)
42986
43481
  return;
42987
43482
  if (msg.channel === "slack" && config.channel === "slack") {
42988
- const slackRoute = await this.ensureSlackRoute(adapter, msg, config);
42989
- if (!slackRoute) {
43483
+ const slackResult = await this.ensureSlackRoute(adapter, msg, config);
43484
+ if (!slackResult) {
42990
43485
  return;
42991
43486
  }
42992
- this.deliverOrBuffer(slackRoute, formatChannelNotification(msg));
43487
+ const preparedMessage = adapter.prepareInboundMessage ? await adapter.prepareInboundMessage(msg, {
43488
+ isFirstRouteTurn: slackResult.isFirstRouteTurn
43489
+ }) : msg;
43490
+ this.deliverOrBuffer({
43491
+ route: slackResult.route,
43492
+ content: formatChannelNotification(preparedMessage),
43493
+ turnSources: [
43494
+ buildChannelTurnSource(slackResult.route, preparedMessage)
43495
+ ]
43496
+ });
42993
43497
  return;
42994
43498
  }
42995
43499
  if (config.dmPolicy === "allowlist") {
@@ -43021,7 +43525,11 @@ class ChannelRegistry {
43021
43525
  return;
43022
43526
  }
43023
43527
  const content = formatChannelNotification(msg);
43024
- this.deliverOrBuffer(route, content);
43528
+ this.deliverOrBuffer({
43529
+ route,
43530
+ content,
43531
+ turnSources: [buildChannelTurnSource(route, msg)]
43532
+ });
43025
43533
  }
43026
43534
  async createConversationForAgent(agentId, summary) {
43027
43535
  const client = await getClient();
@@ -43071,7 +43579,10 @@ class ChannelRegistry {
43071
43579
  route = getRoute(msg.channel, msg.chatId, accountId, routeThreadId);
43072
43580
  }
43073
43581
  if (route) {
43074
- return route;
43582
+ return {
43583
+ route,
43584
+ isFirstRouteTurn: false
43585
+ };
43075
43586
  }
43076
43587
  if (msg.chatType === "channel" && !msg.isMention) {
43077
43588
  return null;
@@ -43092,14 +43603,17 @@ class ChannelRegistry {
43092
43603
  type: "targets_updated",
43093
43604
  channelId: msg.channel
43094
43605
  });
43095
- return this.createSlackRoute(config, msg);
43606
+ return {
43607
+ route: await this.createSlackRoute(config, msg),
43608
+ isFirstRouteTurn: true
43609
+ };
43096
43610
  }
43097
- deliverOrBuffer(route, content) {
43611
+ deliverOrBuffer(delivery) {
43098
43612
  if (this.isReady()) {
43099
- this.messageHandler?.(route, content);
43613
+ this.messageHandler?.(delivery);
43100
43614
  return;
43101
43615
  }
43102
- this.buffer.push({ route, content });
43616
+ this.buffer.push(delivery);
43103
43617
  }
43104
43618
  flushBuffer() {
43105
43619
  if (!this.messageHandler)
@@ -43107,7 +43621,7 @@ class ChannelRegistry {
43107
43621
  while (this.buffer.length > 0) {
43108
43622
  const item = this.buffer.shift();
43109
43623
  if (item) {
43110
- this.messageHandler(item.route, item.content);
43624
+ this.messageHandler(item);
43111
43625
  }
43112
43626
  }
43113
43627
  }
@@ -45617,10 +46131,12 @@ var exports_memoryFilesystem = {};
45617
46131
  __export(exports_memoryFilesystem, {
45618
46132
  renderMemoryFilesystemTree: () => renderMemoryFilesystemTree,
45619
46133
  labelFromRelativePath: () => labelFromRelativePath,
46134
+ isMemfsEnabledOnServer: () => isMemfsEnabledOnServer,
45620
46135
  isLettaCloud: () => isLettaCloud,
45621
46136
  getMemorySystemDir: () => getMemorySystemDir,
45622
46137
  getMemoryFilesystemRoot: () => getMemoryFilesystemRoot,
45623
46138
  ensureMemoryFilesystemDirs: () => ensureMemoryFilesystemDirs,
46139
+ ensureLocalMemfsCheckout: () => ensureLocalMemfsCheckout,
45624
46140
  enableMemfsIfCloud: () => enableMemfsIfCloud,
45625
46141
  applyMemfsFlags: () => applyMemfsFlags,
45626
46142
  MEMORY_TREE_MAX_LINES: () => MEMORY_TREE_MAX_LINES,
@@ -45650,6 +46166,23 @@ function ensureMemoryFilesystemDirs(agentId, homeDir = homedir10()) {
45650
46166
  mkdirSync13(systemDir, { recursive: true });
45651
46167
  }
45652
46168
  }
46169
+ async function isMemfsEnabledOnServer(agentId) {
46170
+ const { getClient: getClient3 } = await Promise.resolve().then(() => (init_client2(), exports_client));
46171
+ const client = await getClient3();
46172
+ const agent = await client.agents.retrieve(agentId);
46173
+ const { GIT_MEMORY_ENABLED_TAG: GIT_MEMORY_ENABLED_TAG2 } = await Promise.resolve().then(() => (init_memoryGit(), exports_memoryGit));
46174
+ const enabled = agent.tags?.includes(GIT_MEMORY_ENABLED_TAG2) ?? false;
46175
+ const { settingsManager: settingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), exports_settings_manager));
46176
+ settingsManager2.setMemfsEnabled(agentId, enabled);
46177
+ return enabled;
46178
+ }
46179
+ async function ensureLocalMemfsCheckout(agentId) {
46180
+ const { isGitRepo: isGitRepo2, cloneMemoryRepo: cloneMemoryRepo2 } = await Promise.resolve().then(() => (init_memoryGit(), exports_memoryGit));
46181
+ if (isGitRepo2(agentId)) {
46182
+ return;
46183
+ }
46184
+ await cloneMemoryRepo2(agentId);
46185
+ }
45653
46186
  function labelFromRelativePath(relativePath) {
45654
46187
  const normalized = relativePath.replace(/\\/g, "/");
45655
46188
  return normalized.replace(/\.md$/, "");
@@ -49895,6 +50428,19 @@ function resolveLettaInvocation(env3 = process.env, argv = process.argv, execPat
49895
50428
  }
49896
50429
  const scriptPath = argv[1] || "";
49897
50430
  if (scriptPath && isDevLettaEntryScript(scriptPath)) {
50431
+ const runtimeName = path4.basename(execPath).toLowerCase();
50432
+ if (runtimeName.includes("bun")) {
50433
+ return {
50434
+ command: execPath,
50435
+ args: [
50436
+ "--loader:.md=text",
50437
+ "--loader:.mdx=text",
50438
+ "--loader:.txt=text",
50439
+ "run",
50440
+ scriptPath
50441
+ ]
50442
+ };
50443
+ }
49898
50444
  return { command: execPath, args: [scriptPath] };
49899
50445
  }
49900
50446
  return null;
@@ -85382,7 +85928,7 @@ __export(exports_diff, {
85382
85928
  ADV_DIFF_IGNORE_WHITESPACE: () => ADV_DIFF_IGNORE_WHITESPACE,
85383
85929
  ADV_DIFF_CONTEXT_LINES: () => ADV_DIFF_CONTEXT_LINES
85384
85930
  });
85385
- import { basename as basename7 } from "node:path";
85931
+ import { basename as basename8 } from "node:path";
85386
85932
  function readFileOrNull(p) {
85387
85933
  try {
85388
85934
  return __require("node:fs").readFileSync(p, "utf-8");
@@ -85406,7 +85952,7 @@ function applyAllOccurrences(content, oldStr, newStr) {
85406
85952
  return { ok: true, out: content.split(oldStr).join(newStr) };
85407
85953
  }
85408
85954
  function computeAdvancedDiff(input, opts) {
85409
- const fileName = basename7(input.filePath || "");
85955
+ const fileName = basename8(input.filePath || "");
85410
85956
  const fileContent = opts?.oldStrOverride !== undefined ? opts.oldStrOverride : readFileOrNull(input.filePath);
85411
85957
  if (fileContent === null && input.kind !== "write") {
85412
85958
  return { mode: "fallback", reason: "File not readable" };
@@ -85472,7 +86018,7 @@ function computeAdvancedDiff(input, opts) {
85472
86018
  return { mode: "advanced", fileName, oldStr, newStr, hunks };
85473
86019
  }
85474
86020
  function parsePatchToAdvancedDiff(patchLines, filePath) {
85475
- const fileName = basename7(filePath);
86021
+ const fileName = basename8(filePath);
85476
86022
  const hunks = [];
85477
86023
  let currentHunk = null;
85478
86024
  let oldLine = 1;
@@ -85753,8 +86299,8 @@ function isFormatterSegment(tokens) {
85753
86299
  }
85754
86300
  }
85755
86301
  function isShellExecutor2(token) {
85756
- const basename8 = token.split("/").pop() ?? token;
85757
- return ["bash", "sh", "zsh", "dash", "ksh"].includes(basename8.toLowerCase());
86302
+ const basename9 = token.split("/").pop() ?? token;
86303
+ return ["bash", "sh", "zsh", "dash", "ksh"].includes(basename9.toLowerCase());
85758
86304
  }
85759
86305
  function normalizeRawCommand(command) {
85760
86306
  if (Array.isArray(command)) {
@@ -86463,7 +87009,7 @@ var init_formatArgsDisplay = __esm(async () => {
86463
87009
  });
86464
87010
 
86465
87011
  // src/helpers/diffPreview.ts
86466
- import path21, { basename as basename8 } from "node:path";
87012
+ import path21, { basename as basename9 } from "node:path";
86467
87013
  function parseHunkLinePrefix(raw) {
86468
87014
  if (raw.length === 0) {
86469
87015
  return { type: "context", content: "" };
@@ -86569,7 +87115,7 @@ async function computeDiffPreviews(toolName, toolArgs, workingDirectory = proces
86569
87115
  filePath: resolvedFilePath,
86570
87116
  content: toolArgs.content || ""
86571
87117
  });
86572
- previews.push(toDiffPreview(result, basename8(filePath)));
87118
+ previews.push(toDiffPreview(result, basename9(filePath)));
86573
87119
  }
86574
87120
  } else if (isFileEditTool2(toolName)) {
86575
87121
  const filePath = toolArgs.file_path;
@@ -86581,7 +87127,7 @@ async function computeDiffPreviews(toolName, toolArgs, workingDirectory = proces
86581
87127
  filePath: resolvedFilePath,
86582
87128
  edits: toolArgs.edits
86583
87129
  });
86584
- previews.push(toDiffPreview(result, basename8(filePath)));
87130
+ previews.push(toDiffPreview(result, basename9(filePath)));
86585
87131
  } else {
86586
87132
  const result = computeAdvancedDiff2({
86587
87133
  kind: "edit",
@@ -86590,7 +87136,7 @@ async function computeDiffPreviews(toolName, toolArgs, workingDirectory = proces
86590
87136
  newString: toolArgs.new_string || "",
86591
87137
  replaceAll: toolArgs.replace_all
86592
87138
  });
86593
- previews.push(toDiffPreview(result, basename8(filePath)));
87139
+ previews.push(toDiffPreview(result, basename9(filePath)));
86594
87140
  }
86595
87141
  }
86596
87142
  } else if (isPatchTool2(toolName) && toolArgs.input) {
@@ -86599,7 +87145,7 @@ async function computeDiffPreviews(toolName, toolArgs, workingDirectory = proces
86599
87145
  if (op.kind === "add" || op.kind === "update") {
86600
87146
  const result = parsePatchToAdvancedDiff2(op.patchLines, op.path);
86601
87147
  if (result) {
86602
- previews.push(toDiffPreview(result, basename8(op.path)));
87148
+ previews.push(toDiffPreview(result, basename9(op.path)));
86603
87149
  }
86604
87150
  }
86605
87151
  }
@@ -86609,7 +87155,7 @@ async function computeDiffPreviews(toolName, toolArgs, workingDirectory = proces
86609
87155
  if (op.kind === "add" || op.kind === "update") {
86610
87156
  const result = parsePatchToAdvancedDiff2(op.patchLines, op.path);
86611
87157
  if (result) {
86612
- previews.push(toDiffPreview(result, basename8(op.path)));
87158
+ previews.push(toDiffPreview(result, basename9(op.path)));
86613
87159
  }
86614
87160
  }
86615
87161
  }
@@ -89703,6 +90249,65 @@ function mergeDequeuedBatchContent(items) {
89703
90249
  normalizeUserContent: (content) => content
89704
90250
  });
89705
90251
  }
90252
+ function getChannelTurnSourceKey(source) {
90253
+ return [
90254
+ source.channel,
90255
+ source.accountId ?? "",
90256
+ source.chatId,
90257
+ source.messageId ?? "",
90258
+ source.threadId ?? "",
90259
+ source.agentId,
90260
+ source.conversationId
90261
+ ].join(":");
90262
+ }
90263
+ function collectBatchChannelTurnSources(runtime, batch) {
90264
+ const seen = new Set;
90265
+ const sources = [];
90266
+ for (const item of batch.items) {
90267
+ const template = runtime.queuedMessagesByItemId.get(item.id);
90268
+ for (const source of template?.channelTurnSources ?? []) {
90269
+ const key = getChannelTurnSourceKey(source);
90270
+ if (seen.has(key)) {
90271
+ continue;
90272
+ }
90273
+ seen.add(key);
90274
+ sources.push(source);
90275
+ }
90276
+ }
90277
+ return sources.length > 0 ? sources : undefined;
90278
+ }
90279
+ async function dispatchChannelTurnLifecycleEvent(event) {
90280
+ if (event.sources.length === 0) {
90281
+ return;
90282
+ }
90283
+ const registry = getChannelRegistry();
90284
+ if (!registry) {
90285
+ return;
90286
+ }
90287
+ if (event.type === "processing") {
90288
+ await registry.dispatchTurnLifecycleEvent(event);
90289
+ return;
90290
+ }
90291
+ await registry.dispatchTurnLifecycleEvent({
90292
+ type: "finished",
90293
+ batchId: event.batchId,
90294
+ sources: event.sources,
90295
+ outcome: event.outcome,
90296
+ ...event.error ? { error: event.error } : {}
90297
+ });
90298
+ }
90299
+ function mapTurnLifecycleOutcome(lastStopReason, didThrow) {
90300
+ if (didThrow) {
90301
+ return "error";
90302
+ }
90303
+ if (lastStopReason === "cancelled") {
90304
+ return "cancelled";
90305
+ }
90306
+ if (lastStopReason && lastStopReason !== "end_turn") {
90307
+ return "error";
90308
+ }
90309
+ return "completed";
90310
+ }
89706
90311
  function isBase64ImageContentPart(part) {
89707
90312
  if (!part || typeof part !== "object") {
89708
90313
  return false;
@@ -89762,6 +90367,7 @@ function getPrimaryQueueMessageItem(items) {
89762
90367
  return null;
89763
90368
  }
89764
90369
  function buildQueuedTurnMessage(runtime, batch) {
90370
+ const channelTurnSources = collectBatchChannelTurnSources(runtime, batch);
89765
90371
  const primaryItem = getPrimaryQueueMessageItem(batch.items);
89766
90372
  if (!primaryItem) {
89767
90373
  for (const item of batch.items) {
@@ -89776,6 +90382,7 @@ function buildQueuedTurnMessage(runtime, batch) {
89776
90382
  type: "message",
89777
90383
  agentId: scopeItem?.agentId ?? runtime.agentId ?? undefined,
89778
90384
  conversationId: scopeItem?.conversationId ?? runtime.conversationId,
90385
+ ...channelTurnSources ? { channelTurnSources } : {},
89779
90386
  messages: [
89780
90387
  {
89781
90388
  role: "user",
@@ -89808,6 +90415,7 @@ function buildQueuedTurnMessage(runtime, batch) {
89808
90415
  messages[firstMessageIndex] = mergedFirstMessage;
89809
90416
  return {
89810
90417
  ...template,
90418
+ ...channelTurnSources ? { channelTurnSources } : {},
89811
90419
  messages
89812
90420
  };
89813
90421
  }
@@ -89888,13 +90496,39 @@ async function drainQueuedMessages(runtime, socket, opts, processQueuedTurn) {
89888
90496
  return;
89889
90497
  }
89890
90498
  const { dequeuedBatch, queuedTurn } = consumedQueuedTurn;
90499
+ const channelTurnSources = queuedTurn.channelTurnSources ?? [];
89891
90500
  emitDequeuedUserMessage(socket, runtime, queuedTurn, dequeuedBatch);
89892
90501
  const preTurnStatus = getListenerStatus(runtime.listener) === "processing" ? "processing" : "receiving";
89893
90502
  if (opts.connectionId && runtime.listener.lastEmittedStatus !== preTurnStatus) {
89894
90503
  runtime.listener.lastEmittedStatus = preTurnStatus;
89895
90504
  opts.onStatusChange?.(preTurnStatus, opts.connectionId);
89896
90505
  }
89897
- await processQueuedTurn(queuedTurn, dequeuedBatch);
90506
+ if (channelTurnSources.length > 0) {
90507
+ await dispatchChannelTurnLifecycleEvent({
90508
+ type: "processing",
90509
+ batchId: dequeuedBatch.batchId,
90510
+ sources: channelTurnSources
90511
+ });
90512
+ }
90513
+ let turnError;
90514
+ let didThrow = false;
90515
+ try {
90516
+ await processQueuedTurn(queuedTurn, dequeuedBatch);
90517
+ } catch (error) {
90518
+ didThrow = true;
90519
+ turnError = error instanceof Error ? error.message : String(error);
90520
+ throw error;
90521
+ } finally {
90522
+ if (channelTurnSources.length > 0) {
90523
+ await dispatchChannelTurnLifecycleEvent({
90524
+ type: "finished",
90525
+ batchId: dequeuedBatch.batchId,
90526
+ sources: channelTurnSources,
90527
+ outcome: mapTurnLifecycleOutcome(runtime.lastStopReason, didThrow),
90528
+ ...turnError ? { error: turnError } : {}
90529
+ });
90530
+ }
90531
+ }
89898
90532
  emitListenerStatus(runtime.listener, opts.onStatusChange, opts.connectionId);
89899
90533
  evictConversationRuntimeIfIdle(runtime);
89900
90534
  }
@@ -89927,6 +90561,7 @@ function scheduleQueuePump(runtime, socket, opts, processQueuedTurn) {
89927
90561
  });
89928
90562
  }
89929
90563
  var init_queue = __esm(async () => {
90564
+ init_registry();
89930
90565
  init_queueRuntime();
89931
90566
  init_errorReporting();
89932
90567
  init_runtime3();
@@ -91033,7 +91668,7 @@ function scanMemoryFilesystem(memoryRoot) {
91033
91668
  } catch {
91034
91669
  return;
91035
91670
  }
91036
- const relativePath = relative12(memoryRoot, fullPath);
91671
+ const relativePath = relative12(memoryRoot, fullPath).replace(/\\/g, "/");
91037
91672
  const isLast = index === sorted.length - 1;
91038
91673
  nodes.push({
91039
91674
  name: isDir ? `${name}/` : name,
@@ -91406,6 +92041,165 @@ function emitChannelTargetsUpdated(socket, channelId) {
91406
92041
  channel_id: channelId
91407
92042
  }, "listener_channels_send_failed", "listener_channels_command");
91408
92043
  }
92044
+ async function handleListMemoryCommand(parsed, socket, overrides = {}) {
92045
+ try {
92046
+ const {
92047
+ ensureLocalMemfsCheckout: actualEnsureLocalMemfsCheckout,
92048
+ getMemoryFilesystemRoot: actualGetMemoryFilesystemRoot,
92049
+ isMemfsEnabledOnServer: actualIsMemfsEnabledOnServer
92050
+ } = await Promise.resolve().then(() => (init_memoryFilesystem(), exports_memoryFilesystem));
92051
+ const ensureLocalMemfsCheckout2 = overrides.ensureLocalMemfsCheckout ?? actualEnsureLocalMemfsCheckout;
92052
+ const getMemoryFilesystemRoot2 = overrides.getMemoryFilesystemRoot ?? actualGetMemoryFilesystemRoot;
92053
+ const isMemfsEnabledOnServer2 = overrides.isMemfsEnabledOnServer ?? actualIsMemfsEnabledOnServer;
92054
+ const { scanMemoryFilesystem: scanMemoryFilesystem2, getFileNodes: getFileNodes2, readFileContent: readFileContent2 } = await Promise.resolve().then(() => (init_memoryScanner(), exports_memoryScanner));
92055
+ const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => exports_frontmatter);
92056
+ const { existsSync: existsSync26 } = await import("node:fs");
92057
+ const { join: join31, posix: posix2 } = await import("node:path");
92058
+ const memoryRoot = getMemoryFilesystemRoot2(parsed.agent_id);
92059
+ let memfsInitialized = existsSync26(join31(memoryRoot, ".git"));
92060
+ const memfsEnabled = memfsInitialized ? true : await isMemfsEnabledOnServer2(parsed.agent_id);
92061
+ if (!memfsEnabled) {
92062
+ safeSocketSend(socket, {
92063
+ type: "list_memory_response",
92064
+ request_id: parsed.request_id,
92065
+ entries: [],
92066
+ done: true,
92067
+ total: 0,
92068
+ success: true,
92069
+ memfs_enabled: false,
92070
+ memfs_initialized: false
92071
+ }, "listener_list_memory_send_failed", "listener_list_memory");
92072
+ return true;
92073
+ }
92074
+ if (!memfsInitialized) {
92075
+ await ensureLocalMemfsCheckout2(parsed.agent_id);
92076
+ memfsInitialized = existsSync26(join31(memoryRoot, ".git"));
92077
+ }
92078
+ if (!memfsInitialized) {
92079
+ throw new Error("MemFS is enabled, but the local memory checkout could not be initialized.");
92080
+ }
92081
+ const treeNodes = scanMemoryFilesystem2(memoryRoot);
92082
+ const fileNodes = getFileNodes2(treeNodes).filter((n) => n.name.endsWith(".md"));
92083
+ const includeReferences = parsed.include_references === true;
92084
+ const allPaths = new Set(fileNodes.map((node) => node.relativePath));
92085
+ const normalizeMemoryReference = (rawReference, sourcePath) => {
92086
+ let target = rawReference.trim();
92087
+ if (!target) {
92088
+ return null;
92089
+ }
92090
+ if (target.startsWith("http://") || target.startsWith("https://") || target.startsWith("mailto:")) {
92091
+ return null;
92092
+ }
92093
+ target = target.replace(/^<|>$/g, "");
92094
+ target = target.split("#")[0] ?? "";
92095
+ target = target.split("?")[0] ?? "";
92096
+ target = target.trim().replace(/\\/g, "/");
92097
+ if (!target || target.startsWith("#")) {
92098
+ return null;
92099
+ }
92100
+ if (target.includes("|")) {
92101
+ target = target.split("|")[0] ?? "";
92102
+ }
92103
+ if (!target) {
92104
+ return null;
92105
+ }
92106
+ const sourceDir = posix2.dirname(sourcePath.replace(/\\/g, "/"));
92107
+ const candidate = target.startsWith("./") || target.startsWith("../") ? posix2.normalize(posix2.join(sourceDir, target)) : posix2.normalize(target.startsWith("/") ? target.slice(1) : target);
92108
+ if (!candidate || candidate.startsWith("../") || candidate === "." || candidate === "..") {
92109
+ return null;
92110
+ }
92111
+ const withExtension = candidate.endsWith(".md") ? candidate : `${candidate}.md`;
92112
+ const candidates = new Set([withExtension]);
92113
+ const isExplicitRelative = target.startsWith("./") || target.startsWith("../");
92114
+ if (!isExplicitRelative && !target.startsWith("/") && sourceDir && sourceDir !== ".") {
92115
+ candidates.add(posix2.normalize(posix2.join(sourceDir, withExtension)));
92116
+ }
92117
+ if (!withExtension.startsWith("system/")) {
92118
+ candidates.add(posix2.normalize(`system/${withExtension}`));
92119
+ }
92120
+ for (const resolved of candidates) {
92121
+ if (allPaths.has(resolved)) {
92122
+ return resolved;
92123
+ }
92124
+ }
92125
+ return null;
92126
+ };
92127
+ const extractMemoryReferences = (body, sourcePath) => {
92128
+ if (!body.includes("[[")) {
92129
+ return [];
92130
+ }
92131
+ const refs = new Set;
92132
+ for (const wikiMatch of body.matchAll(WIKI_LINK_REGEX)) {
92133
+ const rawTarget = wikiMatch[1];
92134
+ if (!rawTarget)
92135
+ continue;
92136
+ const normalized = normalizeMemoryReference(rawTarget, sourcePath);
92137
+ if (normalized && normalized !== sourcePath) {
92138
+ refs.add(normalized);
92139
+ }
92140
+ }
92141
+ return [...refs];
92142
+ };
92143
+ const CHUNK_SIZE = 5;
92144
+ const total = fileNodes.length;
92145
+ for (let i = 0;i < total; i += CHUNK_SIZE) {
92146
+ const chunk = fileNodes.slice(i, i + CHUNK_SIZE);
92147
+ const entries = chunk.map((node) => {
92148
+ const raw = readFileContent2(node.fullPath);
92149
+ const { frontmatter, body } = parseFrontmatter2(raw);
92150
+ const desc = frontmatter.description;
92151
+ return {
92152
+ relative_path: node.relativePath,
92153
+ is_system: node.relativePath.startsWith("system/") || node.relativePath.startsWith("system\\"),
92154
+ description: typeof desc === "string" ? desc : null,
92155
+ content: body,
92156
+ size: body.length,
92157
+ ...includeReferences ? {
92158
+ references: extractMemoryReferences(body, node.relativePath)
92159
+ } : {}
92160
+ };
92161
+ });
92162
+ const done = i + CHUNK_SIZE >= total;
92163
+ const sent = safeSocketSend(socket, {
92164
+ type: "list_memory_response",
92165
+ request_id: parsed.request_id,
92166
+ entries,
92167
+ done,
92168
+ total,
92169
+ success: true,
92170
+ memfs_enabled: true,
92171
+ memfs_initialized: true
92172
+ }, "listener_list_memory_send_failed", "listener_list_memory");
92173
+ if (!sent) {
92174
+ return true;
92175
+ }
92176
+ }
92177
+ if (total === 0) {
92178
+ safeSocketSend(socket, {
92179
+ type: "list_memory_response",
92180
+ request_id: parsed.request_id,
92181
+ entries: [],
92182
+ done: true,
92183
+ total: 0,
92184
+ success: true,
92185
+ memfs_enabled: true,
92186
+ memfs_initialized: true
92187
+ }, "listener_list_memory_send_failed", "listener_list_memory");
92188
+ }
92189
+ } catch (err) {
92190
+ trackListenerError("listener_list_memory_failed", err, "listener_memory_browser");
92191
+ safeSocketSend(socket, {
92192
+ type: "list_memory_response",
92193
+ request_id: parsed.request_id,
92194
+ entries: [],
92195
+ done: true,
92196
+ total: 0,
92197
+ success: false,
92198
+ error: err instanceof Error ? err.message : "Failed to list memory"
92199
+ }, "listener_list_memory_send_failed", "listener_list_memory");
92200
+ }
92201
+ return true;
92202
+ }
91409
92203
  async function handleCronCommand(parsed, socket) {
91410
92204
  if (parsed.type === "cron_list") {
91411
92205
  try {
@@ -92259,7 +93053,7 @@ async function handleSkillCommand(parsed, socket) {
92259
93053
  symlinkSync,
92260
93054
  unlinkSync: unlinkSync8
92261
93055
  } = await import("node:fs");
92262
- const { basename: basename9, join: join31 } = await import("node:path");
93056
+ const { basename: basename10, join: join31 } = await import("node:path");
92263
93057
  const lettaHome = process.env.LETTA_HOME || join31(process.env.HOME || process.env.USERPROFILE || "~", ".letta");
92264
93058
  const globalSkillsDir = join31(lettaHome, "skills");
92265
93059
  if (parsed.type === "skill_enable") {
@@ -92283,7 +93077,7 @@ async function handleSkillCommand(parsed, socket) {
92283
93077
  }, "listener_skill_send_failed", "listener_skill_command");
92284
93078
  return true;
92285
93079
  }
92286
- const linkName = basename9(parsed.skill_path);
93080
+ const linkName = basename10(parsed.skill_path);
92287
93081
  const linkPath = join31(globalSkillsDir, linkName);
92288
93082
  mkdirSync19(globalSkillsDir, { recursive: true });
92289
93083
  if (existsSync26(linkPath)) {
@@ -92495,12 +93289,21 @@ function wireChannelIngress(listener, socket, opts, processQueuedTurn) {
92495
93289
  const registry = getChannelRegistry();
92496
93290
  if (!registry)
92497
93291
  return;
92498
- registry.setMessageHandler((route, messageContent) => {
92499
- const rawRuntime = getOrCreateConversationRuntime(listener, route.agentId, route.conversationId);
93292
+ registry.setMessageHandler((delivery) => {
93293
+ const rawRuntime = getOrCreateConversationRuntime(listener, delivery.route.agentId, delivery.route.conversationId);
92500
93294
  if (!rawRuntime)
92501
93295
  return;
92502
93296
  const conversationRuntime = ensureConversationQueueRuntime(listener, rawRuntime);
92503
- enqueueChannelTurn(conversationRuntime, route, messageContent);
93297
+ const enqueuedItem = enqueueChannelTurn(conversationRuntime, delivery.route, delivery.content, delivery.turnSources);
93298
+ if (!enqueuedItem) {
93299
+ return;
93300
+ }
93301
+ for (const turnSource of delivery.turnSources ?? []) {
93302
+ registry.dispatchTurnLifecycleEvent({
93303
+ type: "queued",
93304
+ source: turnSource
93305
+ });
93306
+ }
92504
93307
  scheduleQueuePump(conversationRuntime, socket, opts, processQueuedTurn);
92505
93308
  });
92506
93309
  registry.setEventHandler((event) => {
@@ -92534,7 +93337,7 @@ function stampInboundUserMessageOtids(incoming) {
92534
93337
  messages
92535
93338
  };
92536
93339
  }
92537
- function enqueueChannelTurn(runtime, route, messageContent) {
93340
+ function enqueueChannelTurn(runtime, route, messageContent, turnSources) {
92538
93341
  const clientMessageId = `cm-channel-${crypto.randomUUID()}`;
92539
93342
  const enqueuedItem = runtime.queueRuntime.enqueue({
92540
93343
  kind: "message",
@@ -92551,6 +93354,7 @@ function enqueueChannelTurn(runtime, route, messageContent) {
92551
93354
  type: "message",
92552
93355
  agentId: route.agentId,
92553
93356
  conversationId: route.conversationId,
93357
+ ...turnSources?.length ? { channelTurnSources: turnSources } : {},
92554
93358
  messages: [
92555
93359
  {
92556
93360
  role: "user",
@@ -93509,144 +94313,7 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
93509
94313
  }
93510
94314
  if (isListMemoryCommand(parsed)) {
93511
94315
  runDetachedListenerTask("list_memory", async () => {
93512
- try {
93513
- const { getMemoryFilesystemRoot: getMemoryFilesystemRoot2 } = await Promise.resolve().then(() => (init_memoryFilesystem(), exports_memoryFilesystem));
93514
- const { scanMemoryFilesystem: scanMemoryFilesystem2, getFileNodes: getFileNodes2, readFileContent: readFileContent2 } = await Promise.resolve().then(() => (init_memoryScanner(), exports_memoryScanner));
93515
- const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => exports_frontmatter);
93516
- const { existsSync: existsSync26 } = await import("node:fs");
93517
- const { join: join31, posix: posix2 } = await import("node:path");
93518
- const memoryRoot = getMemoryFilesystemRoot2(parsed.agent_id);
93519
- const memfsInitialized = existsSync26(join31(memoryRoot, ".git"));
93520
- if (!memfsInitialized) {
93521
- safeSocketSend(socket, {
93522
- type: "list_memory_response",
93523
- request_id: parsed.request_id,
93524
- entries: [],
93525
- done: true,
93526
- total: 0,
93527
- success: true,
93528
- memfs_initialized: false
93529
- }, "listener_list_memory_send_failed", "listener_list_memory");
93530
- return;
93531
- }
93532
- const treeNodes = scanMemoryFilesystem2(memoryRoot);
93533
- const fileNodes = getFileNodes2(treeNodes).filter((n) => n.name.endsWith(".md"));
93534
- const includeReferences = parsed.include_references === true;
93535
- const allPaths = new Set(fileNodes.map((node) => node.relativePath));
93536
- const normalizeMemoryReference = (rawReference, sourcePath) => {
93537
- let target = rawReference.trim();
93538
- if (!target) {
93539
- return null;
93540
- }
93541
- if (target.startsWith("http://") || target.startsWith("https://") || target.startsWith("mailto:")) {
93542
- return null;
93543
- }
93544
- target = target.replace(/^<|>$/g, "");
93545
- target = target.split("#")[0] ?? "";
93546
- target = target.split("?")[0] ?? "";
93547
- target = target.trim().replace(/\\/g, "/");
93548
- if (!target || target.startsWith("#")) {
93549
- return null;
93550
- }
93551
- if (target.includes("|")) {
93552
- target = target.split("|")[0] ?? "";
93553
- }
93554
- if (!target) {
93555
- return null;
93556
- }
93557
- const sourceDir = posix2.dirname(sourcePath.replace(/\\/g, "/"));
93558
- const candidate = target.startsWith("./") || target.startsWith("../") ? posix2.normalize(posix2.join(sourceDir, target)) : posix2.normalize(target.startsWith("/") ? target.slice(1) : target);
93559
- if (!candidate || candidate.startsWith("../") || candidate === "." || candidate === "..") {
93560
- return null;
93561
- }
93562
- const withExtension = candidate.endsWith(".md") ? candidate : `${candidate}.md`;
93563
- const candidates = new Set([withExtension]);
93564
- const isExplicitRelative = target.startsWith("./") || target.startsWith("../");
93565
- if (!isExplicitRelative && !target.startsWith("/") && sourceDir && sourceDir !== ".") {
93566
- candidates.add(posix2.normalize(posix2.join(sourceDir, withExtension)));
93567
- }
93568
- if (!withExtension.startsWith("system/")) {
93569
- candidates.add(posix2.normalize(`system/${withExtension}`));
93570
- }
93571
- for (const resolved of candidates) {
93572
- if (allPaths.has(resolved)) {
93573
- return resolved;
93574
- }
93575
- }
93576
- return null;
93577
- };
93578
- const extractMemoryReferences = (body, sourcePath) => {
93579
- if (!body.includes("[[")) {
93580
- return [];
93581
- }
93582
- const refs = new Set;
93583
- for (const wikiMatch of body.matchAll(WIKI_LINK_REGEX)) {
93584
- const rawTarget = wikiMatch[1];
93585
- if (!rawTarget)
93586
- continue;
93587
- const normalized = normalizeMemoryReference(rawTarget, sourcePath);
93588
- if (normalized && normalized !== sourcePath) {
93589
- refs.add(normalized);
93590
- }
93591
- }
93592
- return [...refs];
93593
- };
93594
- const CHUNK_SIZE = 5;
93595
- const total = fileNodes.length;
93596
- for (let i = 0;i < total; i += CHUNK_SIZE) {
93597
- const chunk = fileNodes.slice(i, i + CHUNK_SIZE);
93598
- const entries = chunk.map((node) => {
93599
- const raw2 = readFileContent2(node.fullPath);
93600
- const { frontmatter, body } = parseFrontmatter2(raw2);
93601
- const desc = frontmatter.description;
93602
- return {
93603
- relative_path: node.relativePath,
93604
- is_system: node.relativePath.startsWith("system/") || node.relativePath.startsWith("system\\"),
93605
- description: typeof desc === "string" ? desc : null,
93606
- content: body,
93607
- size: body.length,
93608
- ...includeReferences ? {
93609
- references: extractMemoryReferences(body, node.relativePath)
93610
- } : {}
93611
- };
93612
- });
93613
- const done = i + CHUNK_SIZE >= total;
93614
- const sent = safeSocketSend(socket, {
93615
- type: "list_memory_response",
93616
- request_id: parsed.request_id,
93617
- entries,
93618
- done,
93619
- total,
93620
- success: true,
93621
- memfs_initialized: true
93622
- }, "listener_list_memory_send_failed", "listener_list_memory");
93623
- if (!sent) {
93624
- return;
93625
- }
93626
- }
93627
- if (total === 0) {
93628
- safeSocketSend(socket, {
93629
- type: "list_memory_response",
93630
- request_id: parsed.request_id,
93631
- entries: [],
93632
- done: true,
93633
- total: 0,
93634
- success: true,
93635
- memfs_initialized: true
93636
- }, "listener_list_memory_send_failed", "listener_list_memory");
93637
- }
93638
- } catch (err) {
93639
- trackListenerError("listener_list_memory_failed", err, "listener_memory_browser");
93640
- safeSocketSend(socket, {
93641
- type: "list_memory_response",
93642
- request_id: parsed.request_id,
93643
- entries: [],
93644
- done: true,
93645
- total: 0,
93646
- success: false,
93647
- error: err instanceof Error ? err.message : "Failed to list memory"
93648
- }, "listener_list_memory_send_failed", "listener_list_memory");
93649
- }
94316
+ await handleListMemoryCommand(parsed, socket);
93650
94317
  });
93651
94318
  return;
93652
94319
  }
@@ -94324,6 +94991,7 @@ var init_client4 = __esm(async () => {
94324
94991
  handleAbortMessageInput,
94325
94992
  handleChangeDeviceStateInput,
94326
94993
  handleCronCommand,
94994
+ handleListMemoryCommand,
94327
94995
  isDetachedChannelsCommand,
94328
94996
  handleChannelsProtocolCommand,
94329
94997
  handleSkillCommand,
@@ -115408,16 +116076,16 @@ function clipStyledSpans(spans, maxColumns) {
115408
116076
  return { spans: clipped, clipped: false };
115409
116077
  }
115410
116078
  function languageFromPath(filePath) {
115411
- const basename9 = filePath.split("/").pop() ?? filePath;
115412
- const lower = basename9.toLowerCase();
116079
+ const basename10 = filePath.split("/").pop() ?? filePath;
116080
+ const lower = basename10.toLowerCase();
115413
116081
  if (lower === "makefile")
115414
116082
  return "makefile";
115415
116083
  if (lower === "dockerfile")
115416
116084
  return "dockerfile";
115417
- const dotIdx = basename9.lastIndexOf(".");
116085
+ const dotIdx = basename10.lastIndexOf(".");
115418
116086
  if (dotIdx < 0)
115419
116087
  return;
115420
- const ext3 = basename9.slice(dotIdx + 1).toLowerCase();
116088
+ const ext3 = basename10.slice(dotIdx + 1).toLowerCase();
115421
116089
  return EXT_TO_LANG[ext3];
115422
116090
  }
115423
116091
  function colorForClassName(className, palette) {
@@ -121829,7 +122497,7 @@ var init_pasteRegistry = __esm(() => {
121829
122497
  import { execFileSync as execFileSync3 } from "node:child_process";
121830
122498
  import { existsSync as existsSync34, readFileSync as readFileSync20, statSync as statSync10, unlinkSync as unlinkSync10 } from "node:fs";
121831
122499
  import { tmpdir as tmpdir4 } from "node:os";
121832
- import { basename as basename9, extname as extname8, isAbsolute as isAbsolute20, join as join42, resolve as resolve29 } from "node:path";
122500
+ import { basename as basename10, extname as extname8, isAbsolute as isAbsolute20, join as join42, resolve as resolve29 } from "node:path";
121833
122501
  function countLines2(text) {
121834
122502
  return (text.match(/\r\n|\r|\n/g) || []).length + 1;
121835
122503
  }
@@ -121886,7 +122554,7 @@ function translatePasteForImages(paste) {
121886
122554
  const id = allocateImage({
121887
122555
  data: b64,
121888
122556
  mediaType: mt,
121889
- filename: basename9(filePath)
122557
+ filename: basename10(filePath)
121890
122558
  });
121891
122559
  s = `[Image #${id}]`;
121892
122560
  }
@@ -123493,7 +124161,7 @@ __export(exports_custom, {
123493
124161
  });
123494
124162
  import { existsSync as existsSync36 } from "node:fs";
123495
124163
  import { readdir as readdir10, readFile as readFile14 } from "node:fs/promises";
123496
- import { basename as basename10, dirname as dirname17, join as join44 } from "node:path";
124164
+ import { basename as basename11, dirname as dirname17, join as join44 } from "node:path";
123497
124165
  async function getCustomCommands() {
123498
124166
  if (cachedCommands !== null) {
123499
124167
  return cachedCommands;
@@ -123553,7 +124221,7 @@ async function findCommandFiles(currentPath, rootPath, commands2, source2) {
123553
124221
  async function parseCommandFile(filePath, rootPath, source2) {
123554
124222
  const content = await readFile14(filePath, "utf-8");
123555
124223
  const { frontmatter, body } = parseFrontmatter(content);
123556
- const id = basename10(filePath, ".md");
124224
+ const id = basename11(filePath, ".md");
123557
124225
  const relativePath = dirname17(filePath).slice(rootPath.length);
123558
124226
  const namespace = relativePath.replace(/^[/\\]/, "") || undefined;
123559
124227
  let description = getStringField(frontmatter, "description");
@@ -143554,6 +144222,10 @@ function App2({
143554
144222
  import_react104.useEffect(() => {
143555
144223
  conversationIdRef.current = conversationId;
143556
144224
  }, [conversationId]);
144225
+ const setConversationIdAndRef = import_react104.useCallback((nextConversationId) => {
144226
+ conversationIdRef.current = nextConversationId;
144227
+ setConversationId3(nextConversationId);
144228
+ }, []);
143557
144229
  const pendingTranscriptStartLineIndexRef = import_react104.useRef(null);
143558
144230
  const lastRunIdRef = import_react104.useRef(null);
143559
144231
  const resumeKey = useSuspend();
@@ -143577,10 +144249,9 @@ function App2({
143577
144249
  import_react104.useEffect(() => {
143578
144250
  if (initialConversationId !== prevInitialConversationIdRef.current) {
143579
144251
  prevInitialConversationIdRef.current = initialConversationId;
143580
- conversationIdRef.current = initialConversationId;
143581
- setConversationId3(initialConversationId);
144252
+ setConversationIdAndRef(initialConversationId);
143582
144253
  }
143583
- }, [initialConversationId]);
144254
+ }, [initialConversationId, setConversationIdAndRef]);
143584
144255
  import_react104.useEffect(() => {
143585
144256
  if (agentId) {
143586
144257
  setCurrentAgentId(agentId);
@@ -147071,7 +147742,7 @@ ${feedback}
147071
147742
  const client = await getClient();
147072
147743
  const resumeData = await getResumeData2(client, agentState, conversationId2);
147073
147744
  await maybeCarryOverActiveConversationModel(conversationId2);
147074
- setConversationId3(conversationId2);
147745
+ setConversationIdAndRef(conversationId2);
147075
147746
  pendingConversationSwitchRef.current = {
147076
147747
  origin: "fork",
147077
147748
  conversationId: conversationId2,
@@ -147136,6 +147807,7 @@ ${feedback}
147136
147807
  runEndHooks,
147137
147808
  maybeCarryOverActiveConversationModel,
147138
147809
  resetBootstrapReminderState,
147810
+ setConversationIdAndRef,
147139
147811
  setCommandRunning,
147140
147812
  setStreaming,
147141
147813
  recoverRestoredPendingApprovals,
@@ -147188,7 +147860,7 @@ ${feedback}
147188
147860
  setLlmConfig(agent.llm_config);
147189
147861
  const agentModelHandle = getPreferredAgentModelHandle2(agent);
147190
147862
  setCurrentModelHandle(agentModelHandle);
147191
- setConversationId3(targetConversationId);
147863
+ setConversationIdAndRef(targetConversationId);
147192
147864
  resetBootstrapReminderState();
147193
147865
  {
147194
147866
  const { getModelDisplayName: getModelDisplayName3 } = await Promise.resolve().then(() => (init_model2(), exports_model2));
@@ -147241,7 +147913,8 @@ ${feedback}
147241
147913
  resetDeferredToolCallCommits,
147242
147914
  resetTrajectoryBases,
147243
147915
  resetBootstrapReminderState,
147244
- resetPendingReasoningCycle
147916
+ resetPendingReasoningCycle,
147917
+ setConversationIdAndRef
147245
147918
  ]);
147246
147919
  const handleCreateNewAgent = import_react104.useCallback(async (name) => {
147247
147920
  setActiveOverlay(null);
@@ -147309,7 +147982,7 @@ ${feedback}
147309
147982
  setLlmConfig(agent.llm_config);
147310
147983
  const agentModelHandle = getPreferredAgentModelHandle2(agent);
147311
147984
  setCurrentModelHandle(agentModelHandle);
147312
- setConversationId3(targetConversationId);
147985
+ setConversationIdAndRef(targetConversationId);
147313
147986
  pendingConversationSwitchRef.current = {
147314
147987
  origin: "agent-switch",
147315
147988
  conversationId: targetConversationId,
@@ -147343,7 +148016,8 @@ ${feedback}
147343
148016
  setCommandRunning,
147344
148017
  resetDeferredToolCallCommits,
147345
148018
  resetTrajectoryBases,
147346
- resetBootstrapReminderState
148019
+ resetBootstrapReminderState,
148020
+ setConversationIdAndRef
147347
148021
  ]);
147348
148022
  const handleBashSubmit = import_react104.useCallback(async (command) => {
147349
148023
  if (bashRunning)
@@ -148284,7 +148958,7 @@ Type your task to begin the loop.`, true);
148284
148958
  hasSetConversationSummaryRef.current = true;
148285
148959
  }
148286
148960
  await maybeCarryOverActiveConversationModel(conversation.id);
148287
- setConversationId3(conversation.id);
148961
+ setConversationIdAndRef(conversation.id);
148288
148962
  pendingConversationSwitchRef.current = {
148289
148963
  origin: "new",
148290
148964
  conversationId: conversation.id,
@@ -148329,7 +149003,7 @@ Type your task to begin the loop.`, true);
148329
149003
  hasSetConversationSummaryRef.current = true;
148330
149004
  }
148331
149005
  await maybeCarryOverActiveConversationModel(forked.id);
148332
- setConversationId3(forked.id);
149006
+ setConversationIdAndRef(forked.id);
148333
149007
  pendingConversationSwitchRef.current = {
148334
149008
  origin: "fork",
148335
149009
  conversationId: forked.id,
@@ -148383,7 +149057,7 @@ Type your task to begin the loop.`, true);
148383
149057
  isolated_block_labels: [...ISOLATED_BLOCK_LABELS]
148384
149058
  });
148385
149059
  await maybeCarryOverActiveConversationModel(conversation.id);
148386
- setConversationId3(conversation.id);
149060
+ setConversationIdAndRef(conversation.id);
148387
149061
  pendingConversationSwitchRef.current = {
148388
149062
  origin: "clear",
148389
149063
  conversationId: conversation.id,
@@ -148634,7 +149308,7 @@ Type your task to begin the loop.`, true);
148634
149308
  if (agentState) {
148635
149309
  const client = await getClient();
148636
149310
  const resumeData = await getResumeData2(client, agentState, targetConvId);
148637
- setConversationId3(targetConvId);
149311
+ setConversationIdAndRef(targetConvId);
148638
149312
  pendingConversationSwitchRef.current = {
148639
149313
  origin: "resume-direct",
148640
149314
  conversationId: targetConvId,
@@ -149569,7 +150243,7 @@ ${SYSTEM_REMINDER_CLOSE}
149569
150243
  name: agentName,
149570
150244
  description: agentDescription,
149571
150245
  lastRunAt: agentLastRunAt,
149572
- conversationId
150246
+ conversationId: conversationIdRef.current
149573
150247
  },
149574
150248
  state: sharedReminderStateRef.current,
149575
150249
  sessionContextReminderEnabled,
@@ -150022,6 +150696,7 @@ ${SYSTEM_REMINDER_CLOSE}
150022
150696
  agentName,
150023
150697
  agentDescription,
150024
150698
  agentLastRunAt,
150699
+ conversationId,
150025
150700
  commandRunner,
150026
150701
  handleExit,
150027
150702
  isExecutingTool,
@@ -150040,7 +150715,8 @@ ${SYSTEM_REMINDER_CLOSE}
150040
150715
  resetTrajectoryBases,
150041
150716
  sessionContextReminderEnabled,
150042
150717
  appendTaskNotificationEvents,
150043
- maybeCarryOverActiveConversationModel
150718
+ maybeCarryOverActiveConversationModel,
150719
+ setConversationIdAndRef
150044
150720
  ]);
150045
150721
  const onSubmitRef = import_react104.useRef(onSubmit);
150046
150722
  import_react104.useEffect(() => {
@@ -151040,7 +151716,7 @@ ${guidance}`);
151040
151716
  const client = await getClient();
151041
151717
  if (agentState) {
151042
151718
  const resumeData = await getResumeData2(client, agentState, action.conversationId);
151043
- setConversationId3(action.conversationId);
151719
+ setConversationIdAndRef(action.conversationId);
151044
151720
  pendingConversationSwitchRef.current = {
151045
151721
  origin: "resume-selector",
151046
151722
  conversationId: action.conversationId,
@@ -151093,7 +151769,8 @@ ${guidance}`);
151093
151769
  commandRunner.getHandle,
151094
151770
  commandRunner.start,
151095
151771
  recoverRestoredPendingApprovals,
151096
- resetBootstrapReminderState
151772
+ resetBootstrapReminderState,
151773
+ setConversationIdAndRef
151097
151774
  ]);
151098
151775
  const handleFeedbackSubmit = import_react104.useCallback(async (message) => {
151099
151776
  const overlayCommand = consumeOverlayCommand("feedback");
@@ -152351,7 +153028,7 @@ If using apply_patch, use this exact relative patch path: ${applyPatchRelativePa
152351
153028
  if (agentState) {
152352
153029
  const client = await getClient();
152353
153030
  const resumeData = await getResumeData2(client, agentState, convId);
152354
- setConversationId3(convId);
153031
+ setConversationIdAndRef(convId);
152355
153032
  pendingConversationSwitchRef.current = {
152356
153033
  origin: "resume-selector",
152357
153034
  conversationId: convId,
@@ -152463,7 +153140,7 @@ If using apply_patch, use this exact relative patch path: ${applyPatchRelativePa
152463
153140
  isolated_block_labels: [...ISOLATED_BLOCK_LABELS]
152464
153141
  });
152465
153142
  await maybeCarryOverActiveConversationModel(conversation.id);
152466
- setConversationId3(conversation.id);
153143
+ setConversationIdAndRef(conversation.id);
152467
153144
  settingsManager.persistSession(agentId, conversation.id);
152468
153145
  const currentAgentName = agentState?.name || "Unnamed Agent";
152469
153146
  const shortConvId = conversation.id.slice(0, 20);
@@ -152547,7 +153224,7 @@ If using apply_patch, use this exact relative patch path: ${applyPatchRelativePa
152547
153224
  if (agentState) {
152548
153225
  const client = await getClient();
152549
153226
  const resumeData = await getResumeData2(client, agentState, actualTargetConv);
152550
- setConversationId3(actualTargetConv);
153227
+ setConversationIdAndRef(actualTargetConv);
152551
153228
  pendingConversationSwitchRef.current = {
152552
153229
  origin: "search",
152553
153230
  conversationId: actualTargetConv,
@@ -153919,10 +154596,12 @@ var exports_memoryFilesystem2 = {};
153919
154596
  __export(exports_memoryFilesystem2, {
153920
154597
  renderMemoryFilesystemTree: () => renderMemoryFilesystemTree2,
153921
154598
  labelFromRelativePath: () => labelFromRelativePath2,
154599
+ isMemfsEnabledOnServer: () => isMemfsEnabledOnServer2,
153922
154600
  isLettaCloud: () => isLettaCloud2,
153923
154601
  getMemorySystemDir: () => getMemorySystemDir2,
153924
154602
  getMemoryFilesystemRoot: () => getMemoryFilesystemRoot2,
153925
154603
  ensureMemoryFilesystemDirs: () => ensureMemoryFilesystemDirs2,
154604
+ ensureLocalMemfsCheckout: () => ensureLocalMemfsCheckout2,
153926
154605
  enableMemfsIfCloud: () => enableMemfsIfCloud2,
153927
154606
  applyMemfsFlags: () => applyMemfsFlags2,
153928
154607
  MEMORY_TREE_MAX_LINES: () => MEMORY_TREE_MAX_LINES2,
@@ -153952,6 +154631,23 @@ function ensureMemoryFilesystemDirs2(agentId, homeDir = homedir41()) {
153952
154631
  mkdirSync30(systemDir, { recursive: true });
153953
154632
  }
153954
154633
  }
154634
+ async function isMemfsEnabledOnServer2(agentId) {
154635
+ const { getClient: getClient3 } = await Promise.resolve().then(() => (init_client2(), exports_client));
154636
+ const client = await getClient3();
154637
+ const agent = await client.agents.retrieve(agentId);
154638
+ const { GIT_MEMORY_ENABLED_TAG: GIT_MEMORY_ENABLED_TAG2 } = await Promise.resolve().then(() => (init_memoryGit(), exports_memoryGit));
154639
+ const enabled = agent.tags?.includes(GIT_MEMORY_ENABLED_TAG2) ?? false;
154640
+ const { settingsManager: settingsManager3 } = await Promise.resolve().then(() => (init_settings_manager(), exports_settings_manager));
154641
+ settingsManager3.setMemfsEnabled(agentId, enabled);
154642
+ return enabled;
154643
+ }
154644
+ async function ensureLocalMemfsCheckout2(agentId) {
154645
+ const { isGitRepo: isGitRepo2, cloneMemoryRepo: cloneMemoryRepo2 } = await Promise.resolve().then(() => (init_memoryGit(), exports_memoryGit));
154646
+ if (isGitRepo2(agentId)) {
154647
+ return;
154648
+ }
154649
+ await cloneMemoryRepo2(agentId);
154650
+ }
153955
154651
  function labelFromRelativePath2(relativePath) {
153956
154652
  const normalized = relativePath.replace(/\\/g, "/");
153957
154653
  return normalized.replace(/\.md$/, "");
@@ -163181,4 +163877,4 @@ Error during initialization: ${message}`);
163181
163877
  }
163182
163878
  main();
163183
163879
 
163184
- //# debugId=29DBF7A2819CB6C064756E2164756E21
163880
+ //# debugId=E826850D234138B664756E2164756E21