@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.
- package/dist/channels-public.js +411 -2
- package/dist/channels-public.js.map +6 -3
- package/dist/channels-slack.js +209 -11
- package/dist/channels-slack.js.map +6 -5
- package/dist/gateway-core.js +110 -13
- package/dist/gateway-core.js.map +6 -5
- package/dist/mcp-client.js +2 -2
- package/dist/mcp-client.js.map +1 -1
- package/dist/schedules.js +4 -2
- package/dist/schedules.js.map +3 -3
- package/dist/types/agent/client-skills.d.ts +2 -0
- package/dist/types/agent/client-skills.d.ts.map +1 -1
- package/dist/types/agent/message.d.ts.map +1 -1
- package/dist/types/backend/local/local-store.d.ts.map +1 -1
- package/dist/types/channels/command-surface.d.ts +54 -0
- package/dist/types/channels/command-surface.d.ts.map +1 -0
- package/dist/types/channels/gateway-core.d.ts +2 -1
- package/dist/types/channels/gateway-core.d.ts.map +1 -1
- package/dist/types/channels/message-channel-executor.d.ts +2 -0
- package/dist/types/channels/message-channel-executor.d.ts.map +1 -1
- package/dist/types/channels/message-channel-idempotency.d.ts +13 -0
- package/dist/types/channels/message-channel-idempotency.d.ts.map +1 -0
- package/dist/types/channels-public.d.ts +4 -0
- package/dist/types/channels-public.d.ts.map +1 -1
- package/dist/types/channels-slack.d.ts +1 -0
- package/dist/types/channels-slack.d.ts.map +1 -1
- package/dist/types/cron/scheduled-task-prompt.d.ts +7 -0
- package/dist/types/cron/scheduled-task-prompt.d.ts.map +1 -1
- package/dist/types/schedules.d.ts +1 -1
- package/dist/types/schedules.d.ts.map +1 -1
- package/dist/types/tools/manager.d.ts.map +1 -1
- package/dist/types/tools/toolset.d.ts +1 -0
- package/dist/types/tools/toolset.d.ts.map +1 -1
- package/dist/types/websocket/listener/runtime.d.ts.map +1 -1
- package/dist/types/websocket/listener/types.d.ts +3 -1
- package/dist/types/websocket/listener/types.d.ts.map +1 -1
- package/letta.js +413 -236
- package/package.json +1 -1
- 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.
|
|
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
|
-
|
|
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
|
|
412295
|
-
|
|
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
|
|
412327
|
-
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
476973
|
-
|
|
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
|
-
|
|
476997
|
-
|
|
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
|
|
478078
|
-
|
|
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.
|
|
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=
|
|
559815
|
+
//# debugId=49F16715F241AC4A64756E2164756E21
|