@letta-ai/letta-code 0.30.10 → 0.30.11

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 (36) hide show
  1. package/dist/channels-public.js +411 -2
  2. package/dist/channels-public.js.map +6 -3
  3. package/dist/channels-slack.js +209 -11
  4. package/dist/channels-slack.js.map +6 -5
  5. package/dist/gateway-core.js +110 -13
  6. package/dist/gateway-core.js.map +6 -5
  7. package/dist/mcp-client.js +2 -2
  8. package/dist/mcp-client.js.map +1 -1
  9. package/dist/schedules.js +4 -2
  10. package/dist/schedules.js.map +3 -3
  11. package/dist/types/agent/client-skills.d.ts +2 -0
  12. package/dist/types/agent/client-skills.d.ts.map +1 -1
  13. package/dist/types/agent/message.d.ts.map +1 -1
  14. package/dist/types/backend/local/local-store.d.ts.map +1 -1
  15. package/dist/types/channels/command-surface.d.ts +54 -0
  16. package/dist/types/channels/command-surface.d.ts.map +1 -0
  17. package/dist/types/channels/gateway-core.d.ts +2 -1
  18. package/dist/types/channels/gateway-core.d.ts.map +1 -1
  19. package/dist/types/channels/message-channel-executor.d.ts +2 -0
  20. package/dist/types/channels/message-channel-executor.d.ts.map +1 -1
  21. package/dist/types/channels/message-channel-idempotency.d.ts +13 -0
  22. package/dist/types/channels/message-channel-idempotency.d.ts.map +1 -0
  23. package/dist/types/channels-public.d.ts +4 -0
  24. package/dist/types/channels-public.d.ts.map +1 -1
  25. package/dist/types/channels-slack.d.ts +1 -0
  26. package/dist/types/channels-slack.d.ts.map +1 -1
  27. package/dist/types/cron/scheduled-task-prompt.d.ts +7 -0
  28. package/dist/types/cron/scheduled-task-prompt.d.ts.map +1 -1
  29. package/dist/types/schedules.d.ts +1 -1
  30. package/dist/types/schedules.d.ts.map +1 -1
  31. package/dist/types/websocket/listener/runtime.d.ts.map +1 -1
  32. package/dist/types/websocket/listener/types.d.ts +2 -1
  33. package/dist/types/websocket/listener/types.d.ts.map +1 -1
  34. package/letta.js +388 -226
  35. package/package.json +1 -1
  36. package/scripts/source-file-size-baseline.json +1 -1
