@letta-ai/letta-code 0.30.5 → 0.30.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/agent-presets.js +3 -13
  2. package/dist/agent-presets.js.map +2 -2
  3. package/dist/channels-slack.js +1 -1
  4. package/dist/channels-slack.js.map +3 -3
  5. package/dist/mcp-client.js +2 -2
  6. package/dist/mcp-client.js.map +1 -1
  7. package/dist/types/agent/subagents/manager.d.ts.map +1 -1
  8. package/dist/types/agent/subagents/subagent-launcher.d.ts +2 -0
  9. package/dist/types/agent/subagents/subagent-launcher.d.ts.map +1 -1
  10. package/dist/types/backend/api/client.d.ts +1 -0
  11. package/dist/types/backend/api/client.d.ts.map +1 -1
  12. package/dist/types/backend/dev/pi-local-endpoint-provider.d.ts.map +1 -1
  13. package/dist/types/backend/dev/pi-model-factory.d.ts.map +1 -1
  14. package/dist/types/backend/dev/pi-models-runtime.d.ts.map +1 -1
  15. package/dist/types/backend/dev/pi-openai-compatible-provider.d.ts +14 -0
  16. package/dist/types/backend/dev/pi-openai-compatible-provider.d.ts.map +1 -0
  17. package/dist/types/backend/dev/pi-provider-registry.d.ts +3 -1
  18. package/dist/types/backend/dev/pi-provider-registry.d.ts.map +1 -1
  19. package/dist/types/backend/local/local-provider-auth-store.d.ts +1 -1
  20. package/dist/types/backend/local/local-provider-auth-store.d.ts.map +1 -1
  21. package/dist/types/channels/accounts.d.ts.map +1 -1
  22. package/dist/types/channels/config.d.ts.map +1 -1
  23. package/dist/types/channels/plugin-types.d.ts +1 -0
  24. package/dist/types/channels/plugin-types.d.ts.map +1 -1
  25. package/dist/types/channels/slack/ingress-controller.d.ts.map +1 -1
  26. package/dist/types/channels/slack/ingress-policy.d.ts +1 -0
  27. package/dist/types/channels/slack/ingress-policy.d.ts.map +1 -1
  28. package/dist/types/channels/types.d.ts +2 -0
  29. package/dist/types/channels/types.d.ts.map +1 -1
  30. package/dist/types/cli/helpers/error-formatter.d.ts +1 -0
  31. package/dist/types/cli/helpers/error-formatter.d.ts.map +1 -1
  32. package/dist/types/providers/byok-providers.d.ts.map +1 -1
  33. package/dist/types/runtime-context.d.ts +2 -0
  34. package/dist/types/runtime-context.d.ts.map +1 -1
  35. package/dist/types/tools/impl/shell-env.d.ts.map +1 -1
  36. package/dist/types/tools/toolset.d.ts +1 -0
  37. package/dist/types/tools/toolset.d.ts.map +1 -1
  38. package/dist/types/websocket/listener/mod-adapter.d.ts +1 -0
  39. package/dist/types/websocket/listener/mod-adapter.d.ts.map +1 -1
  40. package/letta.js +429 -197
  41. package/package.json +1 -1
  42. package/scripts/check-test-mock-isolation.js +5 -1
  43. package/scripts/isolated-unit-tests.json +15 -0
  44. package/scripts/source-file-size-baseline.json +1 -1
  45. package/skills/scheduling-tasks/SKILL.md +58 -54
