@letta-ai/letta-code 0.30.9 → 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 (39) 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/tools/manager.d.ts.map +1 -1
  32. package/dist/types/tools/toolset.d.ts +1 -0
  33. package/dist/types/tools/toolset.d.ts.map +1 -1
  34. package/dist/types/websocket/listener/runtime.d.ts.map +1 -1
  35. package/dist/types/websocket/listener/types.d.ts +3 -1
  36. package/dist/types/websocket/listener/types.d.ts.map +1 -1
  37. package/letta.js +413 -236
  38. package/package.json +1 -1
  39. package/scripts/source-file-size-baseline.json +2 -2
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.9",
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(),
@@ -352446,13 +352456,7 @@ async function resolveBaseToolNamesForModel(modelIdentifier, options3) {
352446
352456
  baseToolNames = TOOL_NAMES;
352447
352457
  }
352448
352458
  if (options3?.include && options3.include.length > 0) {
352449
- const seen = new Set(baseToolNames);
352450
- for (const name of options3.include) {
352451
- if (!seen.has(name)) {
352452
- baseToolNames.push(name);
352453
- seen.add(name);
352454
- }
352455
- }
352459
+ baseToolNames = [...new Set([...baseToolNames, ...options3.include])];
352456
352460
  }
352457
352461
  if (options3?.exclude && options3.exclude.length > 0) {
352458
352462
  const excludeSet = new Set(options3.exclude);
@@ -353620,6 +353624,18 @@ function resolveIncludedToolNames(toolNames) {
353620
353624
  return internalName;
353621
353625
  });
353622
353626
  }