package/letta.js CHANGED
@@ -5476,7 +5476,7 @@ var package_default;
5476
5476
  var init_package = __esm(() => {
5477
5477
  package_default = {
5478
5478
  name: "@letta-ai/letta-code",
5479
- version: "0.30.10",
5479
+ version: "0.30.11",
5480
5480
  description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
5481
5481
  type: "module",
5482
5482
  packageManager: "bun@1.3.0",
@@ -152976,6 +152976,7 @@ function createConversationRuntime(listener, agentId, conversationId) {
152976
152976
  currentToolset: null,
152977
152977
  currentToolsetPreference: "auto",
152978
152978
  currentLoadedTools: [],
152979
+ currentAvailableSkills: [],
152979
152980
  pendingApprovalBatchByToolCallId: new Map,
152980
152981
  approvalMessageIdByToolCallId: new Map,
152981
152982
  pendingInterruptedResults: null,
@@ -157473,6 +157474,7 @@ function getSkillRootRevisions(components) {
157473
157474
  function cloneResult(result) {
157474
157475
  return {
157475
157476
  clientSkills: result.clientSkills.map((s2) => ({ ...s2 })),
157477
+ availableSkills: result.availableSkills.map((skill) => ({ ...skill })),
157476
157478
  skillPathById: { ...result.skillPathById },
157477
157479
  errors: result.errors.map((e2) => ({ ...e2 }))
157478
157480
  };
@@ -157657,6 +157659,13 @@ async function buildClientSkillsPayload(options3 = {}) {
157657
157659
  }
157658
157660
  const result = {
157659
157661
  clientSkills: sortedSkills.map(toClientSkill),
157662
+ availableSkills: sortedSkills.map((skill) => ({
157663
+ id: skill.id,
157664
+ name: skill.name,
157665
+ description: skill.description,
157666
+ path: skill.path,
157667
+ source: skill.source
157668
+ })),
157660
157669
  skillPathById: Object.fromEntries(sortedSkills.filter((skill) => typeof skill.path === "string" && skill.path.length > 0).map((skill) => [skill.id, skill.path])),
157661
157670
  errors: errors7
157662
157671
  };
@@ -157798,6 +157807,7 @@ async function sendMessageStreamWithBackend(backend, conversationId, messages, o
157798
157807
  const { clientTools, contextId } = preparedToolContext;
157799
157808
  const { clientSkills, errors: clientSkillDiscoveryErrors } = await buildClientSkillsPayload({
157800
157809
  agentId: opts.agentId,
157810
+ skillsDirectory: getExecutionContextById(preparedToolContext.contextId)?.runtimeContext.skillsDirectory,
157801
157811
  skillSources: opts.skillSources ?? getSkillSources()
157802
157812
  });
157803
157813
  const resolvedConversationId = conversationId;
@@ -330261,7 +330271,7 @@ function buildDeviceStatus(runtime, params) {
330261
330271
  current_toolset: conversationRuntime?.currentToolset ?? (toolsetPreference === "auto" ? null : toolsetPreference),
330262
330272
  current_toolset_preference: conversationRuntime?.currentToolsetPreference ?? toolsetPreference,
330263
330273
  current_loaded_tools: conversationRuntime?.currentLoadedTools ?? [],
330264
- current_available_skills: [],
330274
+ current_available_skills: conversationRuntime?.currentAvailableSkills ?? [],
330265
330275
  background_processes: buildBackgroundProcessSnapshot(scopedAgentId, scopedConversationId),
330266
330276
  pending_control_requests: interruptedCacheActive ? [] : getPendingControlRequests(listener, scope),
330267
330277
  experiments: experimentManager.list(),
@@ -356536,7 +356546,8 @@ function createLocalConversationRecord(conversationId, agentId, _sequence, body
356536
356546
  } : {},
356537
356547
  ...modelSettings !== undefined ? { model_settings: modelSettings } : {},
356538
356548
  ...typeof bodyRecord.context_window_limit === "number" ? { context_window_limit: bodyRecord.context_window_limit } : {},
356539
- ...typeof bodyRecord.hidden === "boolean" ? { hidden: bodyRecord.hidden } : {}
356549
+ ...typeof bodyRecord.hidden === "boolean" ? { hidden: bodyRecord.hidden } : {},
356550
+ ...isStringArray2(bodyRecord.tags) ? { tags: bodyRecord.tags } : {}
356540
356551
  };
356541
356552
  }
356542
356553
  function updateLocalConversationRecord(current, body, updatedAt) {
@@ -411956,6 +411967,217 @@ var init_access_control = __esm(() => {
411956
411967
  CHANNEL_COMMAND_FLOOR = ["help", "status", "whoami"];
411957
411968
  });
411958
411969
 
411970
+ // src/channels/command-surface.ts
411971
+ function defaultChannelDisplayName(channelId) {
411972
+ return DEFAULT_CHANNEL_DISPLAY_NAMES[channelId] ?? channelId;
411973
+ }
411974
+ function listChannelSlashCommands() {
411975
+ return CHANNEL_SLASH_COMMANDS.map((definition) => ({
411976
+ ...definition,
411977
+ aliases: definition.aliases ? [...definition.aliases] : undefined
411978
+ }));
411979
+ }
411980
+ function parseSingleLineChannelCommand(text2, prefix) {
411981
+ const trimmed = text2.trim();
411982
+ const escapedPrefix = prefix === "/" ? "\\/" : "!";
411983
+ const match4 = trimmed.match(new RegExp(`^${escapedPrefix}([A-Za-z][\\w-]*)(?:@[A-Za-z0-9_]+)?(?:[^\\S\\r\\n]+(.*))?$`));
411984
+ if (!match4) {
411985
+ return null;
411986
+ }
411987
+ const [, name, args] = match4;
411988
+ if (!name) {
411989
+ return null;
411990
+ }
411991
+ return {
411992
+ name: name.toLowerCase(),
411993
+ args: args?.trim() ?? "",
411994
+ raw: trimmed
411995
+ };
411996
+ }
411997
+ function parseAnySingleLineChannelCommand(text2) {
411998
+ return parseSingleLineChannelCommand(text2, "/") ?? parseSingleLineChannelCommand(text2, "!");
411999
+ }
412000
+ function parseChannelCommand(text2, prefix) {
412001
+ const lines = text2.trim().split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
412002
+ const [firstLine, ...remainingLines] = lines;
412003
+ if (!firstLine) {
412004
+ return null;
412005
+ }
412006
+ const firstCommand = parseSingleLineChannelCommand(firstLine, prefix);
412007
+ if (!firstCommand) {
412008
+ return null;
412009
+ }
412010
+ const laterCommand = remainingLines.find((line) => Boolean(parseAnySingleLineChannelCommand(line)));
412011
+ if (laterCommand) {
412012
+ return firstCommand;
412013
+ }
412014
+ const continuationArgs = remainingLines.join(`
412015
+ `).trim();
412016
+ if (!continuationArgs) {
412017
+ return firstCommand;
412018
+ }
412019
+ return {
412020
+ ...firstCommand,
412021
+ args: [firstCommand.args, continuationArgs].filter((part) => part.length > 0).join(`
412022
+ `)
412023
+ };
412024
+ }
412025
+ function parseChannelSlashCommand(text2) {
412026
+ return parseChannelCommand(text2, "/");
412027
+ }
412028
+ function parseChannelBangCommand(text2) {
412029
+ return parseChannelCommand(text2, "!");
412030
+ }
412031
+ function supportedCommandsText(prefix = "/") {
412032
+ return listChannelSlashCommands().map((definition) => `${prefix}${definition.name}`).join(", ");
412033
+ }
412034
+ function supportedSlackMentionSlashCommandsText() {
412035
+ return SLACK_MENTION_SLASH_COMMAND_EXAMPLES.join(", ");
412036
+ }
412037
+ function supportedBangCommandsText() {
412038
+ return SLACK_MENTION_COMMAND_NAMES.map((name) => `!${name}`).join(", ");
412039
+ }
412040
+ function isSupportedSlackMentionCommand(commandName) {
412041
+ return SLACK_MENTION_COMMAND_NAMES.includes(commandName);
412042
+ }
412043
+ function buildChannelHelpMessage(channelId, resolveDisplayName = defaultChannelDisplayName) {
412044
+ const displayName = resolveDisplayName(channelId);
412045
+ if (channelId === "slack") {
412046
+ return [
412047
+ `${displayName} is connected to Letta Code.`,
412048
+ "Talk by mentioning the app in a channel thread. Once a thread is routed, normal replies continue the same agent conversation until detached.",
412049
+ "Control commands start immediately after the mention:",
412050
+ "@agent /model - show this thread's current model",
412051
+ "@agent /model list - show available models",
412052
+ "@agent /model <handle-or-id> - switch this thread's model",
412053
+ "@agent /status - show route and listener status",
412054
+ "@agent /cancel - cancel the current turn",
412055
+ "@agent /chat - show the web chat link",
412056
+ "@agent /feedback <message> - send feedback to the Letta team from this routed thread",
412057
+ "@agent /reflection - start a memory reflection pass",
412058
+ "@agent /detach - stop replying in this thread until mentioned again",
412059
+ "@agent /new - start a fresh conversation for this thread",
412060
+ "@agent /reload - reload settings, local mods, and agent secrets",
412061
+ `Legacy bang aliases still work after a mention: ${supportedBangCommandsText()}.`,
412062
+ "If this chat is not connected yet, send a normal message and follow the pairing instructions."
412063
+ ].join(`
412064
+ `);
412065
+ }
412066
+ return [
412067
+ `${displayName} is connected to Letta Code.`,
412068
+ "Send a normal message here and the connected agent will reply in this chat.",
412069
+ `Supported slash commands here: ${supportedCommandsText()}.`,
412070
+ "If this chat is not connected yet, send any non-command message and follow the pairing instructions."
412071
+ ].join(`
412072
+
412073
+ `);
412074
+ }
412075
+ function buildUnsupportedChannelCommandMessage(channelId, command, resolveDisplayName = defaultChannelDisplayName) {
412076
+ const displayName = resolveDisplayName(channelId);
412077
+ const isBang = command.raw.startsWith("!");
412078
+ const commandKind = isBang ? "bang" : "slash";
412079
+ const supportedCommands = isBang ? supportedBangCommandsText() : channelId === "slack" ? supportedSlackMentionSlashCommandsText() : supportedCommandsText();
412080
+ const supportedLabel = channelId === "slack" && !isBang ? "Slack mention commands" : `${commandKind} commands`;
412081
+ return [
412082
+ `${displayName} received ${command.raw}, but that ${commandKind} command is not supported in channels yet.`,
412083
+ `Supported ${supportedLabel}: ${supportedCommands}.`,
412084
+ `Send normal messages without a leading ${isBang ? "bang" : "slash"} command to talk to the connected agent.`
412085
+ ].join(`
412086
+
412087
+ `);
412088
+ }
412089
+ var DEFAULT_CHANNEL_DISPLAY_NAMES, CHANNEL_SLASH_COMMANDS, SLACK_MENTION_COMMAND_NAMES, SLACK_MENTION_SLASH_COMMAND_EXAMPLES;
412090
+ var init_command_surface = __esm(() => {
412091
+ DEFAULT_CHANNEL_DISPLAY_NAMES = {
412092
+ custom: "Custom",
412093
+ discord: "Discord",
412094
+ signal: "Signal",
412095
+ slack: "Slack",
412096
+ telegram: "Telegram",
412097
+ whatsapp: "WhatsApp"
412098
+ };
412099
+ CHANNEL_SLASH_COMMANDS = [
412100
+ {
412101
+ name: "help",
412102
+ kind: "direct",
412103
+ summary: "Show channel usage guidance."
412104
+ },
412105
+ {
412106
+ name: "status",
412107
+ kind: "direct",
412108
+ summary: "Show this chat's channel connection status."
412109
+ },
412110
+ {
412111
+ name: "whoami",
412112
+ kind: "direct",
412113
+ summary: "Show your access tier and runnable commands here."
412114
+ },
412115
+ {
412116
+ name: "pause",
412117
+ kind: "direct",
412118
+ summary: "Pause agent routing for this chat."
412119
+ },
412120
+ {
412121
+ name: "resume",
412122
+ kind: "direct",
412123
+ summary: "Resume agent routing for this chat."
412124
+ },
412125
+ {
412126
+ name: "cancel",
412127
+ kind: "agent-scoped",
412128
+ summary: "Cancel the in-progress agent turn for this chat."
412129
+ },
412130
+ {
412131
+ name: "chat",
412132
+ kind: "direct",
412133
+ summary: "Show the Letta web chat link for this channel route."
412134
+ },
412135
+ {
412136
+ name: "feedback",
412137
+ kind: "direct",
412138
+ summary: "Send feedback about Letta Code from this routed chat."
412139
+ },
412140
+ {
412141
+ name: "model",
412142
+ kind: "agent-scoped",
412143
+ summary: "Show, list, or switch the model for this chat's routed conversation."
412144
+ },
412145
+ {
412146
+ name: "reflection",
412147
+ aliases: ["reflect"],
412148
+ kind: "agent-scoped",
412149
+ summary: "Start a memory reflection pass for this conversation."
412150
+ },
412151
+ {
412152
+ name: "reload",
412153
+ kind: "agent-scoped",
412154
+ summary: "Reload settings, local mods, and agent secrets."
412155
+ }
412156
+ ];
412157
+ SLACK_MENTION_COMMAND_NAMES = [
412158
+ "help",
412159
+ "detach",
412160
+ "model",
412161
+ "new",
412162
+ "reload"
412163
+ ];
412164
+ SLACK_MENTION_SLASH_COMMAND_EXAMPLES = [
412165
+ "@agent /help",
412166
+ "@agent /status",
412167
+ "@agent /whoami",
412168
+ "@agent /model",
412169
+ "@agent /model list",
412170
+ "@agent /model <handle-or-id>",
412171
+ "@agent /cancel",
412172
+ "@agent /chat",
412173
+ "@agent /feedback <message>",
412174
+ "@agent /reflection",
412175
+ "@agent /detach",
412176
+ "@agent /new",
412177
+ "@agent /reload"
412178
+ ];
412179
+ });
412180
+
411959
412181
  // src/channels/feedback.ts
411960
412182
  function channelDisplayName2(channelId) {
411961
412183
  try {
@@ -412224,126 +412446,17 @@ function channelDisplayName4(channelId) {
412224
412446
  return channelId;
412225
412447
  }
412226
412448
  }
412227
- function listChannelSlashCommands() {
412228
- return CHANNEL_SLASH_COMMANDS.map((definition) => ({
412229
- ...definition,
412230
- aliases: definition.aliases ? [...definition.aliases] : undefined
412231
- }));
412232
- }
412233
- function parseSingleLineChannelCommand(text2, prefix) {
412234
- const trimmed = text2.trim();
412235
- const escapedPrefix = prefix === "/" ? "\\/" : "!";
412236
- const match4 = trimmed.match(new RegExp(`^${escapedPrefix}([A-Za-z][\\w-]*)(?:@[A-Za-z0-9_]+)?(?:[^\\S\\r\\n]+(.*))?$`));
412237
- if (!match4) {
412238
- return null;
412239
- }
412240
- const [, name, args] = match4;
412241
- if (!name) {
412242
- return null;
412243
- }
412244
- return {
412245
- name: name.toLowerCase(),
412246
- args: args?.trim() ?? "",
412247
- raw: trimmed
412248
- };
412249
- }
412250
- function parseAnySingleLineChannelCommand(text2) {
412251
- return parseSingleLineChannelCommand(text2, "/") ?? parseSingleLineChannelCommand(text2, "!");
412252
- }
412253
- function parseChannelCommand(text2, prefix) {
412254
- const lines = text2.trim().split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
412255
- const [firstLine, ...remainingLines] = lines;
412256
- if (!firstLine) {
412257
- return null;
412258
- }
412259
- const firstCommand = parseSingleLineChannelCommand(firstLine, prefix);
412260
- if (!firstCommand) {
412261
- return null;
412262
- }
412263
- const laterCommand = remainingLines.find((line) => Boolean(parseAnySingleLineChannelCommand(line)));
412264
- if (laterCommand) {
412265
- return firstCommand;
412266
- }
412267
- const continuationArgs = remainingLines.join(`
412268
- `).trim();
412269
- if (!continuationArgs) {
412270
- return firstCommand;
412271
- }
412272
- return {
412273
- ...firstCommand,
412274
- args: [firstCommand.args, continuationArgs].filter((part) => part.length > 0).join(`
412275
- `)
412276
- };
412277
- }
412278
- function parseChannelSlashCommand(text2) {
412279
- return parseChannelCommand(text2, "/");
412280
- }
412281
- function parseChannelBangCommand(text2) {
412282
- return parseChannelCommand(text2, "!");
412283
- }
412284
- function supportedCommandsText(prefix = "/") {
412285
- return listChannelSlashCommands().map((definition) => `${prefix}${definition.name}`).join(", ");
412286
- }
412287
- function supportedSlackMentionSlashCommandsText() {
412288
- return SLACK_MENTION_SLASH_COMMAND_EXAMPLES.join(", ");
412289
- }
412290
- function supportedBangCommandsText() {
412291
- return SLACK_MENTION_COMMAND_NAMES.map((name) => `!${name}`).join(", ");
412292
- }
412293
- function isSupportedSlackMentionCommand(commandName) {
412294
- return SLACK_MENTION_COMMAND_NAMES.includes(commandName);
412295
- }
412296
412449
  function isSlackMentionSlashCommand(msg, command) {
412297
412450
  return msg.channel === "slack" && msg.isMention === true && command.raw.startsWith("/");
412298
412451
  }
412299
412452
  function isSlackMentionControlCommand(msg, command) {
412300
412453
  return command.raw.startsWith("!") || isSlackMentionSlashCommand(msg, command);
412301
412454
  }
412302
- function buildChannelHelpMessage(channelId) {
412303
- const displayName = channelDisplayName4(channelId);
412304
- if (channelId === "slack") {
412305
- return [
412306
- `${displayName} is connected to Letta Code.`,
412307
- "Talk by mentioning the app in a channel thread. Once a thread is routed, normal replies continue the same agent conversation until detached.",
412308
- "Control commands start immediately after the mention:",
412309
- "@agent /model - show this thread's current model",
412310
- "@agent /model list - show available models",
412311
- "@agent /model <handle-or-id> - switch this thread's model",
412312
- "@agent /status - show route and listener status",
412313
- "@agent /cancel - cancel the current turn",
412314
- "@agent /chat - show the web chat link",
412315
- "@agent /feedback <message> - send feedback to the Letta team from this routed thread",
412316
- "@agent /reflection - start a memory reflection pass",
412317
- "@agent /detach - stop replying in this thread until mentioned again",
412318
- "@agent /new - start a fresh conversation for this thread",
412319
- "@agent /reload - reload settings, local mods, and agent secrets",
412320
- `Legacy bang aliases still work after a mention: ${supportedBangCommandsText()}.`,
412321
- "If this chat is not connected yet, send a normal message and follow the pairing instructions."
412322
- ].join(`
412323
- `);
412324
- }
412325
- return [
412326
- `${displayName} is connected to Letta Code.`,
412327
- "Send a normal message here and the connected agent will reply in this chat.",
412328
- `Supported slash commands here: ${supportedCommandsText()}.`,
412329
- "If this chat is not connected yet, send any non-command message and follow the pairing instructions."
412330
- ].join(`
412331
-
412332
- `);
412455
+ function buildChannelHelpMessage2(channelId) {
412456
+ return buildChannelHelpMessage(channelId, channelDisplayName4);
412333
412457
  }
412334
- function buildUnsupportedChannelCommandMessage(channelId, command) {
412335
- const displayName = channelDisplayName4(channelId);
412336
- const isBang = command.raw.startsWith("!");
412337
- const commandKind = isBang ? "bang" : "slash";
412338
- const supportedCommands = isBang ? supportedBangCommandsText() : channelId === "slack" ? supportedSlackMentionSlashCommandsText() : supportedCommandsText();
412339
- const supportedLabel = channelId === "slack" && !isBang ? "Slack mention commands" : `${commandKind} commands`;
412340
- return [
412341
- `${displayName} received ${command.raw}, but that ${commandKind} command is not supported in channels yet.`,
412342
- `Supported ${supportedLabel}: ${supportedCommands}.`,
412343
- `Send normal messages without a leading ${isBang ? "bang" : "slash"} command to talk to the connected agent.`
412344
- ].join(`
412345
-
412346
- `);
412458
+ function buildUnsupportedChannelCommandMessage2(channelId, command) {
412459
+ return buildUnsupportedChannelCommandMessage(channelId, command, channelDisplayName4);
412347
412460
  }
412348
412461
  function buildChannelStatusMessage(msg, context3) {
412349
412462
  const displayName = channelDisplayName4(msg.channel);
@@ -412622,7 +412735,7 @@ async function tryHandleChannelSlashCommand(adapter, msg, options3 = {}) {
412622
412735
  const isBangCommand = command.raw.startsWith("!");
412623
412736
  const isSlackMentionControl = isSlackMentionControlCommand(msg, command);
412624
412737
  if (isBangCommand && !isSupportedSlackMentionCommand(command.name)) {
412625
- await adapter.sendDirectReply(msg.chatId, buildUnsupportedChannelCommandMessage(msg.channel, command), buildDirectReplyOptions(msg));
412738
+ await adapter.sendDirectReply(msg.chatId, buildUnsupportedChannelCommandMessage2(msg.channel, command), buildDirectReplyOptions(msg));
412626
412739
  return true;
412627
412740
  }
412628
412741
  const canonicalName = canonicalizeChannelCommandName(command.name);
@@ -412633,7 +412746,7 @@ async function tryHandleChannelSlashCommand(adapter, msg, options3 = {}) {
412633
412746
  const reply = normalizeDirectReplyPayload(await (async () => {
412634
412747
  switch (command.name) {
412635
412748
  case "help":
412636
- return buildChannelHelpMessage(msg.channel);
412749
+ return buildChannelHelpMessage2(msg.channel);
412637
412750
  case "whoami":
412638
412751
  return buildChannelWhoamiMessage(msg, options3.commandGate);
412639
412752
  case "status":
@@ -412675,7 +412788,7 @@ async function tryHandleChannelSlashCommand(adapter, msg, options3 = {}) {
412675
412788
  });
412676
412789
  case "detach":
412677
412790
  if (!isSlackMentionControl) {
412678
- return buildUnsupportedChannelCommandMessage(msg.channel, command);
412791
+ return buildUnsupportedChannelCommandMessage2(msg.channel, command);
412679
412792
  }
412680
412793
  return handleScopedCommand({
412681
412794
  msg,
@@ -412690,7 +412803,7 @@ async function tryHandleChannelSlashCommand(adapter, msg, options3 = {}) {
412690
412803
  });
412691
412804
  case "new":
412692
412805
  if (!isSlackMentionControl) {
412693
- return buildUnsupportedChannelCommandMessage(msg.channel, command);
412806
+ return buildUnsupportedChannelCommandMessage2(msg.channel, command);
412694
412807
  }
412695
412808
  return handleScopedCommand({
412696
412809
  msg,
@@ -412711,7 +412824,7 @@ async function tryHandleChannelSlashCommand(adapter, msg, options3 = {}) {
412711
412824
  handler: options3.handlers?.reload
412712
412825
  });
412713
412826
  default:
412714
- return buildUnsupportedChannelCommandMessage(msg.channel, command);
412827
+ return buildUnsupportedChannelCommandMessage2(msg.channel, command);
412715
412828
  }
412716
412829
  })());
412717
412830
  if (reply === null) {
@@ -412724,92 +412837,14 @@ async function tryHandleChannelSlashCommand(adapter, msg, options3 = {}) {
412724
412837
  } : undefined);
412725
412838
  return true;
412726
412839
  }
412727
- var CHANNEL_SLASH_COMMANDS, SLACK_MENTION_COMMAND_NAMES, SLACK_MENTION_SLASH_COMMAND_EXAMPLES, DEFAULT_CHANNEL_MODEL_LIST_LIMIT = 8;
412840
+ var DEFAULT_CHANNEL_MODEL_LIST_LIMIT = 8;
412728
412841
  var init_commands = __esm(() => {
412729
412842
  init_access_control();
412843
+ init_command_surface();
412730
412844
  init_feedback2();
412731
412845
  init_plugin_registry();
412732
412846
  init_registry_presentation();
412733
- CHANNEL_SLASH_COMMANDS = [
412734
- {
412735
- name: "help",
412736
- kind: "direct",
412737
- summary: "Show channel usage guidance."
412738
- },
412739
- {
412740
- name: "status",
412741
- kind: "direct",
412742
- summary: "Show this chat's channel connection status."
412743
- },
412744
- {
412745
- name: "whoami",
412746
- kind: "direct",
412747
- summary: "Show your access tier and runnable commands here."
412748
- },
412749
- {
412750
- name: "pause",
412751
- kind: "direct",
412752
- summary: "Pause agent routing for this chat."
412753
- },
412754
- {
412755
- name: "resume",
412756
- kind: "direct",
412757
- summary: "Resume agent routing for this chat."
412758
- },
412759
- {
412760
- name: "cancel",
412761
- kind: "agent-scoped",
412762
- summary: "Cancel the in-progress agent turn for this chat."
412763
- },
412764
- {
412765
- name: "chat",
412766
- kind: "direct",
412767
- summary: "Show the Letta web chat link for this channel route."
412768
- },
412769
- {
412770
- name: "feedback",
412771
- kind: "direct",
412772
- summary: "Send feedback about Letta Code from this routed chat."
412773
- },
412774
- {
412775
- name: "model",
412776
- kind: "agent-scoped",
412777
- summary: "Show, list, or switch the model for this chat's routed conversation."
412778
- },
412779
- {
412780
- name: "reflection",
412781
- aliases: ["reflect"],
412782
- kind: "agent-scoped",
412783
- summary: "Start a memory reflection pass for this conversation."
412784
- },
412785
- {
412786
- name: "reload",
412787
- kind: "agent-scoped",
412788
- summary: "Reload settings, local mods, and agent secrets."
412789
- }
412790
- ];
412791
- SLACK_MENTION_COMMAND_NAMES = [
412792
- "help",
412793
- "detach",
412794
- "model",
412795
- "new",
412796
- "reload"
412797
- ];
412798
- SLACK_MENTION_SLASH_COMMAND_EXAMPLES = [
412799
- "@agent /help",
412800
- "@agent /status",
412801
- "@agent /whoami",
412802
- "@agent /model",
412803
- "@agent /model list",
412804
- "@agent /model <handle-or-id>",
412805
- "@agent /cancel",
412806
- "@agent /chat",
412807
- "@agent /feedback <message>",
412808
- "@agent /reflection",
412809
- "@agent /detach",
412810
- "@agent /new",
412811
- "@agent /reload"
412812
- ];
412847
+ init_command_surface();
412813
412848
  });
412814
412849
 
412815
412850
  // src/channels/slack/model-picker-blocks.ts
@@ -424250,10 +424285,24 @@ var init_registry_inbound = __esm(() => {
424250
424285
  });
424251
424286
 
424252
424287
  // src/channels/registry-routes.ts
424288
+ async function resolveConversationModelPin(agentId) {
424289
+ try {
424290
+ const agent2 = await getBackend().retrieveAgent(agentId);
424291
+ return {
424292
+ ...agent2.model ? { model: agent2.model } : {},
424293
+ ...agent2.model_settings ? { model_settings: agent2.model_settings } : {}
424294
+ };
424295
+ } catch (error54) {
424296
+ debugWarn("channels", `Failed to resolve model for agent ${agentId}; creating channel conversation without a pinned model`, error54);
424297
+ return {};
424298
+ }
424299
+ }
424253
424300
  function createChannelRouteProvisioner(deps) {
424254
424301
  async function createConversationForAgent(agentId, summary) {
424302
+ const modelPin = await resolveConversationModelPin(agentId);
424255
424303
  const conversation = await getBackend().createConversation({
424256
424304
  agent_id: agentId,
424305
+ ...modelPin,
424257
424306
  ...summary ? { summary } : {}
424258
424307
  });
424259
424308
  return conversation.id;
@@ -424596,6 +424645,7 @@ function createChannelRouteProvisioner(deps) {
424596
424645
  }
424597
424646
  var init_registry_routes = __esm(() => {
424598
424647
  init_backend2();
424648
+ init_debug();
424599
424649
  init_accounts();
424600
424650
  init_registry_presentation();
424601
424651
  init_routing();
@@ -437141,7 +437191,7 @@ function formatScheduledTaskPrompt(input) {
437141
437191
  return lines.join(`
437142
437192
  `);
437143
437193
  }
437144
- var AUTONOMOUS_NOTICE = "You are running autonomously: no user is watching this turn and questions will not be answered. Deliver results through your available channels or record them in memory, and work until the task is done or genuinely blocked.";
437194
+ var SCHEDULE_ORIGIN_TAG = "origin:schedule", AUTONOMOUS_NOTICE = "You are running autonomously: no user is watching this turn and questions will not be answered. Deliver results through your available channels or record them in memory, and work until the task is done or genuinely blocked.";
437145
437195
 
437146
437196
  // src/cron/prompt.ts
437147
437197
  function getIntendedCronOccurrence(task2, matchedAt) {
@@ -437179,14 +437229,12 @@ function minuteKey(date6) {
437179
437229
  function wrapCronPrompt(task2, timing2) {
437180
437230
  return formatCronPrompt(task2, timing2);
437181
437231
  }
437182
- function getCronConversationSummary(task2) {
437183
- return `[Schedule] ${task2.name}`;
437184
- }
437185
437232
  async function resolveCronFireConversationId(task2) {
437186
437233
  if (task2.conversation_id === NEW_CONVERSATION_TARGET) {
437187
437234
  const conversation = await getBackend().createConversation({
437188
437235
  agent_id: task2.agent_id,
437189
- summary: getCronConversationSummary(task2)
437236
+ summary: task2.name,
437237
+ tags: [SCHEDULE_ORIGIN_TAG]
437190
437238
  });
437191
437239
  return conversation.id;
437192
437240
  }
@@ -459397,9 +459445,18 @@ async function prepareListenerTurn(params) {
459397
459445
  if (isInterrupted()) {
459398
459446
  return { kind: "interrupted" };
459399
459447
  }
459448
+ const availableSkills = (await buildClientSkillsPayload({
459449
+ agentId,
459450
+ skillsDirectory: listenerOptions?.skillsDirectory,
459451
+ skillSources: runtime.skillSources
459452
+ })).availableSkills;
459453
+ if (isInterrupted()) {
459454
+ return { kind: "interrupted" };
459455
+ }
459400
459456
  runtime.currentToolset = preparedToolContext.toolset;
459401
459457
  runtime.currentToolsetPreference = preparedToolContext.toolsetPreference;
459402
459458
  runtime.currentLoadedTools = preparedToolContext.preparedToolContext.loadedToolNames;
459459
+ runtime.currentAvailableSkills = availableSkills;
459403
459460
  return {
459404
459461
  kind: "ready",
459405
459462
  getCachedAgent: () => cachedAgent,
@@ -459413,6 +459470,7 @@ async function prepareListenerTurn(params) {
459413
459470
  };
459414
459471
  }
459415
459472
  var init_turn_setup = __esm(async () => {
459473
+ init_client_skills();
459416
459474
  init_context();
459417
459475
  init_backend2();
459418
459476
  init_engine3();
@@ -476801,6 +476859,56 @@ var init_message_channel_formatting = __esm(() => {
476801
476859
  };
476802
476860
  });
476803
476861
 
476862
+ // src/channels/message-channel-idempotency.ts
476863
+ function isErrorResult(result) {
476864
+ return result.startsWith("Error:");
476865
+ }
476866
+ function createMessageChannelIdempotencyScope() {
476867
+ const inFlight = new Map;
476868
+ let lastSuccessful = null;
476869
+ let latestInvocation = 0;
476870
+ return {
476871
+ async execute(key2, effect) {
476872
+ if (!key2) {
476873
+ latestInvocation += 1;
476874
+ lastSuccessful = null;
476875
+ return await effect();
476876
+ }
476877
+ const pendingDuplicate = inFlight.get(key2);
476878
+ if (pendingDuplicate) {
476879
+ throw new MessageChannelDuplicateActionError("in-flight");
476880
+ }
476881
+ if (lastSuccessful?.key === key2) {
476882
+ throw new MessageChannelDuplicateActionError("completed");
476883
+ }
476884
+ const invocation = ++latestInvocation;
476885
+ lastSuccessful = null;
476886
+ const pending = Promise.resolve().then(effect);
476887
+ inFlight.set(key2, pending);
476888
+ try {
476889
+ const result = await pending;
476890
+ if (invocation === latestInvocation && !isErrorResult(result)) {
476891
+ lastSuccessful = { key: key2 };
476892
+ }
476893
+ return result;
476894
+ } finally {
476895
+ if (inFlight.get(key2) === pending)
476896
+ inFlight.delete(key2);
476897
+ }
476898
+ }
476899
+ };
476900
+ }
476901
+ var MessageChannelDuplicateActionError;
476902
+ var init_message_channel_idempotency = __esm(() => {
476903
+ MessageChannelDuplicateActionError = class MessageChannelDuplicateActionError extends Error {
476904
+ constructor(state) {
476905
+ const detail = state === "in-flight" ? "an identical text send is already in progress" : "the immediately previous MessageChannel call already sent this exact text to the same destination";
476906
+ super(`Duplicate MessageChannel action suppressed: ${detail}. The duplicate was not sent; continue the turn instead of retrying it.`);
476907
+ this.name = "MessageChannelDuplicateActionError";
476908
+ }
476909
+ };
476910
+ });
476911
+
476804
476912
  // src/channels/message-channel-executor.ts
476805
476913
  function firstNonEmptyString4(...values2) {
476806
476914
  for (const value of values2) {
@@ -476950,6 +477058,50 @@ async function dispatchMessageChannelAction(params) {
476950
477058
  formatText: (text2) => formatOutboundChannelMessage(params.request.channel, text2)
476951
477059
  });
476952
477060
  }
477061
+ function trimmedOrNull(value) {
477062
+ const trimmed = value?.trim();
477063
+ return trimmed ? trimmed : null;
477064
+ }
477065
+ function effectiveTextThreadId(request, route) {
477066
+ const requestThreadId = trimmedOrNull(request.threadId);
477067
+ const routeThreadId = trimmedOrNull(route.threadId);
477068
+ if (request.channel === "telegram") {
477069
+ if (requestThreadId)
477070
+ return requestThreadId;
477071
+ if (route.chatType === "direct")
477072
+ return null;
477073
+ return route.chatId.trim().startsWith("-") ? routeThreadId : null;
477074
+ }
477075
+ if (request.channel === "discord") {
477076
+ return route.chatType === "direct" ? route.chatId : requestThreadId ?? routeThreadId;
477077
+ }
477078
+ if (request.channel === "slack") {
477079
+ const isDirect = route.chatType === "direct" || request.chatId.startsWith("D");
477080
+ if (isDirect)
477081
+ return requestThreadId ?? routeThreadId;
477082
+ return request.replyToMessageId ? null : requestThreadId ?? routeThreadId;
477083
+ }
477084
+ return null;
477085
+ }
477086
+ function effectiveTextReplyId(request, route) {
477087
+ const isSlackDirect = request.channel === "slack" && (route.chatType === "direct" || request.chatId.startsWith("D"));
477088
+ return isSlackDirect ? null : trimmedOrNull(request.replyToMessageId);
477089
+ }
477090
+ function messageIdempotencyKey(request, route) {
477091
+ if (request.action !== "send" && request.action !== "send-rich" || request.mediaPath) {
477092
+ return null;
477093
+ }
477094
+ return JSON.stringify({
477095
+ action: request.action,
477096
+ channel: request.channel,
477097
+ chatId: route.chatId,
477098
+ accountId: route.accountId ?? null,
477099
+ chatType: route.chatType ?? null,
477100
+ threadId: effectiveTextThreadId(request, route),
477101
+ message: request.message ?? null,
477102
+ replyToMessageId: effectiveTextReplyId(request, route)
477103
+ });
477104
+ }
476953
477105
  async function executeMessageChannel(input, options3) {
476954
477106
  const normalized = normalizeMessageChannelInput(input, options3.resolver);
476955
477107
  if (typeof normalized === "string")
@@ -476984,10 +477136,8 @@ async function executeMessageChannel(input, options3) {
476984
477136
  channelTurnSources: options3.channelTurnSources
476985
477137
  });
476986
477138
  const requestThreadId = normalized.action === "download-file" ? normalized.threadId : inferredThreadId ?? (normalized.channel === "telegram" && context4.route.chatType === "direct" ? normalized.threadId : context4.route.threadId ?? normalized.threadId);
476987
- return await dispatchMessageChannelAction({
476988
- request: buildMessageChannelRequest(normalized, normalized.chatId, requestThreadId),
476989
- context: context4
476990
- });
477139
+ const request2 = buildMessageChannelRequest(normalized, normalized.chatId, requestThreadId);
477140
+ return await dispatchWithIdempotency(request2, context4, options3.idempotencyScope);
476991
477141
  }
476992
477142
  if (normalized.channel !== "slack") {
476993
477143
  return `Error: Explicit MessageChannel targets are not supported on ${normalized.channel}.`;
@@ -477008,17 +477158,23 @@ async function executeMessageChannel(input, options3) {
477008
477158
  transport: proactive.transport,
477009
477159
  messageActions: proactive.messageActions
477010
477160
  };
477011
- return await dispatchMessageChannelAction({
477012
- request: buildMessageChannelRequest(normalized, proactive.target.chatId, proactive.target.threadId),
477013
- context: context3
477014
- });
477161
+ const request = buildMessageChannelRequest(normalized, proactive.target.chatId, proactive.target.threadId);
477162
+ return await dispatchWithIdempotency(request, context3, options3.idempotencyScope);
477015
477163
  } catch (error54) {
477164
+ if (error54 instanceof MessageChannelDuplicateActionError)
477165
+ throw error54;
477016
477166
  const message = error54 instanceof Error ? error54.message : "unknown error";
477017
477167
  return `Error sending message to ${normalized.channel}: ${message}`;
477018
477168
  }
477019
477169
  }
477170
+ function dispatchWithIdempotency(request, context3, scope) {
477171
+ const dispatch = () => dispatchMessageChannelAction({ request, context: context3 });
477172
+ const key2 = messageIdempotencyKey(request, context3.route);
477173
+ return scope ? scope.execute(key2, dispatch) : dispatch();
477174
+ }
477020
477175
  var init_message_channel_executor = __esm(() => {
477021
477176
  init_message_channel_formatting();
477177
+ init_message_channel_idempotency();
477022
477178
  });
477023
477179
 
477024
477180
  // src/channels/slack/proactive-accounts.ts
@@ -477140,7 +477296,7 @@ function createLocalMessageChannelResolver() {
477140
477296
  }
477141
477297
  };
477142
477298
  }
477143
- async function message_channel(args) {
477299
+ async function message_channel(args, idempotencyScope) {
477144
477300
  if (!getChannelRegistry()) {
477145
477301
  return "Error: Channel system is not initialized. Start with --channels flag.";
477146
477302
  }
@@ -477150,7 +477306,8 @@ async function message_channel(args) {
477150
477306
  return await executeMessageChannel(args, {
477151
477307
  scope: args.parentScope,
477152
477308
  resolver: createLocalMessageChannelResolver(),
477153
- channelTurnSources: args.channelTurnSources
477309
+ channelTurnSources: args.channelTurnSources,
477310
+ idempotencyScope
477154
477311
  });
477155
477312
  }
477156
477313
  var init_message_channel = __esm(() => {
@@ -478089,8 +478246,9 @@ class ChannelGateway {
478089
478246
  this.hooks = hooks;
478090
478247
  this.disposers.push(client.onMessage((message) => this.handleMessage(message)), client.onExternalToolCall((request) => {
478091
478248
  const state = request.runtime ? this.states.get(runtimeKey(request.runtime)) : undefined;
478092
- const sources = state?.active?.sources ?? state?.routedSources ?? [];
478093
- return hooks.executeExternalTool(request, sources);
478249
+ const active = state?.active;
478250
+ const sources = active?.sources ?? state?.routedSources ?? [];
478251
+ return hooks.executeExternalTool(request, sources, active?.idempotencyScope ?? null);
478094
478252
  }));
478095
478253
  }
478096
478254
  close() {
@@ -478178,7 +478336,8 @@ class ChannelGateway {
478178
478336
  batchId: `channel-recovered-${crypto.randomUUID()}`,
478179
478337
  sources: uniqueSources(sources),
478180
478338
  progress: createChannelTurnProgressBuilder(),
478181
- richDraft: null
478339
+ richDraft: null,
478340
+ idempotencyScope: createMessageChannelIdempotencyScope()
478182
478341
  };
478183
478342
  state.active = recoveredTurn;
478184
478343
  }
@@ -478400,7 +478559,8 @@ class ChannelGateway {
478400
478559
  richDraft: this.hooks.createRichDraft?.({
478401
478560
  batchId: `channel-${clientMessageId}`,
478402
478561
  sources
478403
- }) ?? null
478562
+ }) ?? null,
478563
+ idempotencyScope: createMessageChannelIdempotencyScope()
478404
478564
  };
478405
478565
  const processingEvent = {
478406
478566
  type: "processing",
@@ -478479,6 +478639,7 @@ class ChannelGateway {
478479
478639
  var MAX_ACCEPTED_CLIENT_MESSAGE_IDS = 2048;
478480
478640
  var init_gateway_core = __esm(() => {
478481
478641
  init_interactive_policy();
478642
+ init_message_channel_idempotency();
478482
478643
  init_progress_builder();
478483
478644
  });
478484
478645
 
@@ -479972,7 +480133,7 @@ async function startLocalChannelGateway(options3) {
479972
480133
  buildExternalTool: async (runtime) => {
479973
480134
  return buildGatewayMessageChannelTool(registry2.resolveTurnSourcesForScope(runtime.agent_id, runtime.conversation_id));
479974
480135
  },
479975
- executeExternalTool: async (request, sources) => {
480136
+ executeExternalTool: async (request, sources, idempotencyScope) => {
479976
480137
  if (request.tool_name !== "MessageChannel" || !request.runtime) {
479977
480138
  throw new Error(`Unsupported gateway tool: ${request.tool_name}`);
479978
480139
  }
@@ -479985,9 +480146,10 @@ async function startLocalChannelGateway(options3) {
479985
480146
  conversationId: request.runtime.conversation_id
479986
480147
  },
479987
480148
  channelTurnSources: sources
479988
- });
480149
+ }, idempotencyScope);
479989
480150
  return {
479990
- content: [{ type: "text", text: text2 }]
480151
+ content: [{ type: "text", text: text2 }],
480152
+ is_error: text2.startsWith("Error:")
479991
480153
  };
479992
480154
  },
479993
480155
  onLifecycle: (event2) => registry2.dispatchTurnLifecycleEvent(event2),
@@ -492535,7 +492697,7 @@ var init_mcp_client = __esm(() => {
492535
492697
  init_streamableHttp();
492536
492698
  DEFAULT_CLIENT_INFO = {
492537
492699
  name: "letta-code",
492538
- version: "0.30.10"
492700
+ version: "0.30.11"
492539
492701
  };
492540
492702
  });
492541
492703
 
@@ -559650,4 +559812,4 @@ function registerBunOAuthFlows() {
559650
559812
  registerBunOAuthFlows();
559651
559813
  await init_src5().then(() => exports_src2);
559652
559814
 
559653
- //# debugId=BC797C70CE06CED564756E2164756E21
559815
+ //# debugId=49F16715F241AC4A64756E2164756E21