package/letta.js CHANGED
@@ -5464,7 +5464,7 @@ var package_default;
5464
5464
  var init_package = __esm(() => {
5465
5465
  package_default = {
5466
5466
  name: "@letta-ai/letta-code",
5467
- version: "0.30.5",
5467
+ version: "0.30.6",
5468
5468
  description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
5469
5469
  type: "module",
5470
5470
  packageManager: "bun@1.3.0",
@@ -5746,6 +5746,7 @@ var init_memfs_git_proxy = __esm(() => {
5746
5746
  var exports_client = {};
5747
5747
  __export(exports_client, {
5748
5748
  getServerUrl: () => getServerUrl,
5749
+ getRuntimeEnvironmentDeviceId: () => getRuntimeEnvironmentDeviceId,
5749
5750
  getMemfsServerUrl: () => getMemfsServerUrl,
5750
5751
  getMemfsGitProxyRewriteConfig: () => getMemfsGitProxyRewriteConfig,
5751
5752
  getClientDefaultHeaders: () => getClientDefaultHeaders,
@@ -7125,12 +7126,7 @@ You **MUST** be proactive in creating crons when work extends beyond the current
7125
7126
 
7126
7127
  **Cost**: Self-invocation is critical, but expensive. Default to the longest interval that still serves the user. Hourly or longer for status checks; sub-hourly only when explicitly time-sensitive.
7127
7128
 
7128
- Creating crons:
7129
- - One-shot follow-up: \`letta cron add --name <short-name> --description <description> --prompt <future-message> --at "in 30m"\`
7130
- - Recurring monitoring/heartbeat: \`letta cron add --name <short-name> --description <description> --prompt <future-message> --every "2h"\` or \`--cron "0 9 * * *"\`
7131
- Always include \`--name\`, \`--description\`, and \`--prompt\`. \`$AGENT_ID\` is automatically injected into the shell environment, and \`letta cron\` uses it by default, so you do not need to specify which agent to invoke unless overriding the current agent intentionally.
7132
-
7133
- Where crons run: for cloud agents, schedules default to durable Cloud schedules that fire from the cloud and execute in your cloud sandbox — they survive local shutdown, so this is the right default. If the scheduled work must run on a specific computer (e.g. it needs that computer's filesystem or local services), add \`--computer <deviceId>\` (from \`letta environments list\`) to keep the durable Cloud schedule but execute on that computer, with sandbox fallback if it is offline. Use \`--runner local\` only when that fallback is unacceptable; local schedules only fire while a Letta session is running on that computer.
7129
+ The mechanics — flags, where schedules run and execute, timezone handling — live in the scheduling-tasks skill. Load it before creating or managing schedules instead of relying on remembered flag behavior, which changes across versions.
7134
7130
 
7135
7131
  # Harness Architecture
7136
7132
 
@@ -7310,12 +7306,7 @@ You **MUST** be proactive in creating crons when work extends beyond the current
7310
7306
 
7311
7307
  **Cost**: Self-invocation is critical, but expensive. Default to the longest interval that still serves the user. Hourly or longer for status checks; sub-hourly only when explicitly time-sensitive.
7312
7308
 
7313
- Creating crons:
7314
- - One-shot follow-up: \`letta cron add --name <short-name> --description <description> --prompt <future-message> --at "in 30m"\`
7315
- - Recurring monitoring/heartbeat: \`letta cron add --name <short-name> --description <description> --prompt <future-message> --every "2h"\` or \`--cron "0 9 * * *"\`
7316
- Always include \`--name\`, \`--description\`, and \`--prompt\`. \`$AGENT_ID\` is automatically injected into the shell environment, and \`letta cron\` uses it by default, so you do not need to specify which agent to invoke unless overriding the current agent intentionally.
7317
-
7318
- Where crons run: for cloud agents, schedules default to durable Cloud schedules that fire from the cloud and execute in your cloud sandbox — they survive local shutdown, so this is the right default. If the scheduled work must run on a specific computer (e.g. it needs that computer's filesystem or local services), add \`--computer <deviceId>\` (from \`letta environments list\`) to keep the durable Cloud schedule but execute on that computer, with sandbox fallback if it is offline. Use \`--runner local\` only when that fallback is unacceptable; local schedules only fire while a Letta session is running on that computer.
7309
+ The mechanics — flags, where schedules run and execute, timezone handling — live in the scheduling-tasks skill. Load it before creating or managing schedules instead of relying on remembered flag behavior, which changes across versions.
7319
7310
 
7320
7311
  # Harness Architecture
7321
7312
 
@@ -139326,7 +139317,7 @@ function isProviderConfigured(provider, localProviderNames) {
139326
139317
  function listConfiguredPiProviders(localProviderNames) {
139327
139318
  return PI_PROVIDER_SPECS.filter((provider) => isProviderConfigured(provider, localProviderNames)).map((provider) => provider.id);
139328
139319
  }
139329
- var LOCAL_CHATGPT_PROVIDER_NAME = "chatgpt-plus-pro", LOCAL_OPENAI_PROVIDER_NAME = "lc-openai", LOCAL_ANTHROPIC_PROVIDER_NAME = "lc-anthropic", LOCAL_OPENROUTER_PROVIDER_NAME = "lc-openrouter", LOCAL_OLLAMA_PROVIDER_NAME = "lc-ollama", LOCAL_OLLAMA_CLOUD_PROVIDER_NAME = "lc-ollama-cloud", LOCAL_LMSTUDIO_PROVIDER_NAME = "lc-lmstudio", LMSTUDIO_OPENAI_PROVIDER_TYPE = "lmstudio_openai", LEGACY_LMSTUDIO_PROVIDER_TYPE = "lmstudio", LOCAL_LLAMA_CPP_PROVIDER_NAME = "lc-llama-cpp", LOCAL_ZAI_PROVIDER_NAME = "lc-zai", LOCAL_ZAI_CODING_PROVIDER_NAME = "lc-zai-coding", LOCAL_MINIMAX_PROVIDER_NAME = "lc-minimax", LOCAL_MOONSHOT_PROVIDER_NAME = "lc-moonshot", LOCAL_KIMI_CODE_PROVIDER_NAME = "lc-kimi-code", LOCAL_GOOGLE_AI_PROVIDER_NAME = "lc-gemini", LOCAL_BEDROCK_PROVIDER_NAME = "lc-bedrock", prefixedCatalogModelHandle = (prefix) => (model) => `${prefix}${model.id}`, PI_PROVIDER_ALIASES, PI_TUI_DEFAULT_MODEL_IDS, PI_TUI_DEFAULTLESS_PROVIDER_IDS, PI_PROVIDER_OVERRIDES, LOCAL_ENDPOINT_PROVIDER_SPECS, PI_PROVIDER_SPECS, SUPPORTED_LOCAL_PROVIDER_TYPES, KNOWN_PI_PROVIDERS, PROVIDER_TYPE_TO_BASE_PROVIDER;
139320
+ var LOCAL_CHATGPT_PROVIDER_NAME = "chatgpt-plus-pro", LOCAL_OPENAI_PROVIDER_NAME = "lc-openai", LOCAL_OPENAI_COMPATIBLE_PROVIDER_NAME = "lc-openai-compatible", OPENAI_COMPATIBLE_PI_PROVIDER_ID = "openai-compatible", LOCAL_ANTHROPIC_PROVIDER_NAME = "lc-anthropic", LOCAL_OPENROUTER_PROVIDER_NAME = "lc-openrouter", LOCAL_OLLAMA_PROVIDER_NAME = "lc-ollama", LOCAL_OLLAMA_CLOUD_PROVIDER_NAME = "lc-ollama-cloud", LOCAL_LMSTUDIO_PROVIDER_NAME = "lc-lmstudio", LMSTUDIO_OPENAI_PROVIDER_TYPE = "lmstudio_openai", LEGACY_LMSTUDIO_PROVIDER_TYPE = "lmstudio", LOCAL_LLAMA_CPP_PROVIDER_NAME = "lc-llama-cpp", LOCAL_ZAI_PROVIDER_NAME = "lc-zai", LOCAL_ZAI_CODING_PROVIDER_NAME = "lc-zai-coding", LOCAL_MINIMAX_PROVIDER_NAME = "lc-minimax", LOCAL_MOONSHOT_PROVIDER_NAME = "lc-moonshot", LOCAL_KIMI_CODE_PROVIDER_NAME = "lc-kimi-code", LOCAL_GOOGLE_AI_PROVIDER_NAME = "lc-gemini", LOCAL_BEDROCK_PROVIDER_NAME = "lc-bedrock", prefixedCatalogModelHandle = (prefix) => (model) => `${prefix}${model.id}`, PI_PROVIDER_ALIASES, PI_TUI_DEFAULT_MODEL_IDS, PI_TUI_DEFAULTLESS_PROVIDER_IDS, PI_PROVIDER_OVERRIDES, LOCAL_ENDPOINT_PROVIDER_SPECS, PI_PROVIDER_SPECS, SUPPORTED_LOCAL_PROVIDER_TYPES, KNOWN_PI_PROVIDERS, PROVIDER_TYPE_TO_BASE_PROVIDER;
139330
139321
  var init_pi_provider_registry = __esm(() => {
139331
139322
  init_compat2();
139332
139323
  init_all();
@@ -139473,6 +139464,18 @@ var init_pi_provider_registry = __esm(() => {
139473
139464
  envConfigured: () => hasEnvValue(process.env.OLLAMA_API_KEY),
139474
139465
  createCustomModel: true
139475
139466
  },
139467
+ {
139468
+ id: OPENAI_COMPATIBLE_PI_PROVIDER_ID,
139469
+ providerTypes: ["openai-compatible"],
139470
+ handlePrefixes: ["openai-compatible/"],
139471
+ localProviderNames: [
139472
+ OPENAI_COMPATIBLE_PI_PROVIDER_ID,
139473
+ LOCAL_OPENAI_COMPATIBLE_PROVIDER_NAME
139474
+ ],
139475
+ fallbackApiKey: "not-needed",
139476
+ localModelDiscovery: "openai-compatible",
139477
+ createCustomModel: true
139478
+ },
139476
139479
  {
139477
139480
  id: "lmstudio",
139478
139481
  providerTypes: [
@@ -139595,6 +139598,10 @@ async function createOrUpdateLocalProvider(input) {
139595
139598
  }
139596
139599
  const file3 = readAuthFile(input.storageDir);
139597
139600
  const existing = file3.providers[input.providerName];
139601
+ const baseURL = input.baseURL ?? existing?.base_url;
139602
+ if (input.providerType === OPENAI_COMPATIBLE_PI_PROVIDER_ID && !baseURL?.trim()) {
139603
+ throw new Error("OpenAI-compatible API requires a base URL.");
139604
+ }
139598
139605
  const now = new Date().toISOString();
139599
139606
  const auth = input.providerType === "chatgpt_oauth" ? parseChatGPTOAuth(input.apiKey) : { type: "api", key: input.apiKey };
139600
139607
  const next = {
@@ -139606,7 +139613,7 @@ async function createOrUpdateLocalProvider(input) {
139606
139613
  ...input.accessKey ? { access_key: input.accessKey } : {},
139607
139614
  ...input.region ? { region: input.region } : {},
139608
139615
  ...input.profile ? { profile: input.profile } : {},
139609
- ...input.baseURL ?? existing?.base_url ? { base_url: input.baseURL ?? existing?.base_url } : {},
139616
+ ...baseURL ? { base_url: baseURL } : {},
139610
139617
  ...input.timeout !== undefined ? { timeout: input.timeout } : existing?.timeout !== undefined ? { timeout: existing.timeout } : {},
139611
139618
  created_at: existing?.created_at ?? now,
139612
139619
  updated_at: now
@@ -140225,6 +140232,20 @@ var init_byok_providers = __esm(() => {
140225
140232
  providerName: "ollama-cloud",
140226
140233
  providerNames: ["ollama-cloud", "lc-ollama-cloud"]
140227
140234
  },
140235
+ {
140236
+ id: "openai-compatible",
140237
+ displayName: "OpenAI-compatible API",
140238
+ description: "Connect an OpenAI-compatible Chat Completions endpoint",
140239
+ providerType: "openai-compatible",
140240
+ providerName: "openai-compatible",
140241
+ providerNames: ["openai-compatible", "lc-openai-compatible"],
140242
+ requiresApiKey: false,
140243
+ defaultApiKey: LOCAL_PROVIDER_NO_API_KEY,
140244
+ fields: [
140245
+ { key: "apiKey", label: "API Key", secret: true, required: false },
140246
+ { key: "baseUrl", label: "Base URL" }
140247
+ ]
140248
+ },
140228
140249
  {
140229
140250
  id: "lmstudio",
140230
140251
  displayName: "LM Studio (local)",
@@ -146165,6 +146186,7 @@ var init_config2 = __esm(() => {
146165
146186
  allowedUsers: parsed.allowed_users ?? [],
146166
146187
  transcribeVoice: parsed.transcribe_voice === true,
146167
146188
  listenMode: parsed.listen_mode === true,
146189
+ mentionOnlyChannels: Array.isArray(parsed.mention_only_channels) ? parsed.mention_only_channels.filter((channelId) => typeof channelId === "string") : [],
146168
146190
  allowBots: normalizeSlackAllowBotsMode(parsed.allow_bots)
146169
146191
  };
146170
146192
  }
@@ -150405,6 +150427,11 @@ function cloneAccount(account) {
150405
150427
  if (isTelegramChannelAccount(account)) {
150406
150428
  cloned.binding = { ...account.binding };
150407
150429
  }
150430
+ if (isSlackChannelAccount(account)) {
150431
+ cloned.mentionOnlyChannels = [
150432
+ ...account.mentionOnlyChannels ?? []
150433
+ ];
150434
+ }
150408
150435
  if (isDiscordChannelAccount(account) && account.allowedChannels) {
150409
150436
  cloned.allowedChannels = Array.isArray(account.allowedChannels) ? [...account.allowedChannels] : { ...account.allowedChannels };
150410
150437
  }
@@ -150474,6 +150501,8 @@ function normalizeLoadedAccount(account) {
150474
150501
  delete next.progress_ui;
150475
150502
  delete next.progressUi;
150476
150503
  next.listenMode = next.listenMode === true;
150504
+ const mentionOnlyChannels = next.mentionOnlyChannels;
150505
+ next.mentionOnlyChannels = Array.isArray(mentionOnlyChannels) ? [...mentionOnlyChannels] : [];
150477
150506
  next.allowBots = normalizeSlackAllowBotsMode(next.allowBots);
150478
150507
  }
150479
150508
  if (isDiscordChannelAccount(next)) {
@@ -150623,6 +150652,7 @@ function makeDefaultLegacyAccount(channelId) {
150623
150652
  defaultPermissionMode: DEFAULT_SLACK_PERMISSION_MODE,
150624
150653
  transcribeVoice: config3.transcribeVoice === true,
150625
150654
  listenMode: config3.listenMode === true,
150655
+ mentionOnlyChannels: [...config3.mentionOnlyChannels ?? []],
150626
150656
  allowBots: config3.allowBots ?? false,
150627
150657
  createdAt: now,
150628
150658
  updatedAt: now
@@ -150822,6 +150852,7 @@ var init_accounts = __esm(() => {
150822
150852
  group_mode: "groupMode",
150823
150853
  inbound_debounce_ms: "inboundDebounceMs",
150824
150854
  listen_mode: "listenMode",
150855
+ mention_only_channels: "mentionOnlyChannels",
150825
150856
  message_prefix: "messagePrefix",
150826
150857
  media_max_bytes: "mediaMaxBytes",
150827
150858
  attachment_filter: "attachmentFilter",
@@ -154533,6 +154564,9 @@ var init_inbound_debounce = __esm(() => {
154533
154564
  });
154534
154565
 
154535
154566
  // src/channels/slack/ingress-policy.ts
154567
+ function isSlackMentionOnlyChannel(channelId, mentionOnlyChannels) {
154568
+ return mentionOnlyChannels?.includes(channelId) === true;
154569
+ }
154536
154570
  function hasRecordValue(value) {
154537
154571
  return value !== null && typeof value === "object";
154538
154572
  }
@@ -154763,6 +154797,9 @@ function createSlackIngressController(params) {
154763
154797
  })) {
154764
154798
  return;
154765
154799
  }
154800
+ if (basePolicy.chatType === "channel" && !basePolicy.wasMentioned && isSlackMentionOnlyChannel(channelId, config3.mentionOnlyChannels)) {
154801
+ return;
154802
+ }
154766
154803
  const attachments = await resolveSlackInboundAttachments({
154767
154804
  accountId: config3.accountId,
154768
154805
  token: config3.botToken,
@@ -154944,6 +154981,9 @@ function createSlackIngressController(params) {
154944
154981
  return;
154945
154982
  }
154946
154983
  const chatType = resolveSlackChatType(chatId);
154984
+ if (chatType === "channel" && isSlackMentionOnlyChannel(chatId, config3.mentionOnlyChannels)) {
154985
+ return;
154986
+ }
154947
154987
  const threadId = chatType === "channel" ? knownThreadIdsByMessageId.get(targetMessageId) ?? targetMessageId : knownThreadIdsByMessageId.get(targetMessageId) ?? null;
154948
154988
  try {
154949
154989
  await adapter.onMessage({
@@ -173673,6 +173713,10 @@ function getShellEnv() {
173673
173713
  env3[pathKey] = existingPath ? `${pathPrefixes.join(shellPathDelimiter())}${shellPathDelimiter()}${existingPath}` : pathPrefixes.join(shellPathDelimiter());
173674
173714
  }
173675
173715
  env3.USER_CWD = getCurrentWorkingDirectory();
173716
+ const environmentDeviceId = getRuntimeContext()?.environmentDeviceId?.trim();
173717
+ if (environmentDeviceId) {
173718
+ env3.LETTA_RUNTIME_ENVIRONMENT_DEVICE_ID = environmentDeviceId;
173719
+ }
173676
173720
  let agentId;
173677
173721
  try {
173678
173722
  const resolvedAgentId = getCurrentAgentId();
@@ -348867,9 +348911,21 @@ import { join as join34 } from "node:path";
348867
348911
  async function getUnavailableListenerClient() {
348868
348912
  throw new Error("letta.client is not available in listener mods");
348869
348913
  }
348914
+ function resolveListenerAgentMemfsContext(agentId) {
348915
+ if (!agentId)
348916
+ return { enabled: false, memoryDir: null };
348917
+ try {
348918
+ return settingsManager.isMemfsEnabled(agentId) ? {
348919
+ enabled: true,
348920
+ memoryDir: getScopedMemoryFilesystemRoot(agentId)
348921
+ } : { enabled: false, memoryDir: null };
348922
+ } catch {
348923
+ return { enabled: false, memoryDir: null };
348924
+ }
348925
+ }
348870
348926
  function createListenerModContext(options3 = {}) {
348871
348927
  const cwd = options3.workingDirectory ?? getCurrentWorkingDirectory();
348872
- return buildModInvocationContext({
348928
+ const context3 = buildModInvocationContext({
348873
348929
  agent: options3.agent ?? null,
348874
348930
  conversationId: options3.sessionId ?? null,
348875
348931
  modelIdentifier: options3.modelIdentifier ?? null,
@@ -348877,6 +348933,13 @@ function createListenerModContext(options3 = {}) {
348877
348933
  toolset: options3.toolset ?? null,
348878
348934
  workingDirectory: cwd
348879
348935
  });
348936
+ return {
348937
+ ...context3,
348938
+ memfs: resolveListenerAgentMemfsContext(context3.agent.id)
348939
+ };
348940
+ }
348941
+ function createListenerAgentModContext(agentId) {
348942
+ return createListenerModContext({ agent: { id: agentId } });
348880
348943
  }
348881
348944
  function createListenerModAdapter(options3 = {}) {
348882
348945
  return createModAdapter({
@@ -364865,6 +364928,7 @@ function composeSubagentChildEnv(options3) {
364865
364928
  backendMode,
364866
364929
  localBackendStorageDir,
364867
364930
  parentAgentId,
364931
+ subagentType,
364868
364932
  launchProfile,
364869
364933
  inheritedPrimaryRoot,
364870
364934
  memoryScope,
@@ -364878,6 +364942,7 @@ function composeSubagentChildEnv(options3) {
364878
364942
  ...inheritedApiKey && { LETTA_API_KEY: inheritedApiKey },
364879
364943
  ...inheritedBaseUrl && { LETTA_BASE_URL: inheritedBaseUrl },
364880
364944
  LETTA_CODE_AGENT_ROLE: "subagent",
364945
+ ...subagentType === "reflection" && { [LETTA_DISABLE_MODS_ENV]: "1" },
364881
364946
  ...parentAgentId && { LETTA_PARENT_AGENT_ID: parentAgentId },
364882
364947
  ...transcriptPath && { TRANSCRIPT_PATH: transcriptPath },
364883
364948
  ...inheritedChannelContext && {
@@ -365410,6 +365475,7 @@ async function executeSubagent(type3, config3, model, userPrompt, subagentId, is
365410
365475
  backendMode,
365411
365476
  localBackendStorageDir,
365412
365477
  parentAgentId,
365478
+ subagentType: type3,
365413
365479
  launchProfile: effectiveLaunchProfile,
365414
365480
  inheritedPrimaryRoot,
365415
365481
  memoryScope,
@@ -373761,6 +373827,7 @@ function parseInheritedChannelContextEnv() {
373761
373827
  async function prepareToolExecutionContextForScope(params) {
373762
373828
  const {
373763
373829
  connectionId,
373830
+ environmentDeviceId,
373764
373831
  agentId,
373765
373832
  conversationId,
373766
373833
  overrideModel,
@@ -373826,6 +373893,7 @@ async function prepareToolExecutionContextForScope(params) {
373826
373893
  agent: agent2,
373827
373894
  runtimeContext: {
373828
373895
  connectionId,
373896
+ environmentDeviceId,
373829
373897
  agentId,
373830
373898
  agentName: agent2.name ?? null,
373831
373899
  conversationId: scopedConversationId,
@@ -375025,13 +375093,13 @@ echo password=${token}
375025
375093
  } else {
375026
375094
  helper = `!f() { echo "username=letta"; echo "password=${token}"; }; f`;
375027
375095
  }
375028
- await gitConfig(dir, [
375029
- "config",
375030
- `credential.${normalizedBaseUrl}.helper`,
375031
- helper
375032
- ]);
375096
+ const writeHelperWithReset = async (key) => {
375097
+ await gitConfig(dir, ["config", "--local", "--replace-all", key, ""]);
375098
+ await gitConfig(dir, ["config", "--local", "--add", key, helper]);
375099
+ };
375100
+ await writeHelperWithReset(`credential.${normalizedBaseUrl}.helper`);
375033
375101
  if (rawBaseUrl !== normalizedBaseUrl) {
375034
- await gitConfig(dir, ["config", `credential.${rawBaseUrl}.helper`, helper]);
375102
+ await writeHelperWithReset(`credential.${rawBaseUrl}.helper`);
375035
375103
  }
375036
375104
  debugLog("memfs-git", `Configured local credential helper for ${normalizedBaseUrl}${rawBaseUrl !== normalizedBaseUrl ? ` (and raw ${rawBaseUrl})` : ""}`);
375037
375105
  }
@@ -379215,6 +379283,7 @@ function isEncryptedContentError(e2) {
379215
379283
  function formatErrorDetails2(e2, agentId, conversationId, options3 = {}) {
379216
379284
  let runId;
379217
379285
  const surface = options3.surface ?? "terminal";
379286
+ const fallback = (raw) => options3.unclassifiedFallback === "generic" ? GENERIC_ERROR_MESSAGE : raw;
379218
379287
  const encryptedContentMsg = checkEncryptedContentError(e2);
379219
379288
  if (encryptedContentMsg)
379220
379289
  return encryptedContentMsg;
@@ -379303,6 +379372,8 @@ Detail: ${detail}` : "";
379303
379372
  const conversationBusyMessage = formatConversationBusyErrorForDisplay(baseError, runId, options3);
379304
379373
  if (conversationBusyMessage)
379305
379374
  return conversationBusyMessage;
379375
+ if (options3.unclassifiedFallback === "generic")
379376
+ return fallback(baseError);
379306
379377
  return runId && agentId ? `${baseError}
379307
379378
  ${createAgentLink(runId, agentId, conversationId, surface)}` : baseError;
379308
379379
  }
@@ -379316,32 +379387,34 @@ ${createAgentLink(runId, agentId, conversationId, surface)}` : baseError;
379316
379387
  const conversationBusyMessage = formatConversationBusyErrorForDisplay(baseError, runId, options3);
379317
379388
  if (conversationBusyMessage)
379318
379389
  return conversationBusyMessage;
379390
+ if (options3.unclassifiedFallback === "generic")
379391
+ return fallback(baseError);
379319
379392
  return runId && agentId ? `${baseError}
379320
379393
  ${createAgentLink(runId, agentId, conversationId, surface)}` : baseError;
379321
379394
  }
379322
- return e2.message;
379395
+ return fallback(e2.message);
379323
379396
  }
379324
379397
  if (e2 instanceof Error) {
379325
- return e2.message;
379398
+ return fallback(e2.message);
379326
379399
  }
379327
379400
  if (typeof e2 === "object" && e2 !== null) {
379328
379401
  const obj = e2;
379329
379402
  if (typeof obj.message === "string") {
379330
- return obj.message;
379403
+ return fallback(obj.message);
379331
379404
  }
379332
379405
  if (typeof obj.error === "string") {
379333
- return obj.error;
379406
+ return fallback(obj.error);
379334
379407
  }
379335
379408
  if (typeof obj.detail === "string") {
379336
- return obj.detail;
379409
+ return fallback(obj.detail);
379337
379410
  }
379338
379411
  try {
379339
- return JSON.stringify(e2, null, 2);
379412
+ return fallback(JSON.stringify(e2, null, 2));
379340
379413
  } catch {
379341
- return "[Error: Unable to serialize error object]";
379414
+ return fallback("[Error: Unable to serialize error object]");
379342
379415
  }
379343
379416
  }
379344
- return String(e2);
379417
+ return fallback(String(e2));
379345
379418
  }
379346
379419
  function getRetryStatusMessage(errorDetail2) {
379347
379420
  if (!errorDetail2)
@@ -379373,7 +379446,7 @@ function createAgentLink(runId, agentId, conversationId, surface) {
379373
379446
  const agentRef = surface === "terminal" ? buildAgentTerminalLink(agentId, { conversationId }) : agentId;
379374
379447
  return `View agent: ${agentRef} (run: ${runId})`;
379375
379448
  }
379376
- var LETTA_USAGE_URL, LETTA_AGENTS_URL, CLOUDFLARE_EDGE_5XX_MARKER_PATTERN, CLOUDFLARE_EDGE_5XX_TITLE_PATTERN, CLOUDFLARE_EDGE_5XX_FORMATTED_PATTERN, CLOUDFLARE_JSON_5XX_PATTERN, CHATGPT_USAGE_LIMIT_HINT = "Switch models with /model, or connect your own provider keys with /connect.", ENCRYPTED_CONTENT_HINT, DEFAULT_RETRY_MESSAGE = "Unexpected downstream LLM API error, retrying...", ENDPOINT_TYPE_DISPLAY_NAMES;
379449
+ var LETTA_USAGE_URL, LETTA_AGENTS_URL, GENERIC_ERROR_MESSAGE = "The request failed. Please try again.", CLOUDFLARE_EDGE_5XX_MARKER_PATTERN, CLOUDFLARE_EDGE_5XX_TITLE_PATTERN, CLOUDFLARE_EDGE_5XX_FORMATTED_PATTERN, CLOUDFLARE_JSON_5XX_PATTERN, CHATGPT_USAGE_LIMIT_HINT = "Switch models with /model, or connect your own provider keys with /connect.", ENCRYPTED_CONTENT_HINT, DEFAULT_RETRY_MESSAGE = "Unexpected downstream LLM API error, retrying...", ENDPOINT_TYPE_DISPLAY_NAMES;
379377
379450
  var init_error_formatter = __esm(() => {
379378
379451
  init_error();
379379
379452
  init_conversation_busy_error();
@@ -380797,7 +380870,10 @@ function createLocalEndpointPiProvider(options3) {
380797
380870
  name: `${options3.name} API key`,
380798
380871
  resolve: async ({ credential }) => {
380799
380872
  const apiKey = credential?.key ?? options3.apiKey;
380800
- return apiKey ? { auth: { apiKey }, source: "local provider record" } : undefined;
380873
+ return apiKey ? {
380874
+ auth: apiKey === "not-needed" ? { apiKey, headers: { Authorization: null } } : { apiKey },
380875
+ source: "local provider record"
380876
+ } : undefined;
380801
380877
  }
380802
380878
  }
380803
380879
  },
@@ -381158,6 +381234,27 @@ var init_pi_ollama_provider = __esm(() => {
381158
381234
  init_pi_local_endpoint_provider();
381159
381235
  });
381160
381236
 
381237
+ // src/backend/dev/pi-openai-compatible-provider.ts
381238
+ function createOpenAICompatiblePiProvider(options3) {
381239
+ return createLocalEndpointPiProvider({
381240
+ id: OPENAI_COMPATIBLE_PI_PROVIDER_ID,
381241
+ name: "OpenAI-compatible API",
381242
+ baseURL: options3.baseURL,
381243
+ ...options3.apiKey ? { apiKey: options3.apiKey } : {},
381244
+ ...options3.fetchImpl ? { fetchImpl: options3.fetchImpl } : {},
381245
+ ...options3.discoveryTimeoutMs ? { discoveryTimeoutMs: options3.discoveryTimeoutMs } : {},
381246
+ discover: openAICompatibleDiscover
381247
+ });
381248
+ }
381249
+ var openAICompatibleDiscover = async (context3) => {
381250
+ const list = await context3.fetchJson(`${context3.openAIBaseURL}/models`);
381251
+ return modelIdsFromOpenAICompatibleList(list).map((modelId) => context3.lastKnown.get(modelId) ?? context3.buildModel({ id: modelId }));
381252
+ };
381253
+ var init_pi_openai_compatible_provider = __esm(() => {
381254
+ init_pi_local_endpoint_provider();
381255
+ init_pi_provider_registry();
381256
+ });
381257
+
381161
381258
  // src/backend/dev/pi-models-runtime.ts
381162
381259
  function aliasedAuthContext() {
381163
381260
  const base2 = defaultProviderAuthContext();
@@ -381185,7 +381282,7 @@ function resolveLocalEndpointConnection(providerId2, storageDir) {
381185
381282
  break;
381186
381283
  }
381187
381284
  const baseURL = record5?.base_url ?? spec.baseUrlEnv?.() ?? spec.defaultBaseURL ?? "";
381188
- const apiKey = localProviderApiKeyFromRecord(record5) ?? spec.apiKeyEnv?.() ?? spec.fallbackApiKey;
381285
+ const apiKey = localProviderApiKeyFromRecord(record5) ?? spec.apiKeyEnv?.() ?? (record5 !== null || spec.autoDetectLocalEndpoint === true ? spec.fallbackApiKey : undefined);
381189
381286
  return {
381190
381287
  baseURL,
381191
381288
  apiKey,
@@ -381424,6 +381521,7 @@ var init_pi_models_runtime = __esm(() => {
381424
381521
  init_pi_lmstudio_provider();
381425
381522
  init_pi_mod_provider();
381426
381523
  init_pi_ollama_provider();
381524
+ init_pi_openai_compatible_provider();
381427
381525
  init_pi_provider_mod_registry();
381428
381526
  init_pi_provider_registry();
381429
381527
  init_registered_pi_provider_runtime();
@@ -381440,7 +381538,11 @@ var init_pi_models_runtime = __esm(() => {
381440
381538
  })
381441
381539
  ],
381442
381540
  [LLAMA_CPP_PI_PROVIDER_ID, (input) => createLlamaCppPiProvider(input)],
381443
- [LMSTUDIO_PI_PROVIDER_ID, (input) => createLmStudioPiProvider(input)]
381541
+ [LMSTUDIO_PI_PROVIDER_ID, (input) => createLmStudioPiProvider(input)],
381542
+ [
381543
+ OPENAI_COMPATIBLE_PI_PROVIDER_ID,
381544
+ (input) => createOpenAICompatiblePiProvider(input)
381545
+ ]
381444
381546
  ]);
381445
381547
  });
381446
381548
 
@@ -381674,7 +381776,10 @@ async function resolvePiModelForAgent(modelHandle, modelSettings = {}, options3
381674
381776
  }
381675
381777
  const fallbackModelId = !registeredProvider && spec?.piProvider ? fallbackCatalogModelId(spec.piProvider, modelId) : undefined;
381676
381778
  const { model: publishedModel, auth: authResult } = await modelsRuntime.resolveTurn(runtimeProviderId, modelId, fallbackModelId);
381677
- connection = { ...connection, apiKey: authResult?.auth.apiKey };
381779
+ connection = {
381780
+ ...connection,
381781
+ apiKey: authResult?.auth.apiKey === LOCAL_PROVIDER_NO_API_KEY ? undefined : authResult?.auth.apiKey
381782
+ };
381678
381783
  if (authResult?.auth.baseUrl)
381679
381784
  baseURL = authResult.auth.baseUrl;
381680
381785
  if (authResult?.auth.headers) {
@@ -426741,6 +426846,9 @@ function isNullableString4(value) {
426741
426846
  function isBoolean3(value) {
426742
426847
  return value === true || value === false;
426743
426848
  }
426849
+ function isStringArray5(value) {
426850
+ return Array.isArray(value) && value.every(isString3);
426851
+ }
426744
426852
  function isDefaultPermissionMode2(value) {
426745
426853
  return value === "standard" || value === "acceptEdits" || value === "unrestricted" || value === "default" || value === "bypassPermissions" || value === "fullAccess";
426746
426854
  }
@@ -426757,6 +426865,7 @@ var init_account_config4 = __esm(() => {
426757
426865
  "transcribe_voice",
426758
426866
  "show_completed_reaction",
426759
426867
  "listen_mode",
426868
+ "mention_only_channels",
426760
426869
  "allow_bots"
426761
426870
  ]);
426762
426871
  slackAccountConfigAdapter = {
@@ -426766,7 +426875,7 @@ var init_account_config4 = __esm(() => {
426766
426875
  return false;
426767
426876
  }
426768
426877
  }
426769
- return (config3.bot_token === undefined || isString3(config3.bot_token)) && (config3.app_token === undefined || isString3(config3.app_token)) && (config3.mode === undefined || config3.mode === "socket") && (config3.agent_id === undefined || isNullableString4(config3.agent_id)) && (config3.default_permission_mode === undefined || isDefaultPermissionMode2(config3.default_permission_mode)) && (config3.transcribe_voice === undefined || isBoolean3(config3.transcribe_voice)) && (config3.show_completed_reaction === undefined || isBoolean3(config3.show_completed_reaction)) && (config3.listen_mode === undefined || isBoolean3(config3.listen_mode)) && isValidSlackAllowBotsConfigValue(config3.allow_bots);
426878
+ return (config3.bot_token === undefined || isString3(config3.bot_token)) && (config3.app_token === undefined || isString3(config3.app_token)) && (config3.mode === undefined || config3.mode === "socket") && (config3.agent_id === undefined || isNullableString4(config3.agent_id)) && (config3.default_permission_mode === undefined || isDefaultPermissionMode2(config3.default_permission_mode)) && (config3.transcribe_voice === undefined || isBoolean3(config3.transcribe_voice)) && (config3.show_completed_reaction === undefined || isBoolean3(config3.show_completed_reaction)) && (config3.listen_mode === undefined || isBoolean3(config3.listen_mode)) && (config3.mention_only_channels === undefined || isStringArray5(config3.mention_only_channels)) && isValidSlackAllowBotsConfigValue(config3.allow_bots);
426770
426879
  },
426771
426880
  toAccountPatch(config3) {
426772
426881
  return {
@@ -426777,6 +426886,7 @@ var init_account_config4 = __esm(() => {
426777
426886
  defaultPermissionMode: isDefaultPermissionMode2(config3.default_permission_mode) ? migratePermissionMode(config3.default_permission_mode) : undefined,
426778
426887
  transcribeVoice: isBoolean3(config3.transcribe_voice) ? config3.transcribe_voice : undefined,
426779
426888
  listenMode: isBoolean3(config3.listen_mode) ? config3.listen_mode : undefined,
426889
+ mentionOnlyChannels: isStringArray5(config3.mention_only_channels) ? [...config3.mention_only_channels] : undefined,
426780
426890
  allowBots: config3.allow_bots !== undefined && isValidSlackAllowBotsConfigValue(config3.allow_bots) ? normalizeSlackAllowBotsMode(config3.allow_bots) : undefined
426781
426891
  };
426782
426892
  },
@@ -426789,6 +426899,7 @@ var init_account_config4 = __esm(() => {
426789
426899
  default_permission_mode: account.defaultPermissionMode ?? DEFAULT_SLACK_PERMISSION_MODE,
426790
426900
  transcribe_voice: account.transcribeVoice === true,
426791
426901
  listen_mode: account.listenMode === true,
426902
+ mention_only_channels: [...account.mentionOnlyChannels ?? []],
426792
426903
  allow_bots: account.allowBots ?? false
426793
426904
  };
426794
426905
  },
@@ -426801,6 +426912,7 @@ var init_account_config4 = __esm(() => {
426801
426912
  default_permission_mode: account.defaultPermissionMode ?? DEFAULT_SLACK_PERMISSION_MODE,
426802
426913
  transcribe_voice: account.transcribeVoice === true,
426803
426914
  listen_mode: account.listenMode === true,
426915
+ mention_only_channels: [...account.mentionOnlyChannels ?? []],
426804
426916
  allow_bots: account.allowBots ?? false
426805
426917
  };
426806
426918
  },
@@ -426890,7 +427002,7 @@ function isNullableString5(value) {
426890
427002
  function isBoolean5(value) {
426891
427003
  return typeof value === "boolean";
426892
427004
  }
426893
- function isStringArray5(value) {
427005
+ function isStringArray6(value) {
426894
427006
  return Array.isArray(value) && value.every((entry) => typeof entry === "string");
426895
427007
  }
426896
427008
  function isGroupMode3(value) {
@@ -426936,22 +427048,22 @@ var init_account_config6 = __esm(() => {
426936
427048
  return false;
426937
427049
  }
426938
427050
  }
426939
- return (config3.agent_id === undefined || isNullableString5(config3.agent_id)) && (config3.self_chat_mode === undefined || isBoolean5(config3.self_chat_mode)) && (config3.group_mode === undefined || isGroupMode3(config3.group_mode)) && (config3.allowed_groups === undefined || isStringArray5(config3.allowed_groups)) && (config3.mention_patterns === undefined || isStringArray5(config3.mention_patterns)) && (config3.transcribe_voice === undefined || isBoolean5(config3.transcribe_voice)) && (config3.download_media === undefined || isBoolean5(config3.download_media)) && (config3.media_max_bytes === undefined || isPositiveNumber2(config3.media_max_bytes)) && (config3.attachment_filter === undefined || isBoolean5(config3.attachment_filter)) && (config3.attachment_mime_types === undefined || isStringArray5(config3.attachment_mime_types)) && (config3.attachment_allowed_recipients === undefined || isStringArray5(config3.attachment_allowed_recipients)) && (config3.attachment_allowed_paths === undefined || isStringArray5(config3.attachment_allowed_paths)) && (config3.attachment_path_recursive === undefined || isBoolean5(config3.attachment_path_recursive)) && (config3.inbound_debounce_ms === undefined || isValidInboundDebounceMs(config3.inbound_debounce_ms)) && (config3.waiting_behavior === undefined || isWaitingBehavior(config3.waiting_behavior)) && (config3.message_prefix === undefined || isString5(config3.message_prefix));
427051
+ return (config3.agent_id === undefined || isNullableString5(config3.agent_id)) && (config3.self_chat_mode === undefined || isBoolean5(config3.self_chat_mode)) && (config3.group_mode === undefined || isGroupMode3(config3.group_mode)) && (config3.allowed_groups === undefined || isStringArray6(config3.allowed_groups)) && (config3.mention_patterns === undefined || isStringArray6(config3.mention_patterns)) && (config3.transcribe_voice === undefined || isBoolean5(config3.transcribe_voice)) && (config3.download_media === undefined || isBoolean5(config3.download_media)) && (config3.media_max_bytes === undefined || isPositiveNumber2(config3.media_max_bytes)) && (config3.attachment_filter === undefined || isBoolean5(config3.attachment_filter)) && (config3.attachment_mime_types === undefined || isStringArray6(config3.attachment_mime_types)) && (config3.attachment_allowed_recipients === undefined || isStringArray6(config3.attachment_allowed_recipients)) && (config3.attachment_allowed_paths === undefined || isStringArray6(config3.attachment_allowed_paths)) && (config3.attachment_path_recursive === undefined || isBoolean5(config3.attachment_path_recursive)) && (config3.inbound_debounce_ms === undefined || isValidInboundDebounceMs(config3.inbound_debounce_ms)) && (config3.waiting_behavior === undefined || isWaitingBehavior(config3.waiting_behavior)) && (config3.message_prefix === undefined || isString5(config3.message_prefix));
426940
427052
  },
426941
427053
  toAccountPatch(config3) {
426942
427054
  return {
426943
427055
  agentId: isNullableString5(config3.agent_id) ? config3.agent_id : undefined,
426944
427056
  selfChatMode: isBoolean5(config3.self_chat_mode) ? config3.self_chat_mode : undefined,
426945
427057
  groupMode: isGroupMode3(config3.group_mode) ? config3.group_mode : undefined,
426946
- allowedGroups: isStringArray5(config3.allowed_groups) ? [...config3.allowed_groups] : undefined,
426947
- mentionPatterns: isStringArray5(config3.mention_patterns) ? [...config3.mention_patterns] : undefined,
427058
+ allowedGroups: isStringArray6(config3.allowed_groups) ? [...config3.allowed_groups] : undefined,
427059
+ mentionPatterns: isStringArray6(config3.mention_patterns) ? [...config3.mention_patterns] : undefined,
426948
427060
  transcribeVoice: isBoolean5(config3.transcribe_voice) ? config3.transcribe_voice : undefined,
426949
427061
  downloadMedia: isBoolean5(config3.download_media) ? config3.download_media : undefined,
426950
427062
  mediaMaxBytes: isPositiveNumber2(config3.media_max_bytes) ? config3.media_max_bytes : undefined,
426951
427063
  attachmentFilter: isBoolean5(config3.attachment_filter) ? config3.attachment_filter : undefined,
426952
- attachmentMimeTypes: isStringArray5(config3.attachment_mime_types) ? [...config3.attachment_mime_types] : undefined,
426953
- attachmentAllowedRecipients: isStringArray5(config3.attachment_allowed_recipients) ? [...config3.attachment_allowed_recipients] : undefined,
426954
- attachmentAllowedPaths: isStringArray5(config3.attachment_allowed_paths) ? [...config3.attachment_allowed_paths] : undefined,
427064
+ attachmentMimeTypes: isStringArray6(config3.attachment_mime_types) ? [...config3.attachment_mime_types] : undefined,
427065
+ attachmentAllowedRecipients: isStringArray6(config3.attachment_allowed_recipients) ? [...config3.attachment_allowed_recipients] : undefined,
427066
+ attachmentAllowedPaths: isStringArray6(config3.attachment_allowed_paths) ? [...config3.attachment_allowed_paths] : undefined,
426955
427067
  attachmentPathRecursive: isBoolean5(config3.attachment_path_recursive) ? config3.attachment_path_recursive : undefined,
426956
427068
  inboundDebounceMs: isValidInboundDebounceMs(config3.inbound_debounce_ms) ? Math.trunc(Math.min(config3.inbound_debounce_ms, 1e4)) : undefined,
426957
427069
  waitingBehavior: isWaitingBehavior(config3.waiting_behavior) ? config3.waiting_behavior : undefined,
@@ -427307,6 +427419,7 @@ function createAccountFromPatch(channelId, accountId, patch2) {
427307
427419
  defaultPermissionMode: normalizedPatch.defaultPermissionMode ?? DEFAULT_SLACK_PERMISSION_MODE,
427308
427420
  transcribeVoice: normalizedPatch.transcribeVoice === true,
427309
427421
  listenMode: normalizedPatch.listenMode === true,
427422
+ mentionOnlyChannels: [...normalizedPatch.mentionOnlyChannels ?? []],
427310
427423
  allowBots: normalizedPatch.allowBots ?? false,
427311
427424
  dmPolicy: normalizedPatch.dmPolicy ?? "open",
427312
427425
  allowedUsers: normalizedPatch.allowedUsers ?? [],
@@ -427423,6 +427536,9 @@ function mergeAccountPatch(existing, patch2) {
427423
427536
  defaultPermissionMode: normalizedPatch.defaultPermissionMode ?? existing.defaultPermissionMode ?? DEFAULT_SLACK_PERMISSION_MODE,
427424
427537
  transcribeVoice: normalizedPatch.transcribeVoice ?? existing.transcribeVoice ?? false,
427425
427538
  listenMode: normalizedPatch.listenMode ?? existing.listenMode ?? false,
427539
+ mentionOnlyChannels: [
427540
+ ...normalizedPatch.mentionOnlyChannels ?? existing.mentionOnlyChannels ?? []
427541
+ ],
427426
427542
  allowBots: normalizedPatch.allowBots ?? existing.allowBots ?? false,
427427
427543
  dmPolicy: normalizedPatch.dmPolicy ?? existing.dmPolicy,
427428
427544
  allowedUsers: normalizedPatch.allowedUsers ?? existing.allowedUsers,
@@ -427616,6 +427732,7 @@ function toAccountSnapshot(account) {
427616
427732
  defaultPermissionMode: account.defaultPermissionMode ?? DEFAULT_SLACK_PERMISSION_MODE,
427617
427733
  transcribeVoice: account.transcribeVoice === true,
427618
427734
  listenMode: account.listenMode === true,
427735
+ mentionOnlyChannels: [...account.mentionOnlyChannels ?? []],
427619
427736
  allowBots: account.allowBots ?? false,
427620
427737
  createdAt: account.createdAt,
427621
427738
  updatedAt: account.updatedAt
@@ -427776,6 +427893,7 @@ function getChannelConfigSnapshot(channelId, accountId) {
427776
427893
  defaultPermissionMode: account.defaultPermissionMode ?? DEFAULT_SLACK_PERMISSION_MODE,
427777
427894
  transcribeVoice: account.transcribeVoice === true,
427778
427895
  listenMode: account.listenMode === true,
427896
+ mentionOnlyChannels: [...account.mentionOnlyChannels ?? []],
427779
427897
  allowBots: account.allowBots ?? false
427780
427898
  };
427781
427899
  }
@@ -428299,6 +428417,7 @@ async function setChannelConfigLive(channelId, patch2, accountId) {
428299
428417
  threadPolicyByChannel: normalizedPatch.threadPolicyByChannel,
428300
428418
  acknowledgeMessageReaction: normalizedPatch.acknowledgeMessageReaction,
428301
428419
  listenMode: normalizedPatch.listenMode,
428420
+ mentionOnlyChannels: normalizedPatch.mentionOnlyChannels,
428302
428421
  allowBots: normalizedPatch.allowBots,
428303
428422
  removeStaleRoutes: normalizedPatch.removeStaleRoutes,
428304
428423
  inboundDebounceMs: normalizedPatch.inboundDebounceMs,
@@ -428336,6 +428455,7 @@ async function setChannelConfigLive(channelId, patch2, accountId) {
428336
428455
  threadPolicyByChannel: normalizedPatch.threadPolicyByChannel,
428337
428456
  acknowledgeMessageReaction: normalizedPatch.acknowledgeMessageReaction,
428338
428457
  listenMode: normalizedPatch.listenMode,
428458
+ mentionOnlyChannels: normalizedPatch.mentionOnlyChannels,
428339
428459
  allowBots: normalizedPatch.allowBots,
428340
428460
  removeStaleRoutes: normalizedPatch.removeStaleRoutes,
428341
428461
  inboundDebounceMs: normalizedPatch.inboundDebounceMs,
@@ -429992,6 +430112,9 @@ function isConnectOAuthProvider(provider) {
429992
430112
  function isConnectBedrockProvider(provider) {
429993
430113
  return provider.byokProvider.providerType === "bedrock" || provider.byokProvider.providerType === "amazon-bedrock";
429994
430114
  }
430115
+ function isConnectBaseURLRequired(provider) {
430116
+ return provider.byokProvider.fields?.some((field) => field.key === "baseUrl" && field.required !== false) === true;
430117
+ }
429995
430118
  function isConnectApiKeyProvider(provider) {
429996
430119
  return !isConnectOAuthProvider(provider) && !isConnectBedrockProvider(provider);
429997
430120
  }
@@ -430049,6 +430172,7 @@ function formatUsage() {
430049
430172
  " letta connect codex --method device-code",
430050
430173
  " letta connect anthropic <api_key>",
430051
430174
  " letta connect openai --api-key <api_key>",
430175
+ " letta connect openai-compatible --base-url http://localhost:8000/v1 [--api-key <api_key>]",
430052
430176
  " letta connect ollama --base-url http://192.168.1.50:11434/v1",
430053
430177
  " letta connect lmstudio --base-url http://127.0.0.1:1234/v1 --timeout 600s",
430054
430178
  " letta connect llama-cpp --base-url http://localhost:8080/v1",
@@ -430262,6 +430386,10 @@ Provider '${providerName}' saved.`);
430262
430386
  io.stderr(getErrorMessage2(error54));
430263
430387
  return 1;
430264
430388
  }
430389
+ if (isConnectBaseURLRequired(provider) && !connectionOptions.baseURL?.trim()) {
430390
+ io.stderr(`Missing base URL for ${provider.canonical}. Pass --base-url <url>.`);
430391
+ return 1;
430392
+ }
430265
430393
  apiKey ||= defaultConnectApiKey(provider) ?? "";
430266
430394
  if (!apiKey && isConnectZaiBaseProvider(provider)) {
430267
430395
  io.stdout(`Do you have a Z.ai Coding plan?
@@ -438839,6 +438967,100 @@ var init_cron = __esm(async () => {
438839
438967
  await init_scheduler();
438840
438968
  });
438841
438969
 
438970
+ // src/backend/api/environments.ts
438971
+ var exports_environments2 = {};
438972
+ __export(exports_environments2, {
438973
+ sendEnvironmentMessage: () => sendEnvironmentMessage,
438974
+ resolveEnvironmentConnectionId: () => resolveEnvironmentConnectionId,
438975
+ resolveAgentSandboxConnectionId: () => resolveAgentSandboxConnectionId,
438976
+ listEnvironments: () => listEnvironments,
438977
+ isEnvironmentOnline: () => isEnvironmentOnline,
438978
+ getEnvironmentConnection: () => getEnvironmentConnection,
438979
+ describeEnvironment: () => describeEnvironment,
438980
+ createAgentSandbox: () => createAgentSandbox
438981
+ });
438982
+ async function listEnvironments(options3 = {}) {
438983
+ return apiRequest("GET", "/v1/environments", undefined, {
438984
+ query: {
438985
+ limit: options3.limit,
438986
+ after: options3.after,
438987
+ onlineOnly: options3.onlineOnly
438988
+ }
438989
+ });
438990
+ }
438991
+ async function sendEnvironmentMessage(connectionId, body3) {
438992
+ return apiRequest("POST", `/v1/environments/${encodeURIComponent(connectionId)}/messages`, body3);
438993
+ }
438994
+ async function getEnvironmentConnection(deviceId) {
438995
+ return apiRequest("GET", `/v1/environments/${encodeURIComponent(deviceId)}`);
438996
+ }
438997
+ async function createAgentSandbox(agentId) {
438998
+ return apiRequest("POST", `/v1/agents/${encodeURIComponent(agentId)}/sandboxes`, {});
438999
+ }
439000
+ function isEnvironmentOnline(environment2) {
439001
+ return typeof environment2.connectionId === "string" && environment2.connectionId.length > 0 && typeof environment2.lastHeartbeat === "number" && Date.now() - environment2.lastHeartbeat < 120000;
439002
+ }
439003
+ function describeEnvironment(environment2) {
439004
+ const status = isEnvironmentOnline(environment2) ? "online" : "offline";
439005
+ return `${environment2.connectionName} (${environment2.deviceId}, ${status})`;
439006
+ }
439007
+ async function resolveEnvironmentConnectionId(selector) {
439008
+ const trimmed = selector.trim();
439009
+ if (!trimmed) {
439010
+ throw new Error("Environment selector must not be empty");
439011
+ }
439012
+ const response = await listEnvironments({ limit: 100 });
439013
+ const matches2 = response.connections.filter((environment3) => {
439014
+ return environment3.connectionId === trimmed || environment3.id === trimmed || environment3.deviceId === trimmed || environment3.connectionName === trimmed;
439015
+ });
439016
+ if (matches2.length === 0) {
439017
+ throw new Error(`Environment "${trimmed}" not found. Run \`letta environments list\` to discover available environments.`);
439018
+ }
439019
+ const onlineMatches = matches2.filter(isEnvironmentOnline);
439020
+ if (onlineMatches.length === 0) {
439021
+ throw new Error(`Environment "${trimmed}" is offline. Matched: ${matches2.map(describeEnvironment).join(", ")}`);
439022
+ }
439023
+ if (onlineMatches.length > 1) {
439024
+ throw new Error(`Environment "${trimmed}" is ambiguous. Matched: ${onlineMatches.map(describeEnvironment).join(", ")}`);
439025
+ }
439026
+ const environment2 = onlineMatches[0];
439027
+ if (!environment2) {
439028
+ throw new Error(`Environment "${trimmed}" is offline`);
439029
+ }
439030
+ if (!environment2.connectionId) {
439031
+ throw new Error(`Environment "${trimmed}" has no active connection id`);
439032
+ }
439033
+ return { connectionId: environment2.connectionId, environment: environment2 };
439034
+ }
439035
+ async function resolveAgentSandboxConnectionId(agentId, options3 = {}) {
439036
+ const timeoutMs = options3.timeoutMs ?? 3 * 60000;
439037
+ const pollIntervalMs = options3.pollIntervalMs ?? 2000;
439038
+ const sandbox = await createAgentSandbox(agentId);
439039
+ const deviceId = sandbox.deviceId || `sandbox-${agentId}`;
439040
+ const deadline = Date.now() + timeoutMs;
439041
+ let lastEnvironment = null;
439042
+ let lastError = null;
439043
+ while (Date.now() < deadline) {
439044
+ try {
439045
+ const environment2 = await getEnvironmentConnection(deviceId);
439046
+ lastEnvironment = environment2;
439047
+ if (isEnvironmentOnline(environment2) && environment2.connectionId) {
439048
+ return { connectionId: environment2.connectionId, environment: environment2 };
439049
+ }
439050
+ } catch (error54) {
439051
+ lastError = error54;
439052
+ }
439053
+ await new Promise((resolve31) => setTimeout(resolve31, pollIntervalMs));
439054
+ }
439055
+ if (lastEnvironment) {
439056
+ throw new Error(`Timed out waiting for cloud sandbox ${sandbox.connectionName} to come online. Last status: ${describeEnvironment(lastEnvironment)}`);
439057
+ }
439058
+ throw new Error(`Timed out waiting for cloud sandbox ${sandbox.connectionName} to register${lastError instanceof Error ? `: ${lastError.message}` : ""}`);
439059
+ }
439060
+ var init_environments2 = __esm(() => {
439061
+ init_request();
439062
+ });
439063
+
438842
439064
  // src/cli/subcommands/cron-runner.ts
438843
439065
  function isLocalAgent(agentId) {
438844
439066
  return agentId.startsWith("agent-local-");
@@ -438881,7 +439103,7 @@ function validateTargetDevice(deviceId, environment2) {
438881
439103
  if (deviceId === SYNTHETIC_CLOUD_DEVICE_ID) {
438882
439104
  return {
438883
439105
  ok: false,
438884
- error: `"Cloud" is the default execution target, not a computer. Omit --computer to run in the agent's cloud sandbox.`
439106
+ error: `"Cloud" is not a computer. Pass --runner cloud to run in the agent's Cloud sandbox.`
438885
439107
  };
438886
439108
  }
438887
439109
  if (deviceId === SYNTHETIC_LOCAL_PLACEHOLDER_ID) {
@@ -438898,6 +439120,32 @@ function validateTargetDevice(deviceId, environment2) {
438898
439120
  }
438899
439121
  return { ok: true };
438900
439122
  }
439123
+ async function resolveInferredTargetDevice(deviceId, lookupEnvironment) {
439124
+ if (deviceId.startsWith("sandbox-")) {
439125
+ return { kind: "cloud-sandbox" };
439126
+ }
439127
+ const environment2 = await lookupEnvironment();
439128
+ const basicValidity = validateTargetDevice(deviceId, environment2);
439129
+ if (!basicValidity.ok) {
439130
+ return {
439131
+ kind: "local-fallback",
439132
+ reason: "this computer is not connected to your Letta account"
439133
+ };
439134
+ }
439135
+ if (!environment2) {
439136
+ return {
439137
+ kind: "local-fallback",
439138
+ reason: "this computer is not connected to your Letta account"
439139
+ };
439140
+ }
439141
+ if (!isEnvironmentOnline(environment2)) {
439142
+ return {
439143
+ kind: "local-fallback",
439144
+ reason: "this computer's connection to Letta is not currently online"
439145
+ };
439146
+ }
439147
+ return { kind: "device" };
439148
+ }
438901
439149
  function buildCloudScheduleInput(params) {
438902
439150
  const notes = [];
438903
439151
  let schedule;
@@ -438928,6 +439176,9 @@ function buildCloudScheduleInput(params) {
438928
439176
  };
438929
439177
  }
438930
439178
  var CLOUD_EXECUTION_TARGET = "cloud-sandbox", SYNTHETIC_CLOUD_DEVICE_ID = "__letta_cloud__", SYNTHETIC_LOCAL_PLACEHOLDER_ID = "local", CLOUD_CRON_UTC_NOTE = "Recurring Cloud schedules currently interpret cron expressions in UTC (timezone support is tracked in LET-9815).", CLOUD_DEVICE_FALLBACK_NOTE = "If the target computer is offline when the schedule fires, execution falls back to the agent's cloud sandbox.";
439179
+ var init_cron_runner = __esm(() => {
439180
+ init_environments2();
439181
+ });
438931
439182
 
438932
439183
  // src/cli/subcommands/cron-task-ref.ts
438933
439184
  async function ensureSettingsForCloud() {
@@ -438977,103 +439228,10 @@ function printAmbiguousTaskName(name, matches2) {
438977
439228
  var init_cron_task_ref = __esm(async () => {
438978
439229
  init_schedules2();
438979
439230
  init_backend_mode();
439231
+ init_cron_runner();
438980
439232
  await init_cron();
438981
439233
  });
438982
439234
 
438983
- // src/backend/api/environments.ts
438984
- var exports_environments2 = {};
438985
- __export(exports_environments2, {
438986
- sendEnvironmentMessage: () => sendEnvironmentMessage,
438987
- resolveEnvironmentConnectionId: () => resolveEnvironmentConnectionId,
438988
- resolveAgentSandboxConnectionId: () => resolveAgentSandboxConnectionId,
438989
- listEnvironments: () => listEnvironments,
438990
- isEnvironmentOnline: () => isEnvironmentOnline,
438991
- getEnvironmentConnection: () => getEnvironmentConnection,
438992
- describeEnvironment: () => describeEnvironment,
438993
- createAgentSandbox: () => createAgentSandbox
438994
- });
438995
- async function listEnvironments(options3 = {}) {
438996
- return apiRequest("GET", "/v1/environments", undefined, {
438997
- query: {
438998
- limit: options3.limit,
438999
- after: options3.after,
439000
- onlineOnly: options3.onlineOnly
439001
- }
439002
- });
439003
- }
439004
- async function sendEnvironmentMessage(connectionId, body3) {
439005
- return apiRequest("POST", `/v1/environments/${encodeURIComponent(connectionId)}/messages`, body3);
439006
- }
439007
- async function getEnvironmentConnection(deviceId) {
439008
- return apiRequest("GET", `/v1/environments/${encodeURIComponent(deviceId)}`);
439009
- }
439010
- async function createAgentSandbox(agentId) {
439011
- return apiRequest("POST", `/v1/agents/${encodeURIComponent(agentId)}/sandboxes`, {});
439012
- }
439013
- function isEnvironmentOnline(environment2) {
439014
- return typeof environment2.connectionId === "string" && environment2.connectionId.length > 0 && typeof environment2.lastHeartbeat === "number" && Date.now() - environment2.lastHeartbeat < 120000;
439015
- }
439016
- function describeEnvironment(environment2) {
439017
- const status = isEnvironmentOnline(environment2) ? "online" : "offline";
439018
- return `${environment2.connectionName} (${environment2.deviceId}, ${status})`;
439019
- }
439020
- async function resolveEnvironmentConnectionId(selector) {
439021
- const trimmed = selector.trim();
439022
- if (!trimmed) {
439023
- throw new Error("Environment selector must not be empty");
439024
- }
439025
- const response = await listEnvironments({ limit: 100 });
439026
- const matches2 = response.connections.filter((environment3) => {
439027
- return environment3.connectionId === trimmed || environment3.id === trimmed || environment3.deviceId === trimmed || environment3.connectionName === trimmed;
439028
- });
439029
- if (matches2.length === 0) {
439030
- throw new Error(`Environment "${trimmed}" not found. Run \`letta environments list\` to discover available environments.`);
439031
- }
439032
- const onlineMatches = matches2.filter(isEnvironmentOnline);
439033
- if (onlineMatches.length === 0) {
439034
- throw new Error(`Environment "${trimmed}" is offline. Matched: ${matches2.map(describeEnvironment).join(", ")}`);
439035
- }
439036
- if (onlineMatches.length > 1) {
439037
- throw new Error(`Environment "${trimmed}" is ambiguous. Matched: ${onlineMatches.map(describeEnvironment).join(", ")}`);
439038
- }
439039
- const environment2 = onlineMatches[0];
439040
- if (!environment2) {
439041
- throw new Error(`Environment "${trimmed}" is offline`);
439042
- }
439043
- if (!environment2.connectionId) {
439044
- throw new Error(`Environment "${trimmed}" has no active connection id`);
439045
- }
439046
- return { connectionId: environment2.connectionId, environment: environment2 };
439047
- }
439048
- async function resolveAgentSandboxConnectionId(agentId, options3 = {}) {
439049
- const timeoutMs = options3.timeoutMs ?? 3 * 60000;
439050
- const pollIntervalMs = options3.pollIntervalMs ?? 2000;
439051
- const sandbox = await createAgentSandbox(agentId);
439052
- const deviceId = sandbox.deviceId || `sandbox-${agentId}`;
439053
- const deadline = Date.now() + timeoutMs;
439054
- let lastEnvironment = null;
439055
- let lastError = null;
439056
- while (Date.now() < deadline) {
439057
- try {
439058
- const environment2 = await getEnvironmentConnection(deviceId);
439059
- lastEnvironment = environment2;
439060
- if (isEnvironmentOnline(environment2) && environment2.connectionId) {
439061
- return { connectionId: environment2.connectionId, environment: environment2 };
439062
- }
439063
- } catch (error54) {
439064
- lastError = error54;
439065
- }
439066
- await new Promise((resolve31) => setTimeout(resolve31, pollIntervalMs));
439067
- }
439068
- if (lastEnvironment) {
439069
- throw new Error(`Timed out waiting for cloud sandbox ${sandbox.connectionName} to come online. Last status: ${describeEnvironment(lastEnvironment)}`);
439070
- }
439071
- throw new Error(`Timed out waiting for cloud sandbox ${sandbox.connectionName} to register${lastError instanceof Error ? `: ${lastError.message}` : ""}`);
439072
- }
439073
- var init_environments2 = __esm(() => {
439074
- init_request();
439075
- });
439076
-
439077
439235
  // src/cli/subcommands/cron.ts
439078
439236
  import { parseArgs as parseArgs5 } from "node:util";
439079
439237
  function printUsage4() {
@@ -439096,18 +439254,25 @@ Add options:
439096
439254
  --cron <expr> Raw 5-field cron expression
439097
439255
  --agent <id> Agent ID (defaults to LETTA_AGENT_ID)
439098
439256
  --conversation <id> Conversation ID (defaults to LETTA_CONVERSATION_ID or "default")
439099
- --runner <runner> Where the schedule lives and fires:
439100
- cloud - durable Cloud schedule; executes in the
439101
- agent's managed cloud sandbox (default for
439102
- cloud agents)
439257
+ --runner <runner> Where the schedule lives and fires (normally omit:
439258
+ the default keeps the schedule running where it was
439259
+ created):
439260
+ cloud - durable Cloud schedule (default for cloud
439261
+ agents on Cloud-reachable runtimes:
439262
+ external listeners are targeted, managed
439263
+ sandboxes stay untargeted). Explicit
439264
+ --runner cloud always uses the Cloud sandbox
439103
439265
  local - this device's scheduler (~/.letta/crons.json);
439104
439266
  only fires while a session runs here (default
439105
- for local-backend agents / self-hosted)
439106
- --computer <id> (cloud runner only) Execute on one of your
439107
- connected computers (deviceId from
439108
- \`letta environments list\`) instead of the agent's
439109
- cloud sandbox. Falls back to the sandbox if the
439110
- computer is offline at fire time.
439267
+ for local-backend agents / self-hosted, and
439268
+ the fallback when Cloud scheduling cannot
439269
+ reach this computer)
439270
+ --computer <id> (cloud runner only) Override execution with a
439271
+ connected external environment (deviceId from
439272
+ \`letta environments list\`). Falls back to the Cloud
439273
+ sandbox if the computer is offline at fire time.
439274
+ Managed sandboxes and Desktop-local connections are
439275
+ not currently valid Cloud schedule targets.
439111
439276
 
439112
439277
  List/filter options:
439113
439278
  --agent <id> Filter by agent ID
@@ -439270,13 +439435,15 @@ async function handleAdd(values2) {
439270
439435
  console.error("Error: no schedule specified.");
439271
439436
  return 1;
439272
439437
  }
439273
- const targetDeviceId = values2.computer?.trim() || undefined;
439438
+ let targetDeviceId = values2.computer?.trim() || undefined;
439274
439439
  const resolved = await getRunnerForAgent(values2.runner, agentId);
439275
439440
  if ("error" in resolved) {
439276
439441
  console.error(`Error: ${resolved.error}`);
439277
439442
  return 1;
439278
439443
  }
439279
- if (targetDeviceId && resolved.runner !== "cloud") {
439444
+ let runner = resolved.runner;
439445
+ let localFallbackNote;
439446
+ if (targetDeviceId && runner !== "cloud") {
439280
439447
  console.error("Error: --computer requires the cloud runner. Run `letta cron add` on the target computer itself (with --runner local) to schedule there locally.");
439281
439448
  return 1;
439282
439449
  }
@@ -439286,8 +439453,17 @@ async function handleAdd(values2) {
439286
439453
  console.error(`Error: ${validity.error}`);
439287
439454
  return 1;
439288
439455
  }
439456
+ } else if (runner === "cloud" && values2.runner !== "cloud") {
439457
+ const inferredDeviceId = getRuntimeEnvironmentDeviceId();
439458
+ const resolution = await resolveInferredTargetDevice(inferredDeviceId, () => lookupEnvironmentForTarget(inferredDeviceId));
439459
+ if (resolution.kind === "device") {
439460
+ targetDeviceId = inferredDeviceId;
439461
+ } else if (resolution.kind === "local-fallback") {
439462
+ runner = "local";
439463
+ localFallbackNote = `This schedule is local to this computer (${resolution.reason}): it only fires while a Letta session is running here. For a schedule that fires regardless, pass --runner cloud (runs in the agent's cloud sandbox) or --computer <deviceId> (runs on a connected computer, from \`letta environments list\`).`;
439464
+ }
439289
439465
  }
439290
- if (resolved.runner === "cloud") {
439466
+ if (runner === "cloud") {
439291
439467
  return handleCloudAdd({
439292
439468
  agentId,
439293
439469
  conversationId,
@@ -439331,8 +439507,10 @@ async function handleAdd(values2) {
439331
439507
  if (note) {
439332
439508
  output.note = note;
439333
439509
  }
439334
- if (result.warning) {
439335
- output.warning = result.warning;
439510
+ const fallbackWarning = localFallbackNote && recurring ? `${localFallbackNote} Recurring schedules on an unregistered computer stop firing whenever no session is running — strongly consider a durable alternative.` : localFallbackNote;
439511
+ const warnings = [fallbackWarning, result.warning].filter(Boolean);
439512
+ if (warnings.length > 0) {
439513
+ output.warning = warnings.join(" ");
439336
439514
  }
439337
439515
  console.log(JSON.stringify(output, null, 2));
439338
439516
  console.error("Created local schedule: it only fires while a Letta session is running on this device.");
@@ -439694,9 +439872,11 @@ async function runCronSubcommand(argv) {
439694
439872
  }
439695
439873
  var CRON_OPTIONS;
439696
439874
  var init_cron2 = __esm(async () => {
439875
+ init_client2();
439697
439876
  init_request();
439698
439877
  init_schedules2();
439699
439878
  init_backend_mode();
439879
+ init_cron_runner();
439700
439880
  await __promiseAll([
439701
439881
  init_cron(),
439702
439882
  init_cron_task_ref()
@@ -448335,13 +448515,13 @@ var init_channel_service_validation = __esm(() => {
448335
448515
  function isExperimentId(value) {
448336
448516
  return typeof value === "string" && EXPERIMENT_IDS.has(value);
448337
448517
  }
448338
- function isStringArray6(value) {
448518
+ function isStringArray7(value) {
448339
448519
  return Array.isArray(value) && value.every((item) => typeof item === "string");
448340
448520
  }
448341
448521
  function isClientToolsetConfig(value) {
448342
448522
  if (!isObjectRecord2(value))
448343
448523
  return false;
448344
- return (value.base === undefined || typeof value.base === "string" && TOOLSET_PREFERENCES.has(value.base)) && (value.include === undefined || isStringArray6(value.include));
448524
+ return (value.base === undefined || typeof value.base === "string" && TOOLSET_PREFERENCES.has(value.base)) && (value.include === undefined || isStringArray7(value.include));
448345
448525
  }
448346
448526
  function isStringRecord2(value) {
448347
448527
  return !!value && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((item) => typeof item === "string");
@@ -448372,7 +448552,7 @@ function isInputCommand(value) {
448372
448552
  }
448373
448553
  const payload = candidate.payload;
448374
448554
  if (payload.kind === "create_message") {
448375
- return Array.isArray(payload.messages) && (payload.image_failure_mode === undefined || payload.image_failure_mode === "strict" || payload.image_failure_mode === "drop") && (payload.client_tool_allowlist === undefined || isStringArray6(payload.client_tool_allowlist)) && (payload.client_toolset === undefined || isClientToolsetConfig(payload.client_toolset)) && (payload.external_tool_scope_ids === undefined || isStringArray6(payload.external_tool_scope_ids)) && (payload.exclude_interactive_tools === undefined || typeof payload.exclude_interactive_tools === "boolean");
448555
+ return Array.isArray(payload.messages) && (payload.image_failure_mode === undefined || payload.image_failure_mode === "strict" || payload.image_failure_mode === "drop") && (payload.client_tool_allowlist === undefined || isStringArray7(payload.client_tool_allowlist)) && (payload.client_toolset === undefined || isClientToolsetConfig(payload.client_toolset)) && (payload.external_tool_scope_ids === undefined || isStringArray7(payload.external_tool_scope_ids)) && (payload.exclude_interactive_tools === undefined || typeof payload.exclude_interactive_tools === "boolean");
448376
448556
  }
448377
448557
  if (payload.kind === "approval_response") {
448378
448558
  return isValidApprovalResponseBody(payload);
@@ -448397,9 +448577,9 @@ function legacyEnvironmentMessageToInputCommand(value) {
448397
448577
  payload: {
448398
448578
  kind: "create_message",
448399
448579
  messages: candidate.messages,
448400
- client_tool_allowlist: isStringArray6(candidate.clientToolAllowlist) ? candidate.clientToolAllowlist : undefined,
448580
+ client_tool_allowlist: isStringArray7(candidate.clientToolAllowlist) ? candidate.clientToolAllowlist : undefined,
448401
448581
  client_toolset: isClientToolsetConfig(candidate.clientToolset) ? candidate.clientToolset : undefined,
448402
- external_tool_scope_ids: isStringArray6(candidate.externalToolScopeIds) ? candidate.externalToolScopeIds : undefined
448582
+ external_tool_scope_ids: isStringArray7(candidate.externalToolScopeIds) ? candidate.externalToolScopeIds : undefined
448403
448583
  }
448404
448584
  };
448405
448585
  }
@@ -448431,7 +448611,7 @@ function getInvalidInputReason(value) {
448431
448611
  reason: "Protocol violation: input.payload.image_failure_mode must be strict or drop"
448432
448612
  };
448433
448613
  }
448434
- if (payload.client_tool_allowlist !== undefined && !isStringArray6(payload.client_tool_allowlist)) {
448614
+ if (payload.client_tool_allowlist !== undefined && !isStringArray7(payload.client_tool_allowlist)) {
448435
448615
  return {
448436
448616
  runtime: candidate.runtime,
448437
448617
  reason: "Protocol violation: input.payload.client_tool_allowlist must be string[]"
@@ -448449,7 +448629,7 @@ function getInvalidInputReason(value) {
448449
448629
  reason: "Protocol violation: input.payload.exclude_interactive_tools must be boolean"
448450
448630
  };
448451
448631
  }
448452
- if (payload.external_tool_scope_ids !== undefined && !isStringArray6(payload.external_tool_scope_ids)) {
448632
+ if (payload.external_tool_scope_ids !== undefined && !isStringArray7(payload.external_tool_scope_ids)) {
448453
448633
  return {
448454
448634
  runtime: candidate.runtime,
448455
448635
  reason: "Protocol violation: input.payload.external_tool_scope_ids must be string[]"
@@ -448811,7 +448991,7 @@ function isCreateAgentCommand(value) {
448811
448991
  if (!value || typeof value !== "object")
448812
448992
  return false;
448813
448993
  const c = value;
448814
- return c.type === "create_agent" && typeof c.request_id === "string" && (c.personality === "memo" || c.personality === "blank" || c.personality === "tutorial" || c.personality === "linus" || c.personality === "kawaii") && (c.model === undefined || typeof c.model === "string") && (c.tags === undefined || isStringArray6(c.tags)) && (c.pin_global === undefined || typeof c.pin_global === "boolean");
448994
+ return c.type === "create_agent" && typeof c.request_id === "string" && (c.personality === "memo" || c.personality === "blank" || c.personality === "tutorial" || c.personality === "linus" || c.personality === "kawaii") && (c.model === undefined || typeof c.model === "string") && (c.tags === undefined || isStringArray7(c.tags)) && (c.pin_global === undefined || typeof c.pin_global === "boolean");
448815
448995
  }
448816
448996
  function isAgentListCommand(value) {
448817
448997
  if (!value || typeof value !== "object")
@@ -451068,6 +451248,7 @@ async function applyModelUpdateForRuntime(params) {
451068
451248
  conversationId,
451069
451249
  overrideModel: model.handle,
451070
451250
  overrideProviderType: providerTypeFromModelSettings3(modelSettings) ?? inferProviderTypeFromRegistryHandle(model.handle) ?? null,
451251
+ modContext: createListenerAgentModContext(agentId),
451071
451252
  modAdapters,
451072
451253
  modEvents: createListenerModEvents(modAdapters)
451073
451254
  });
@@ -451140,6 +451321,7 @@ async function applyToolsetUpdateForRuntime(params) {
451140
451321
  const preparedToolContext = await prepareToolExecutionContextForScope({
451141
451322
  agentId,
451142
451323
  conversationId,
451324
+ modContext: createListenerAgentModContext(agentId),
451143
451325
  modAdapters,
451144
451326
  modEvents: createListenerModEvents(modAdapters)
451145
451327
  });
@@ -452655,13 +452837,24 @@ function getLoopErrorNoticeDecision(params) {
452655
452837
  apiError
452656
452838
  };
452657
452839
  }
452658
- const formattedMessage = formatErrorDetails2(apiError ? buildStructuredFormatInput(apiError) : params.error ?? params.message, params.agentId ?? undefined, params.conversationId ?? undefined);
452840
+ const formattedMessage = formatErrorDetails2(apiError ? buildStructuredFormatInput(apiError) : params.error ?? params.message, params.agentId ?? undefined, params.conversationId ?? undefined, {
452841
+ surface: params.surface,
452842
+ unclassifiedFallback: params.unclassifiedFallback
452843
+ });
452659
452844
  return {
452660
452845
  visibility: "transcript",
452661
452846
  message: formattedMessage,
452662
452847
  apiError
452663
452848
  };
452664
452849
  }
452850
+ function getTranscriptLoopErrorMessage(params) {
452851
+ const decision = getLoopErrorNoticeDecision({
452852
+ ...params,
452853
+ surface: "plain",
452854
+ unclassifiedFallback: "generic"
452855
+ });
452856
+ return decision.visibility === "transcript" ? decision.message : undefined;
452857
+ }
452665
452858
  function emitLoopErrorNotice(socket, runtime, params) {
452666
452859
  const decision = getLoopErrorNoticeDecision(params);
452667
452860
  if (decision.visibility === "debug_only") {
@@ -455231,23 +455424,27 @@ function finalizeHandledRecoveryTurn(runtime, socket, turnLease, params) {
455231
455424
  }
455232
455425
  const terminalStopReason = params.drainResult.stopReason || "error";
455233
455426
  const runId = runtime.activeRunId;
455427
+ const noticeParams = {
455428
+ message: `Recovery continuation ended unexpectedly: ${terminalStopReason}`,
455429
+ agentId: params.agentId,
455430
+ conversationId: params.conversationId
455431
+ };
455234
455432
  const transition = finishListenerTurn(runtime, turnLease, {
455235
455433
  stopReason: terminalStopReason,
455236
455434
  socket,
455237
455435
  agentId: params.agentId,
455238
455436
  conversationId: params.conversationId,
455239
- turnId: params.turnId
455437
+ turnId: params.turnId,
455438
+ error: getTranscriptLoopErrorMessage(noticeParams)
455240
455439
  });
455241
455440
  if (!transition.finished) {
455242
455441
  return transition;
455243
455442
  }
455244
455443
  emitLoopErrorNotice(socket, runtime, {
455245
- message: `Recovery continuation ended unexpectedly: ${terminalStopReason}`,
455444
+ ...noticeParams,
455246
455445
  stopReason: terminalStopReason,
455247
455446
  isTerminal: true,
455248
- runId: runId || undefined,
455249
- agentId: params.agentId ?? undefined,
455250
- conversationId: params.conversationId
455447
+ runId: runId || undefined
455251
455448
  });
455252
455449
  return transition;
455253
455450
  }
@@ -455508,6 +455705,7 @@ async function resolveRecoveredApprovalResponse(runtime, socket, response, proce
455508
455705
  conversationId: recovered.conversationId,
455509
455706
  workingDirectory,
455510
455707
  permissionModeState: getOrCreateConversationPermissionModeStateRef(runtime.listener, recovered.agentId, recovered.conversationId),
455708
+ modContext: createListenerAgentModContext(recovered.agentId),
455511
455709
  modAdapters,
455512
455710
  modEvents: createListenerModEvents(modAdapters)
455513
455711
  });
@@ -455603,13 +455801,17 @@ async function resolveRecoveredApprovalResponse(runtime, socket, response, proce
455603
455801
  recovered.pendingRequestIds = new Set(recovered.approvalsByRequestId.keys());
455604
455802
  recovered.responsesByRequestId.clear();
455605
455803
  }
455804
+ const stopReason = recoveryLease.signal.aborted ? "cancelled" : "error";
455606
455805
  finishListenerTurn(runtime, recoveryLease, {
455607
- stopReason: recoveryLease.signal.aborted ? "cancelled" : "error",
455806
+ stopReason,
455608
455807
  socket,
455609
455808
  agentId: recovered.agentId,
455610
455809
  conversationId: recovered.conversationId,
455611
455810
  turnId: `batch-recovered-${requestId}`,
455612
- error: error54 instanceof Error ? error54.message : String(error54)
455811
+ error: stopReason === "error" ? getTranscriptLoopErrorMessage({
455812
+ error: error54,
455813
+ message: error54 instanceof Error ? error54.message : String(error54)
455814
+ }) : undefined
455613
455815
  });
455614
455816
  throw error54;
455615
455817
  }
@@ -455917,6 +456119,7 @@ async function resolveStaleApprovals(runtime, socket, turnLease, deps = {}) {
455917
456119
  conversationId: recoveryConversationId,
455918
456120
  workingDirectory: recoveryWorkingDirectory,
455919
456121
  permissionModeState: getOrCreateConversationPermissionModeStateRef(runtime.listener, runtime.agentId, runtime.conversationId),
456122
+ modContext: createListenerAgentModContext(runtime.agentId),
455920
456123
  modAdapters,
455921
456124
  modEvents: createListenerModEvents(modAdapters)
455922
456125
  });
@@ -459107,7 +459310,7 @@ async function emitListenerTurnStart(options3) {
459107
459310
  sessionId: options3.conversationId,
459108
459311
  workingDirectory: options3.workingDirectory,
459109
459312
  permissionMode: options3.permissionMode ?? null,
459110
- agent: options3.cachedAgent ?? null
459313
+ agent: options3.cachedAgent ?? { id: options3.agentId }
459111
459314
  });
459112
459315
  const event2 = {
459113
459316
  agentId: options3.agentId,
@@ -459134,7 +459337,7 @@ async function emitListenerTurnEnd(options3) {
459134
459337
  sessionId: options3.conversationId,
459135
459338
  workingDirectory: options3.workingDirectory,
459136
459339
  permissionMode: options3.permissionMode ?? null,
459137
- agent: options3.cachedAgent ?? null
459340
+ agent: options3.cachedAgent ?? { id: options3.agentId }
459138
459341
  });
459139
459342
  const event2 = {
459140
459343
  agentId: options3.agentId,
@@ -460227,8 +460430,10 @@ async function prepareListenerTurn(params) {
460227
460430
  inboundUserTranscriptLines = buildInboundUserTranscriptLines(currentInput);
460228
460431
  }
460229
460432
  const modAdapters = await ensureListenerModAdaptersForAgent(runtime.listener, agentId);
460433
+ const environmentDeviceId = connectionId ? runtime.listener.connections.get(connectionId)?.options.deviceId : undefined;
460230
460434
  const preparedToolContext = await prepareToolExecutionContextForScope({
460231
460435
  connectionId,
460436
+ environmentDeviceId,
460232
460437
  agentId,
460233
460438
  conversationId,
460234
460439
  clientToolset: msg.clientToolset,
@@ -460239,6 +460444,7 @@ async function prepareListenerTurn(params) {
460239
460444
  permissionModeState,
460240
460445
  skillSources: runtime.skillSources,
460241
460446
  cachedAgent,
460447
+ modContext: createListenerAgentModContext(agentId),
460242
460448
  modAdapters,
460243
460449
  modEvents: createListenerModEvents(modAdapters)
460244
460450
  });
@@ -460359,7 +460565,8 @@ async function handleIncomingMessageInner(msg, socket, runtime, onStatusChange,
460359
460565
  if (!agentId) {
460360
460566
  finishTurn({
460361
460567
  stopReason: "error",
460362
- conversationId
460568
+ conversationId,
460569
+ error: getTranscriptLoopErrorMessage({ message: "Missing agent ID" })
460363
460570
  });
460364
460571
  return;
460365
460572
  }
@@ -460732,24 +460939,29 @@ async function handleIncomingMessageInner(msg, socket, runtime, onStatusChange,
460732
460939
  }
460733
460940
  const errorMessage2 = errorDetail2 || `Unexpected stop reason: ${stopReason}`;
460734
460941
  const terminalRunId = runId || runtime.activeRunId || runErrorInfo2?.run_id;
460942
+ const noticeParams = {
460943
+ message: errorMessage2,
460944
+ agentId,
460945
+ conversationId,
460946
+ runErrorInfo: runErrorInfo2 ?? undefined,
460947
+ cancelRequested: turnAbortSignal.aborted,
460948
+ abortSignal: turnAbortSignal
460949
+ };
460950
+ const terminalError = getTranscriptLoopErrorMessage(noticeParams);
460735
460951
  const transition = finishTurn({
460736
460952
  stopReason: effectiveStopReason,
460737
460953
  agentId,
460738
- conversationId
460954
+ conversationId,
460955
+ error: terminalError
460739
460956
  });
460740
460957
  if (!transition.finished) {
460741
460958
  break;
460742
460959
  }
460743
460960
  const formattedError = emitLoopErrorNotice(socket, runtime, {
460744
- message: errorMessage2,
460961
+ ...noticeParams,
460745
460962
  stopReason: effectiveStopReason,
460746
460963
  isTerminal: true,
460747
- runId: terminalRunId,
460748
- agentId,
460749
- conversationId,
460750
- runErrorInfo: runErrorInfo2 ?? undefined,
460751
- cancelRequested: turnAbortSignal.aborted,
460752
- abortSignal: turnAbortSignal
460964
+ runId: terminalRunId
460753
460965
  });
460754
460966
  runtime.lastTerminalLoopErrorMessage = formattedError ?? errorMessage2;
460755
460967
  runtime.lastTerminalLoopErrorRunId = terminalRunId ?? null;
@@ -460776,10 +460988,14 @@ async function handleIncomingMessageInner(msg, socket, runtime, onStatusChange,
460776
460988
  });
460777
460989
  if (approvalResult.kind === "error") {
460778
460990
  const terminalRunId = runId || runtime.activeRunId;
460991
+ const terminalError = getTranscriptLoopErrorMessage({
460992
+ message: approvalResult.message
460993
+ });
460779
460994
  const transition = finishTurn({
460780
460995
  stopReason: "error",
460781
460996
  agentId,
460782
- conversationId
460997
+ conversationId,
460998
+ error: terminalError
460783
460999
  });
460784
461000
  if (!transition.finished) {
460785
461001
  return;
@@ -460877,24 +461093,29 @@ async function handleIncomingMessageInner(msg, socket, runtime, onStatusChange,
460877
461093
  }
460878
461094
  const errorMessage2 = error54 instanceof Error ? error54.message : String(error54);
460879
461095
  const terminalRunId = runtime.activeRunId;
461096
+ const noticeParams = {
461097
+ message: errorMessage2,
461098
+ agentId,
461099
+ conversationId,
461100
+ error: error54,
461101
+ cancelRequested: turnAbortSignal.aborted,
461102
+ abortSignal: turnAbortSignal
461103
+ };
461104
+ const terminalError = getTranscriptLoopErrorMessage(noticeParams);
460880
461105
  const transition = finishTurn({
460881
461106
  stopReason: "error",
460882
461107
  agentId: agentId || null,
460883
- conversationId
461108
+ conversationId,
461109
+ error: terminalError
460884
461110
  });
460885
461111
  if (!transition.finished) {
460886
461112
  return;
460887
461113
  }
460888
461114
  const formattedError = emitLoopErrorNotice(socket, runtime, {
460889
- message: errorMessage2,
461115
+ ...noticeParams,
460890
461116
  stopReason: "error",
460891
461117
  isTerminal: true,
460892
- runId: terminalRunId,
460893
- agentId: agentId || undefined,
460894
- conversationId,
460895
- error: error54,
460896
- cancelRequested: turnAbortSignal.aborted,
460897
- abortSignal: turnAbortSignal
461118
+ runId: terminalRunId
460898
461119
  });
460899
461120
  runtime.lastTerminalLoopErrorMessage = formattedError ?? errorMessage2;
460900
461121
  runtime.lastTerminalLoopErrorRunId = terminalRunId ?? null;
@@ -460914,7 +461135,8 @@ async function handleIncomingMessageInner(msg, socket, runtime, onStatusChange,
460914
461135
  socket,
460915
461136
  runId: runtime.activeRunId || msgRunIds[msgRunIds.length - 1],
460916
461137
  agentId: agentId || null,
460917
- conversationId
461138
+ conversationId,
461139
+ error: turnAbortSignal.aborted ? undefined : getTranscriptLoopErrorMessage({ message: "Unexpected turn failure" })
460918
461140
  });
460919
461141
  }
460920
461142
  if (runtime.activeConnectionId === connectionId) {
@@ -466115,6 +466337,7 @@ async function startConnectedListenerRuntime(runtime, transport, opts, processQu
466115
466337
  if (runtime !== getActiveRuntime() || runtime.intentionallyClosed) {
466116
466338
  return;
466117
466339
  }
466340
+ installExternalToolBridge(runtime);
466118
466341
  const shouldStartHeartbeat = options3.startHeartbeat !== false;
466119
466342
  const cronSchedulerDisabledByEnv = process.env.LETTA_DISABLE_CRON_SCHEDULER === "1";
466120
466343
  const shouldStartCronScheduler = options3.startCronScheduler !== false && !cronSchedulerDisabledByEnv;
@@ -469394,6 +469617,9 @@ async function resolveListenerStartupMode(channelNames, channelsRequested = chan
469394
469617
  if (channelsRequested) {
469395
469618
  return { kind: "local-channels", serverUrl, backend: "self-hosted" };
469396
469619
  }
469620
+ if (process.env.IGNORE_SELF_HOSTED_LISTENER_ERROR === "1") {
469621
+ return { kind: "remote", serverUrl };
469622
+ }
469397
469623
  return { kind: "unsupported-self-hosted", serverUrl };
469398
469624
  }
469399
469625
  function getScopedChannelRestoreAgentScope() {
@@ -469542,7 +469768,7 @@ async function runListenSubcommand(argv) {
469542
469768
  try {
469543
469769
  const deviceId = settingsManager.getOrCreateDeviceId();
469544
469770
  const startupMode = await resolveListenerStartupMode(channelNames, channelNames.length > 0 || restoreEnabledChannels);
469545
- if (startupMode.kind === "unsupported-self-hosted" && process.env.IGNORE_SELF_HOSTED_LISTENER_ERROR !== "1") {
469771
+ if (startupMode.kind === "unsupported-self-hosted") {
469546
469772
  console.error(`Self-hosted listener registration is not available for ${startupMode.serverUrl}.`);
469547
469773
  console.error("Start with --channels to run local channel adapters, or unset LETTA_BASE_URL to use Letta API remote environments.");
469548
469774
  await flushListenerTelemetryEnd("listener_self_hosted_no_channels");
@@ -491886,7 +492112,7 @@ var init_mcp_client = __esm(() => {
491886
492112
  init_streamableHttp();
491887
492113
  DEFAULT_CLIENT_INFO = {
491888
492114
  name: "letta-code",
491889
- version: "0.30.5"
492115
+ version: "0.30.6"
491890
492116
  };
491891
492117
  });
491892
492118
 
@@ -523799,6 +524025,7 @@ function formatConnectUsage() {
523799
524025
  " /connect codex",
523800
524026
  " /connect anthropic <api_key>",
523801
524027
  " /connect openai <api_key>",
524028
+ " /connect openai-compatible --base-url http://localhost:8000/v1 [--api-key <api_key>]",
523802
524029
  " /connect lmstudio --base-url http://127.0.0.1:1234/v1 --timeout 600s",
523803
524030
  " /connect bedrock iam --access-key <id> --secret-key <key> --region <region>",
523804
524031
  " /connect bedrock profile --profile <name> --region <region>"
@@ -523876,7 +524103,7 @@ function formatApiKeyUsage(provider) {
523876
524103
  `Usage: /connect ${provider.canonical} [api_key]`,
523877
524104
  "",
523878
524105
  `Connect to ${provider.byokProvider.displayName}. API key is optional for this local provider.`,
523879
- "Optional: --base-url <url> --timeout <ms|duration|false>"
524106
+ isConnectBaseURLRequired(provider) ? "Required: --base-url <url>. Optional: --timeout <ms|duration|false>" : "Optional: --base-url <url> --timeout <ms|duration|false>"
523880
524107
  ].join(`
523881
524108
  `);
523882
524109
  }
@@ -524172,6 +524399,12 @@ Usage: /connect chatgpt [--name <provider-name>]`, false);
524172
524399
  if (parsed.error) {
524173
524400
  addCommandResult(ctx.buffersRef, ctx.refreshDerived, msg, `${parsed.error}
524174
524401
 
524402
+ ${formatApiKeyUsage(provider)}`, false);
524403
+ return;
524404
+ }
524405
+ if (isConnectBaseURLRequired(provider) && !parsed.baseURL?.trim()) {
524406
+ addCommandResult(ctx.buffersRef, ctx.refreshDerived, msg, `Missing required field: --base-url <url>.
524407
+
524175
524408
  ${formatApiKeyUsage(provider)}`, false);
524176
524409
  return;
524177
524410
  }
@@ -555619,9 +555852,8 @@ Note: Flags should use double dashes for full names (e.g., --yolo, not -yolo)`);
555619
555852
  const command = positionals[2];
555620
555853
  if (values2.help) {
555621
555854
  printHelp();
555622
- const helpDelayMs = Number.parseInt(process.env.LETTA_TEST_HELP_EXIT_DELAY_MS ?? "", 10);
555623
- if (Number.isFinite(helpDelayMs) && helpDelayMs > 0) {
555624
- await new Promise((resolve40) => setTimeout(resolve40, helpDelayMs));
555855
+ if (process.env.LETTA_TEST_WAIT_FOR_STARTUP_AUTO_UPDATE === "1") {
555856
+ await autoUpdatePromise;
555625
555857
  }
555626
555858
  process.exit(0);
555627
555859
  }
@@ -558987,4 +559219,4 @@ function registerBunOAuthFlows() {
558987
559219
  registerBunOAuthFlows();
558988
559220
  await init_src5().then(() => exports_src2);
558989
559221
 
558990
- //# debugId=AF6DD74653AB022F64756E2164756E21
559222
+ //# debugId=AD09C8202E7874BF64756E2164756E21