353627
+ function resolveAllowlistedToolNames(allowlist) {
353628
+ if (!allowlist)
353629
+ return [];
353630
+ const toolNames = [];
353631
+ for (const allowedName of allowlist) {
353632
+ const internalName = getInternalToolName(allowedName);
353633
+ if (Object.hasOwn(TOOL_DEFINITIONS, internalName)) {
353634
+ toolNames.push(internalName);
353635
+ }
353636
+ }
353637
+ return toolNames;
353638
+ }
353623
353639
  function appendUniqueToolNames(baseToolNames, includedToolNames) {
353624
353640
  const result = [...baseToolNames];
353625
353641
  const seen = new Set(result);
@@ -353711,7 +353727,7 @@ async function prepareToolExecutionContextForResolvedTarget(params) {
353711
353727
  } = params;
353712
353728
  const effectiveModel = modelIdentifier && modelIdentifier.length > 0 ? resolveModel(modelIdentifier) ?? modelIdentifier : null;
353713
353729
  const effectiveToolsetPreference = clientToolset?.base ?? toolsetPreference;
353714
- const includedToolNames = resolveIncludedToolNames(clientToolset?.include);
353730
+ const includedToolNames = appendUniqueToolNames(resolveIncludedToolNames(clientToolset?.include), resolveAllowlistedToolNames(clientToolAllowlist));
353715
353731
  if (effectiveToolsetPreference === "auto") {
353716
353732
  const derivedToolset = effectiveModel ? deriveToolsetFromModel(effectiveModel, providerType) : "default";
353717
353733
  const scopedModContext2 = buildModInvocationContext({
@@ -353790,6 +353806,7 @@ async function prepareToolExecutionContextForScope(params) {
353790
353806
  externalToolScopeIds,
353791
353807
  workingDirectory,
353792
353808
  permissionModeState,
353809
+ skillsDirectory,
353793
353810
  skillSources,
353794
353811
  cachedAgent,
353795
353812
  modContext,
@@ -353844,6 +353861,7 @@ async function prepareToolExecutionContextForScope(params) {
353844
353861
  agentName: agent2.name ?? null,
353845
353862
  conversationId: scopedConversationId,
353846
353863
  workingDirectory,
353864
+ ...skillsDirectory !== undefined ? { skillsDirectory } : {},
353847
353865
  ...skillSources !== undefined ? { skillSources } : {}
353848
353866
  }
353849
353867
  });
@@ -356528,7 +356546,8 @@ function createLocalConversationRecord(conversationId, agentId, _sequence, body
356528
356546
  } : {},
356529
356547
  ...modelSettings !== undefined ? { model_settings: modelSettings } : {},
356530
356548
  ...typeof bodyRecord.context_window_limit === "number" ? { context_window_limit: bodyRecord.context_window_limit } : {},
356531
- ...typeof bodyRecord.hidden === "boolean" ? { hidden: bodyRecord.hidden } : {}
356549
+ ...typeof bodyRecord.hidden === "boolean" ? { hidden: bodyRecord.hidden } : {},
356550
+ ...isStringArray2(bodyRecord.tags) ? { tags: bodyRecord.tags } : {}
356532
356551
  };
356533
356552
  }
356534
356553
  function updateLocalConversationRecord(current, body, updatedAt) {
@@ -411948,6 +411967,217 @@ var init_access_control = __esm(() => {
411948
411967
  CHANNEL_COMMAND_FLOOR = ["help", "status", "whoami"];
411949
411968
  });
411950
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
+
411951
412181
  // src/channels/feedback.ts
411952
412182
  function channelDisplayName2(channelId) {
411953
412183
  try {
@@ -412216,126 +412446,17 @@ function channelDisplayName4(channelId) {
412216
412446
  return channelId;
412217
412447
  }
412218
412448
  }
412219
- function listChannelSlashCommands() {
412220
- return CHANNEL_SLASH_COMMANDS.map((definition) => ({
412221
- ...definition,
412222
- aliases: definition.aliases ? [...definition.aliases] : undefined
412223
- }));
412224
- }
412225
- function parseSingleLineChannelCommand(text2, prefix) {
412226
- const trimmed = text2.trim();
412227
- const escapedPrefix = prefix === "/" ? "\\/" : "!";
412228
- const match4 = trimmed.match(new RegExp(`^${escapedPrefix}([A-Za-z][\\w-]*)(?:@[A-Za-z0-9_]+)?(?:[^\\S\\r\\n]+(.*))?$`));
412229
- if (!match4) {
412230
- return null;
412231
- }
412232
- const [, name, args] = match4;
412233
- if (!name) {
412234
- return null;
412235
- }
412236
- return {
412237
- name: name.toLowerCase(),
412238
- args: args?.trim() ?? "",
412239
- raw: trimmed
412240
- };
412241
- }
412242
- function parseAnySingleLineChannelCommand(text2) {
412243
- return parseSingleLineChannelCommand(text2, "/") ?? parseSingleLineChannelCommand(text2, "!");
412244
- }
412245
- function parseChannelCommand(text2, prefix) {
412246
- const lines = text2.trim().split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
412247
- const [firstLine, ...remainingLines] = lines;
412248
- if (!firstLine) {
412249
- return null;
412250
- }
412251
- const firstCommand = parseSingleLineChannelCommand(firstLine, prefix);
412252
- if (!firstCommand) {
412253
- return null;
412254
- }
412255
- const laterCommand = remainingLines.find((line) => Boolean(parseAnySingleLineChannelCommand(line)));
412256
- if (laterCommand) {
412257
- return firstCommand;
412258
- }
412259
- const continuationArgs = remainingLines.join(`
412260
- `).trim();
412261
- if (!continuationArgs) {
412262
- return firstCommand;
412263
- }
412264
- return {
412265
- ...firstCommand,
412266
- args: [firstCommand.args, continuationArgs].filter((part) => part.length > 0).join(`
412267
- `)
412268
- };
412269
- }
412270
- function parseChannelSlashCommand(text2) {
412271
- return parseChannelCommand(text2, "/");
412272
- }
412273
- function parseChannelBangCommand(text2) {
412274
- return parseChannelCommand(text2, "!");
412275
- }
412276
- function supportedCommandsText(prefix = "/") {
412277
- return listChannelSlashCommands().map((definition) => `${prefix}${definition.name}`).join(", ");
412278
- }
412279
- function supportedSlackMentionSlashCommandsText() {
412280
- return SLACK_MENTION_SLASH_COMMAND_EXAMPLES.join(", ");
412281
- }
412282
- function supportedBangCommandsText() {
412283
- return SLACK_MENTION_COMMAND_NAMES.map((name) => `!${name}`).join(", ");
412284
- }
412285
- function isSupportedSlackMentionCommand(commandName) {
412286
- return SLACK_MENTION_COMMAND_NAMES.includes(commandName);
412287
- }
412288
412449
  function isSlackMentionSlashCommand(msg, command) {
412289
412450
  return msg.channel === "slack" && msg.isMention === true && command.raw.startsWith("/");
412290
412451
  }
412291
412452
  function isSlackMentionControlCommand(msg, command) {
412292
412453
  return command.raw.startsWith("!") || isSlackMentionSlashCommand(msg, command);
412293
412454
  }
412294
- function buildChannelHelpMessage(channelId) {
412295
- const displayName = channelDisplayName4(channelId);
412296
- if (channelId === "slack") {
412297
- return [
412298
- `${displayName} is connected to Letta Code.`,
412299
- "Talk by mentioning the app in a channel thread. Once a thread is routed, normal replies continue the same agent conversation until detached.",
412300
- "Control commands start immediately after the mention:",
412301
- "@agent /model - show this thread's current model",
412302
- "@agent /model list - show available models",
412303
- "@agent /model <handle-or-id> - switch this thread's model",
412304
- "@agent /status - show route and listener status",
412305
- "@agent /cancel - cancel the current turn",
412306
- "@agent /chat - show the web chat link",
412307
- "@agent /feedback <message> - send feedback to the Letta team from this routed thread",
412308
- "@agent /reflection - start a memory reflection pass",
412309
- "@agent /detach - stop replying in this thread until mentioned again",
412310
- "@agent /new - start a fresh conversation for this thread",
412311
- "@agent /reload - reload settings, local mods, and agent secrets",
412312
- `Legacy bang aliases still work after a mention: ${supportedBangCommandsText()}.`,
412313
- "If this chat is not connected yet, send a normal message and follow the pairing instructions."
412314
- ].join(`
412315
- `);
412316
- }
412317
- return [
412318
- `${displayName} is connected to Letta Code.`,
412319
- "Send a normal message here and the connected agent will reply in this chat.",
412320
- `Supported slash commands here: ${supportedCommandsText()}.`,
412321
- "If this chat is not connected yet, send any non-command message and follow the pairing instructions."
412322
- ].join(`
412323
-
412324
- `);
412455
+ function buildChannelHelpMessage2(channelId) {
412456
+ return buildChannelHelpMessage(channelId, channelDisplayName4);
412325
412457
  }
412326
- function buildUnsupportedChannelCommandMessage(channelId, command) {
412327
- const displayName = channelDisplayName4(channelId);
412328
- const isBang = command.raw.startsWith("!");
412329
- const commandKind = isBang ? "bang" : "slash";
412330
- const supportedCommands = isBang ? supportedBangCommandsText() : channelId === "slack" ? supportedSlackMentionSlashCommandsText() : supportedCommandsText();
412331
- const supportedLabel = channelId === "slack" && !isBang ? "Slack mention commands" : `${commandKind} commands`;
412332
- return [
412333
- `${displayName} received ${command.raw}, but that ${commandKind} command is not supported in channels yet.`,
412334
- `Supported ${supportedLabel}: ${supportedCommands}.`,
412335
- `Send normal messages without a leading ${isBang ? "bang" : "slash"} command to talk to the connected agent.`
412336
- ].join(`
412337
-
412338
- `);
412458
+ function buildUnsupportedChannelCommandMessage2(channelId, command) {
412459
+ return buildUnsupportedChannelCommandMessage(channelId, command, channelDisplayName4);
412339
412460
  }
412340
412461
  function buildChannelStatusMessage(msg, context3) {
412341
412462
  const displayName = channelDisplayName4(msg.channel);
@@ -412614,7 +412735,7 @@ async function tryHandleChannelSlashCommand(adapter, msg, options3 = {}) {
412614
412735
  const isBangCommand = command.raw.startsWith("!");
412615
412736
  const isSlackMentionControl = isSlackMentionControlCommand(msg, command);
412616
412737
  if (isBangCommand && !isSupportedSlackMentionCommand(command.name)) {
412617
- await adapter.sendDirectReply(msg.chatId, buildUnsupportedChannelCommandMessage(msg.channel, command), buildDirectReplyOptions(msg));
412738
+ await adapter.sendDirectReply(msg.chatId, buildUnsupportedChannelCommandMessage2(msg.channel, command), buildDirectReplyOptions(msg));
412618
412739
  return true;
412619
412740
  }
412620
412741
  const canonicalName = canonicalizeChannelCommandName(command.name);
@@ -412625,7 +412746,7 @@ async function tryHandleChannelSlashCommand(adapter, msg, options3 = {}) {
412625
412746
  const reply = normalizeDirectReplyPayload(await (async () => {
412626
412747
  switch (command.name) {
412627
412748
  case "help":
412628
- return buildChannelHelpMessage(msg.channel);
412749
+ return buildChannelHelpMessage2(msg.channel);
412629
412750
  case "whoami":
412630
412751
  return buildChannelWhoamiMessage(msg, options3.commandGate);
412631
412752
  case "status":
@@ -412667,7 +412788,7 @@ async function tryHandleChannelSlashCommand(adapter, msg, options3 = {}) {
412667
412788
  });
412668
412789
  case "detach":
412669
412790
  if (!isSlackMentionControl) {
412670
- return buildUnsupportedChannelCommandMessage(msg.channel, command);
412791
+ return buildUnsupportedChannelCommandMessage2(msg.channel, command);
412671
412792
  }
412672
412793
  return handleScopedCommand({
412673
412794
  msg,
@@ -412682,7 +412803,7 @@ async function tryHandleChannelSlashCommand(adapter, msg, options3 = {}) {
412682
412803
  });
412683
412804
  case "new":
412684
412805
  if (!isSlackMentionControl) {
412685
- return buildUnsupportedChannelCommandMessage(msg.channel, command);
412806
+ return buildUnsupportedChannelCommandMessage2(msg.channel, command);
412686
412807
  }
412687
412808
  return handleScopedCommand({
412688
412809
  msg,
@@ -412703,7 +412824,7 @@ async function tryHandleChannelSlashCommand(adapter, msg, options3 = {}) {
412703
412824
  handler: options3.handlers?.reload
412704
412825
  });
412705
412826
  default:
412706
- return buildUnsupportedChannelCommandMessage(msg.channel, command);
412827
+ return buildUnsupportedChannelCommandMessage2(msg.channel, command);
412707
412828
  }
412708
412829
  })());
412709
412830
  if (reply === null) {
@@ -412716,92 +412837,14 @@ async function tryHandleChannelSlashCommand(adapter, msg, options3 = {}) {
412716
412837
  } : undefined);
412717
412838
  return true;
412718
412839
  }
412719
- 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;
412720
412841
  var init_commands = __esm(() => {
412721
412842
  init_access_control();
412843
+ init_command_surface();
412722
412844
  init_feedback2();
412723
412845
  init_plugin_registry();
412724
412846
  init_registry_presentation();
412725
- CHANNEL_SLASH_COMMANDS = [
412726
- {
412727
- name: "help",
412728
- kind: "direct",
412729
- summary: "Show channel usage guidance."
412730
- },
412731
- {
412732
- name: "status",
412733
- kind: "direct",
412734
- summary: "Show this chat's channel connection status."
412735
- },
412736
- {
412737
- name: "whoami",
412738
- kind: "direct",
412739
- summary: "Show your access tier and runnable commands here."
412740
- },
412741
- {
412742
- name: "pause",
412743
- kind: "direct",
412744
- summary: "Pause agent routing for this chat."
412745
- },
412746
- {
412747
- name: "resume",
412748
- kind: "direct",
412749
- summary: "Resume agent routing for this chat."
412750
- },
412751
- {
412752
- name: "cancel",
412753
- kind: "agent-scoped",
412754
- summary: "Cancel the in-progress agent turn for this chat."
412755
- },
412756
- {
412757
- name: "chat",
412758
- kind: "direct",
412759
- summary: "Show the Letta web chat link for this channel route."
412760
- },
412761
- {
412762
- name: "feedback",
412763
- kind: "direct",
412764
- summary: "Send feedback about Letta Code from this routed chat."
412765
- },
412766
- {
412767
- name: "model",
412768
- kind: "agent-scoped",
412769
- summary: "Show, list, or switch the model for this chat's routed conversation."
412770
- },
412771
- {
412772
- name: "reflection",
412773
- aliases: ["reflect"],
412774
- kind: "agent-scoped",
412775
- summary: "Start a memory reflection pass for this conversation."
412776
- },
412777
- {
412778
- name: "reload",
412779
- kind: "agent-scoped",
412780
- summary: "Reload settings, local mods, and agent secrets."
412781
- }
412782
- ];
412783
- SLACK_MENTION_COMMAND_NAMES = [
412784
- "help",
412785
- "detach",
412786
- "model",
412787
- "new",
412788
- "reload"
412789
- ];
412790
- SLACK_MENTION_SLASH_COMMAND_EXAMPLES = [
412791
- "@agent /help",
412792
- "@agent /status",
412793
- "@agent /whoami",
412794
- "@agent /model",
412795
- "@agent /model list",
412796
- "@agent /model <handle-or-id>",
412797
- "@agent /cancel",
412798
- "@agent /chat",
412799
- "@agent /feedback <message>",
412800
- "@agent /reflection",
412801
- "@agent /detach",
412802
- "@agent /new",
412803
- "@agent /reload"
412804
- ];
412847
+ init_command_surface();
412805
412848
  });
412806
412849
 
412807
412850
  // src/channels/slack/model-picker-blocks.ts
@@ -424242,10 +424285,24 @@ var init_registry_inbound = __esm(() => {
424242
424285
  });
424243
424286
 
424244
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
+ }
424245
424300
  function createChannelRouteProvisioner(deps) {
424246
424301
  async function createConversationForAgent(agentId, summary) {
424302
+ const modelPin = await resolveConversationModelPin(agentId);
424247
424303
  const conversation = await getBackend().createConversation({
424248
424304
  agent_id: agentId,
424305
+ ...modelPin,
424249
424306
  ...summary ? { summary } : {}
424250
424307
  });
424251
424308
  return conversation.id;
@@ -424588,6 +424645,7 @@ function createChannelRouteProvisioner(deps) {
424588
424645
  }
424589
424646
  var init_registry_routes = __esm(() => {
424590
424647
  init_backend2();
424648
+ init_debug();
424591
424649
  init_accounts();
424592
424650
  init_registry_presentation();
424593
424651
  init_routing();
@@ -437133,7 +437191,7 @@ function formatScheduledTaskPrompt(input) {
437133
437191
  return lines.join(`
437134
437192
  `);
437135
437193
  }
437136
- 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.";
437137
437195
 
437138
437196
  // src/cron/prompt.ts
437139
437197
  function getIntendedCronOccurrence(task2, matchedAt) {
@@ -437171,14 +437229,12 @@ function minuteKey(date6) {
437171
437229
  function wrapCronPrompt(task2, timing2) {
437172
437230
  return formatCronPrompt(task2, timing2);
437173
437231
  }
437174
- function getCronConversationSummary(task2) {
437175
- return `[Schedule] ${task2.name}`;
437176
- }
437177
437232
  async function resolveCronFireConversationId(task2) {
437178
437233
  if (task2.conversation_id === NEW_CONVERSATION_TARGET) {
437179
437234
  const conversation = await getBackend().createConversation({
437180
437235
  agent_id: task2.agent_id,
437181
- summary: getCronConversationSummary(task2)
437236
+ summary: task2.name,
437237
+ tags: [SCHEDULE_ORIGIN_TAG]
437182
437238
  });
437183
437239
  return conversation.id;
437184
437240
  }
@@ -459366,7 +459422,8 @@ async function prepareListenerTurn(params) {
459366
459422
  inboundUserTranscriptLines = buildInboundUserTranscriptLines(currentInput);
459367
459423
  }
459368
459424
  const modAdapters = await ensureListenerModAdaptersForAgent(runtime.listener, agentId);
459369
- const environmentDeviceId = connectionId ? runtime.listener.connections.get(connectionId)?.options.deviceId : undefined;
459425
+ const listenerOptions = connectionId ? runtime.listener.connections.get(connectionId)?.options : runtime.listener.connections.values().next().value?.options;
459426
+ const environmentDeviceId = listenerOptions?.deviceId;
459370
459427
  const preparedToolContext = await prepareToolExecutionContextForScope({
459371
459428
  connectionId,
459372
459429
  environmentDeviceId,
@@ -459378,6 +459435,7 @@ async function prepareListenerTurn(params) {
459378
459435
  externalToolScopeIds: msg.externalToolScopeIds,
459379
459436
  workingDirectory,
459380
459437
  permissionModeState,
459438
+ skillsDirectory: listenerOptions?.skillsDirectory,
459381
459439
  skillSources: runtime.skillSources,
459382
459440
  cachedAgent,
459383
459441
  modContext: createListenerAgentModContext(agentId),
@@ -459387,9 +459445,18 @@ async function prepareListenerTurn(params) {
459387
459445
  if (isInterrupted()) {
459388
459446
  return { kind: "interrupted" };
459389
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
+ }
459390
459456
  runtime.currentToolset = preparedToolContext.toolset;
459391
459457
  runtime.currentToolsetPreference = preparedToolContext.toolsetPreference;
459392
459458
  runtime.currentLoadedTools = preparedToolContext.preparedToolContext.loadedToolNames;
459459
+ runtime.currentAvailableSkills = availableSkills;
459393
459460
  return {
459394
459461
  kind: "ready",
459395
459462
  getCachedAgent: () => cachedAgent,
@@ -459403,6 +459470,7 @@ async function prepareListenerTurn(params) {
459403
459470
  };
459404
459471
  }
459405
459472
  var init_turn_setup = __esm(async () => {
459473
+ init_client_skills();
459406
459474
  init_context();
459407
459475
  init_backend2();
459408
459476
  init_engine3();
@@ -468612,13 +468680,14 @@ function shouldAcquireStandaloneListenerLock() {
468612
468680
  return shouldAcquireManualListenerLock(getSpawnerListenerInstanceId(), process.env.LETTA_DESKTOP_MODE === "1");
468613
468681
  }
468614
468682
  function printListenUsage() {
468615
- console.log(`Usage: letta server [--env-name <name>] [--channels <list>] [--debug]
468683
+ console.log(`Usage: letta server [--env-name <name>] [--channels <list>] [--skills <path>] [--debug]
468616
468684
  `);
468617
468685
  console.log(`Register this letta-code instance to receive messages from Letta Cloud.
468618
468686
  `);
468619
468687
  console.log("Options:");
468620
468688
  console.log(" --env-name <name> Friendly name for this environment (uses hostname if not provided)");
468621
468689
  console.log(" --channels <list> Comma-separated channel names to enable (e.g. telegram)");
468690
+ console.log(" --skills <path> Use this directory for environment-provided skills");
468622
468691
  console.log(" --install-channel-runtimes Install missing runtime deps for the selected channels before startup");
468623
468692
  console.log(" --debug Plain-text mode: log all WebSocket events instead of interactive UI");
468624
468693
  console.log(` -h, --help Show this help message
@@ -468652,6 +468721,7 @@ async function runListenSubcommand(argv) {
468652
468721
  return 1;
468653
468722
  }
468654
468723
  const debugMode = !!values2.debug;
468724
+ const skillsDirectory = values2.skills ?? process.env.LETTA_SKILLS_DIRECTORY;
468655
468725
  if (values2.help) {
468656
468726
  printListenUsage();
468657
468727
  return 0;
@@ -468967,6 +469037,7 @@ async function runListenSubcommand(argv) {
468967
469037
  supportsSplitStatusChannels: nextSupportsSplitStatusChannels,
468968
469038
  deviceId,
468969
469039
  connectionName,
469040
+ skillsDirectory,
468970
469041
  onWsEvent: shouldLogWsEvents ? wsEventLogger : undefined,
468971
469042
  onStatusChange: (status) => {
468972
469043
  sessionLog.log(`status: ${status}`);
@@ -469032,6 +469103,7 @@ async function runListenSubcommand(argv) {
469032
469103
  supportsSplitStatusChannels: nextSupportsSplitStatusChannels,
469033
469104
  deviceId,
469034
469105
  connectionName,
469106
+ skillsDirectory,
469035
469107
  onWsEvent: shouldLogWsEvents ? wsEventLogger : undefined,
469036
469108
  onStatusChange: (status) => {
469037
469109
  sessionLog.log(`status: ${status}`);
@@ -469124,6 +469196,7 @@ var init_listen = __esm(async () => {
469124
469196
  LISTEN_OPTIONS = {
469125
469197
  "env-name": { type: "string" },
469126
469198
  channels: { type: "string" },
469199
+ skills: { type: "string" },
469127
469200
  "install-channel-runtimes": { type: "boolean" },
469128
469201
  help: { type: "boolean", short: "h" },
469129
469202
  debug: { type: "boolean" }
@@ -476786,6 +476859,56 @@ var init_message_channel_formatting = __esm(() => {
476786
476859
  };
476787
476860
  });
476788
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
+
476789
476912
  // src/channels/message-channel-executor.ts
476790
476913
  function firstNonEmptyString4(...values2) {
476791
476914
  for (const value of values2) {
@@ -476935,6 +477058,50 @@ async function dispatchMessageChannelAction(params) {
476935
477058
  formatText: (text2) => formatOutboundChannelMessage(params.request.channel, text2)
476936
477059
  });
476937
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
+ }
476938
477105
  async function executeMessageChannel(input, options3) {
476939
477106
  const normalized = normalizeMessageChannelInput(input, options3.resolver);
476940
477107
  if (typeof normalized === "string")
@@ -476969,10 +477136,8 @@ async function executeMessageChannel(input, options3) {
476969
477136
  channelTurnSources: options3.channelTurnSources
476970
477137
  });
476971
477138
  const requestThreadId = normalized.action === "download-file" ? normalized.threadId : inferredThreadId ?? (normalized.channel === "telegram" && context4.route.chatType === "direct" ? normalized.threadId : context4.route.threadId ?? normalized.threadId);
476972
- return await dispatchMessageChannelAction({
476973
- request: buildMessageChannelRequest(normalized, normalized.chatId, requestThreadId),
476974
- context: context4
476975
- });
477139
+ const request2 = buildMessageChannelRequest(normalized, normalized.chatId, requestThreadId);
477140
+ return await dispatchWithIdempotency(request2, context4, options3.idempotencyScope);
476976
477141
  }
476977
477142
  if (normalized.channel !== "slack") {
476978
477143
  return `Error: Explicit MessageChannel targets are not supported on ${normalized.channel}.`;
@@ -476993,17 +477158,23 @@ async function executeMessageChannel(input, options3) {
476993
477158
  transport: proactive.transport,
476994
477159
  messageActions: proactive.messageActions
476995
477160
  };
476996
- return await dispatchMessageChannelAction({
476997
- request: buildMessageChannelRequest(normalized, proactive.target.chatId, proactive.target.threadId),
476998
- context: context3
476999
- });
477161
+ const request = buildMessageChannelRequest(normalized, proactive.target.chatId, proactive.target.threadId);
477162
+ return await dispatchWithIdempotency(request, context3, options3.idempotencyScope);
477000
477163
  } catch (error54) {
477164
+ if (error54 instanceof MessageChannelDuplicateActionError)
477165
+ throw error54;
477001
477166
  const message = error54 instanceof Error ? error54.message : "unknown error";
477002
477167
  return `Error sending message to ${normalized.channel}: ${message}`;
477003
477168
  }
477004
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
+ }
477005
477175
  var init_message_channel_executor = __esm(() => {
477006
477176
  init_message_channel_formatting();
477177
+ init_message_channel_idempotency();
477007
477178
  });
477008
477179
 
477009
477180
  // src/channels/slack/proactive-accounts.ts
@@ -477125,7 +477296,7 @@ function createLocalMessageChannelResolver() {
477125
477296
  }
477126
477297
  };
477127
477298
  }
477128
- async function message_channel(args) {
477299
+ async function message_channel(args, idempotencyScope) {
477129
477300
  if (!getChannelRegistry()) {
477130
477301
  return "Error: Channel system is not initialized. Start with --channels flag.";
477131
477302
  }
@@ -477135,7 +477306,8 @@ async function message_channel(args) {
477135
477306
  return await executeMessageChannel(args, {
477136
477307
  scope: args.parentScope,
477137
477308
  resolver: createLocalMessageChannelResolver(),
477138
- channelTurnSources: args.channelTurnSources
477309
+ channelTurnSources: args.channelTurnSources,
477310
+ idempotencyScope
477139
477311
  });
477140
477312
  }
477141
477313
  var init_message_channel = __esm(() => {
@@ -478074,8 +478246,9 @@ class ChannelGateway {
478074
478246
  this.hooks = hooks;
478075
478247
  this.disposers.push(client.onMessage((message) => this.handleMessage(message)), client.onExternalToolCall((request) => {
478076
478248
  const state = request.runtime ? this.states.get(runtimeKey(request.runtime)) : undefined;
478077
- const sources = state?.active?.sources ?? state?.routedSources ?? [];
478078
- 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);
478079
478252
  }));
478080
478253
  }
478081
478254
  close() {
@@ -478163,7 +478336,8 @@ class ChannelGateway {
478163
478336
  batchId: `channel-recovered-${crypto.randomUUID()}`,
478164
478337
  sources: uniqueSources(sources),
478165
478338
  progress: createChannelTurnProgressBuilder(),
478166
- richDraft: null
478339
+ richDraft: null,
478340
+ idempotencyScope: createMessageChannelIdempotencyScope()
478167
478341
  };
478168
478342
  state.active = recoveredTurn;
478169
478343
  }
@@ -478385,7 +478559,8 @@ class ChannelGateway {
478385
478559
  richDraft: this.hooks.createRichDraft?.({
478386
478560
  batchId: `channel-${clientMessageId}`,
478387
478561
  sources
478388
- }) ?? null
478562
+ }) ?? null,
478563
+ idempotencyScope: createMessageChannelIdempotencyScope()
478389
478564
  };
478390
478565
  const processingEvent = {
478391
478566
  type: "processing",
@@ -478464,6 +478639,7 @@ class ChannelGateway {
478464
478639
  var MAX_ACCEPTED_CLIENT_MESSAGE_IDS = 2048;
478465
478640
  var init_gateway_core = __esm(() => {
478466
478641
  init_interactive_policy();
478642
+ init_message_channel_idempotency();
478467
478643
  init_progress_builder();
478468
478644
  });
478469
478645
 
@@ -479957,7 +480133,7 @@ async function startLocalChannelGateway(options3) {
479957
480133
  buildExternalTool: async (runtime) => {
479958
480134
  return buildGatewayMessageChannelTool(registry2.resolveTurnSourcesForScope(runtime.agent_id, runtime.conversation_id));
479959
480135
  },
479960
- executeExternalTool: async (request, sources) => {
480136
+ executeExternalTool: async (request, sources, idempotencyScope) => {
479961
480137
  if (request.tool_name !== "MessageChannel" || !request.runtime) {
479962
480138
  throw new Error(`Unsupported gateway tool: ${request.tool_name}`);
479963
480139
  }
@@ -479970,9 +480146,10 @@ async function startLocalChannelGateway(options3) {
479970
480146
  conversationId: request.runtime.conversation_id
479971
480147
  },
479972
480148
  channelTurnSources: sources
479973
- });
480149
+ }, idempotencyScope);
479974
480150
  return {
479975
- content: [{ type: "text", text: text2 }]
480151
+ content: [{ type: "text", text: text2 }],
480152
+ is_error: text2.startsWith("Error:")
479976
480153
  };
479977
480154
  },
479978
480155
  onLifecycle: (event2) => registry2.dispatchTurnLifecycleEvent(event2),
@@ -492520,7 +492697,7 @@ var init_mcp_client = __esm(() => {
492520
492697
  init_streamableHttp();
492521
492698
  DEFAULT_CLIENT_INFO = {
492522
492699
  name: "letta-code",
492523
- version: "0.30.9"
492700
+ version: "0.30.11"
492524
492701
  };
492525
492702
  });
492526
492703
 
@@ -559635,4 +559812,4 @@ function registerBunOAuthFlows() {
559635
559812
  registerBunOAuthFlows();
559636
559813
  await init_src5().then(() => exports_src2);
559637
559814
 
559638
- //# debugId=5CF69A1BB16338C364756E2164756E21
559815
+ //# debugId=49F16715F241AC4A64756E2164756E21