@letta-ai/letta-code 0.30.25 → 0.30.26

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 (44) hide show
  1. package/dist/agent-presets.js +1 -339
  2. package/dist/agent-presets.js.map +2 -2
  3. package/dist/channels-slack.js +319 -41
  4. package/dist/channels-slack.js.map +4 -3
  5. package/dist/channels-telegram.js +424 -0
  6. package/dist/channels-telegram.js.map +14 -0
  7. package/dist/mcp-client.js +2 -2
  8. package/dist/mcp-client.js.map +1 -1
  9. package/dist/types/agent/modify.d.ts.map +1 -1
  10. package/dist/types/backend/dev/pi-model-factory.d.ts.map +1 -1
  11. package/dist/types/channels/slack/attachment-primitives.d.ts +69 -0
  12. package/dist/types/channels/slack/attachment-primitives.d.ts.map +1 -0
  13. package/dist/types/channels/telegram/debounce.d.ts +22 -0
  14. package/dist/types/channels/telegram/debounce.d.ts.map +1 -0
  15. package/dist/types/channels/telegram/ingress.d.ts +35 -0
  16. package/dist/types/channels/telegram/ingress.d.ts.map +1 -0
  17. package/dist/types/channels/telegram/message-action-contract.d.ts +16 -0
  18. package/dist/types/channels/telegram/message-action-contract.d.ts.map +1 -0
  19. package/dist/types/channels/telegram/message-shapes.d.ts +154 -0
  20. package/dist/types/channels/telegram/message-shapes.d.ts.map +1 -0
  21. package/dist/types/channels/telegram/outbound.d.ts +21 -0
  22. package/dist/types/channels/telegram/outbound.d.ts.map +1 -0
  23. package/dist/types/channels-slack.d.ts +2 -0
  24. package/dist/types/channels-slack.d.ts.map +1 -1
  25. package/dist/types/channels-telegram.d.ts +20 -0
  26. package/dist/types/channels-telegram.d.ts.map +1 -0
  27. package/dist/types/cli/helpers/stream-debug.d.ts +6 -0
  28. package/dist/types/cli/helpers/stream-debug.d.ts.map +1 -0
  29. package/dist/types/cli/helpers/stream-stall-reconciler.d.ts +16 -0
  30. package/dist/types/cli/helpers/stream-stall-reconciler.d.ts.map +1 -0
  31. package/dist/types/cli/helpers/stream.d.ts +1 -0
  32. package/dist/types/cli/helpers/stream.d.ts.map +1 -1
  33. package/dist/types/tools/impl/github-pull-request-tracker.d.ts +2 -0
  34. package/dist/types/tools/impl/github-pull-request-tracker.d.ts.map +1 -1
  35. package/dist/types/tools/impl/task.d.ts +2 -0
  36. package/dist/types/tools/impl/task.d.ts.map +1 -1
  37. package/dist/types/utils/openai-reasoning-effort.d.ts +6 -0
  38. package/dist/types/utils/openai-reasoning-effort.d.ts.map +1 -0
  39. package/dist/types/websocket/listener/constants.d.ts +0 -2
  40. package/dist/types/websocket/listener/constants.d.ts.map +1 -1
  41. package/letta.js +1547 -1770
  42. package/package.json +12 -1
  43. package/scripts/source-file-size-baseline.json +4 -5
  44. package/skills/teleporting-between-environments/SKILL.md +23 -9
package/letta.js CHANGED
@@ -5509,7 +5509,7 @@ var package_default;
5509
5509
  var init_package = __esm(() => {
5510
5510
  package_default = {
5511
5511
  name: "@letta-ai/letta-code",
5512
- version: "0.30.25",
5512
+ version: "0.30.26",
5513
5513
  description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
5514
5514
  type: "module",
5515
5515
  packageManager: "bun@1.3.10",
@@ -5543,6 +5543,8 @@ var init_package = __esm(() => {
5543
5543
  "dist/gateway-core.js.map",
5544
5544
  "dist/channels-slack.js",
5545
5545
  "dist/channels-slack.js.map",
5546
+ "dist/channels-telegram.js",
5547
+ "dist/channels-telegram.js.map",
5546
5548
  "dist/types",
5547
5549
  "docs"
5548
5550
  ],
@@ -5606,6 +5608,12 @@ var init_package = __esm(() => {
5606
5608
  browser: "./dist/channels-slack.js",
5607
5609
  import: "./dist/channels-slack.js",
5608
5610
  default: "./dist/channels-slack.js"
5611
+ },
5612
+ "./channels/telegram": {
5613
+ types: "./dist/types/channels-telegram.d.ts",
5614
+ browser: "./dist/channels-telegram.js",
5615
+ import: "./dist/channels-telegram.js",
5616
+ default: "./dist/channels-telegram.js"
5609
5617
  }
5610
5618
  },
5611
5619
  repository: {
@@ -5721,6 +5729,9 @@ var init_package = __esm(() => {
5721
5729
  "channels/slack": [
5722
5730
  "./dist/types/channels-slack.d.ts"
5723
5731
  ],
5732
+ "channels/telegram": [
5733
+ "./dist/types/channels-telegram.d.ts"
5734
+ ],
5724
5735
  protocol: [
5725
5736
  "./dist/types/types/protocol.d.ts"
5726
5737
  ]
@@ -140480,6 +140491,46 @@ var init_openai_codex_provider = __esm(() => {
140480
140491
  CHATGPT_OAUTH_PROVIDER_NAME_PATTERN = /^[A-Za-z0-9._-]+$/;
140481
140492
  });
140482
140493
 
140494
+ // src/utils/openai-reasoning-effort.ts
140495
+ function openaiModelNameFromHandle(modelHandleOrId) {
140496
+ if (!modelHandleOrId)
140497
+ return null;
140498
+ const trimmed = modelHandleOrId.trim().toLowerCase();
140499
+ if (!trimmed)
140500
+ return null;
140501
+ const slashIndex = trimmed.lastIndexOf("/");
140502
+ const unprefixed = slashIndex === -1 ? trimmed : trimmed.slice(slashIndex + 1);
140503
+ return unprefixed.endsWith("-fast") ? unprefixed.slice(0, -"-fast".length) : unprefixed;
140504
+ }
140505
+ function supportsNoneReasoningEffort(model) {
140506
+ return NONE_REASONING_MODEL_PREFIXES.some((prefix) => model === prefix || model.startsWith(`${prefix}-`));
140507
+ }
140508
+ function normalizeReasoningEffortForModel(modelHandleOrId, effort) {
140509
+ if (effort == null)
140510
+ return effort;
140511
+ const model = openaiModelNameFromHandle(modelHandleOrId);
140512
+ if (!model)
140513
+ return effort;
140514
+ if (effort === "minimal" && supportsNoneReasoningEffort(model)) {
140515
+ return "none";
140516
+ }
140517
+ if (effort === "max" && model.startsWith("gpt-5.5")) {
140518
+ return "xhigh";
140519
+ }
140520
+ return effort;
140521
+ }
140522
+ var NONE_REASONING_MODEL_PREFIXES;
140523
+ var init_openai_reasoning_effort = __esm(() => {
140524
+ NONE_REASONING_MODEL_PREFIXES = [
140525
+ "gpt-5.1",
140526
+ "gpt-5.2",
140527
+ "gpt-5.3",
140528
+ "gpt-5.4",
140529
+ "gpt-5.5",
140530
+ "gpt-5.6"
140531
+ ];
140532
+ });
140533
+
140483
140534
  // src/utils/type-guards.ts
140484
140535
  function isRecord(value) {
140485
140536
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -142282,59 +142333,6 @@ var init_models7 = __esm(() => {
142282
142333
  parallel_tool_calls: true
142283
142334
  }
142284
142335
  },
142285
- {
142286
- id: "bedrock-opus-4.6",
142287
- handle: "bedrock/us.anthropic.claude-opus-4-6-v1",
142288
- label: "Bedrock Opus 4.6",
142289
- shortLabel: "Opus 4.6 BR",
142290
- description: "Opus 4.6 via AWS Bedrock",
142291
- updateArgs: {
142292
- context_window: 180000,
142293
- max_output_tokens: 64000,
142294
- max_reasoning_tokens: 31999,
142295
- parallel_tool_calls: true
142296
- }
142297
- },
142298
- {
142299
- id: "bedrock-opus-4.7",
142300
- handle: "bedrock/us.anthropic.claude-opus-4-7",
142301
- label: "Bedrock Opus 4.7",
142302
- shortLabel: "Opus 4.7 BR",
142303
- description: "Opus 4.7 via AWS Bedrock",
142304
- updateArgs: {
142305
- context_window: 200000,
142306
- max_output_tokens: 128000,
142307
- reasoning_effort: "medium",
142308
- enable_reasoner: true,
142309
- parallel_tool_calls: true
142310
- }
142311
- },
142312
- {
142313
- id: "bedrock-sonnet-4.6",
142314
- handle: "bedrock/us.anthropic.claude-sonnet-4-6",
142315
- label: "Bedrock Sonnet 4.6",
142316
- shortLabel: "Sonnet 4.6 BR",
142317
- description: "Sonnet 4.6 via AWS Bedrock",
142318
- updateArgs: {
142319
- context_window: 180000,
142320
- max_output_tokens: 64000,
142321
- max_reasoning_tokens: 31999,
142322
- parallel_tool_calls: true
142323
- }
142324
- },
142325
- {
142326
- id: "bedrock-sonnet-5",
142327
- handle: "bedrock/us.anthropic.claude-sonnet-5",
142328
- label: "Bedrock Sonnet 5",
142329
- shortLabel: "Sonnet 5 BR",
142330
- description: "Sonnet 5 via AWS Bedrock",
142331
- updateArgs: {
142332
- context_window: 180000,
142333
- max_output_tokens: 64000,
142334
- max_reasoning_tokens: 31999,
142335
- parallel_tool_calls: true
142336
- }
142337
- },
142338
142336
  {
142339
142337
  id: "haiku",
142340
142338
  handle: "anthropic/claude-haiku-4-5",
@@ -142775,19 +142773,6 @@ var init_models7 = __esm(() => {
142775
142773
  parallel_tool_calls: true
142776
142774
  }
142777
142775
  },
142778
- {
142779
- id: "gpt-5-codex",
142780
- handle: "openai/gpt-5-codex",
142781
- label: "GPT-5-Codex",
142782
- description: "GPT-5 variant (med reasoning) optimized for coding",
142783
- updateArgs: {
142784
- reasoning_effort: "medium",
142785
- verbosity: "medium",
142786
- context_window: 272000,
142787
- max_output_tokens: 128000,
142788
- parallel_tool_calls: true
142789
- }
142790
- },
142791
142776
  {
142792
142777
  id: "gpt-5.5-none",
142793
142778
  handle: "openai/gpt-5.5",
@@ -142983,45 +142968,6 @@ var init_models7 = __esm(() => {
142983
142968
  parallel_tool_calls: true
142984
142969
  }
142985
142970
  },
142986
- {
142987
- id: "gpt-5.4-pro-medium",
142988
- handle: "openai/gpt-5.4-pro",
142989
- label: "GPT-5.4 Pro",
142990
- description: "GPT-5.4 Pro — max performance variant (med reasoning)",
142991
- updateArgs: {
142992
- reasoning_effort: "medium",
142993
- verbosity: "medium",
142994
- context_window: 272000,
142995
- max_output_tokens: 128000,
142996
- parallel_tool_calls: true
142997
- }
142998
- },
142999
- {
143000
- id: "gpt-5.4-pro-high",
143001
- handle: "openai/gpt-5.4-pro",
143002
- label: "GPT-5.4 Pro",
143003
- description: "GPT-5.4 Pro — max performance variant (high reasoning)",
143004
- updateArgs: {
143005
- reasoning_effort: "high",
143006
- verbosity: "medium",
143007
- context_window: 272000,
143008
- max_output_tokens: 128000,
143009
- parallel_tool_calls: true
143010
- }
143011
- },
143012
- {
143013
- id: "gpt-5.4-pro-xhigh",
143014
- handle: "openai/gpt-5.4-pro",
143015
- label: "GPT-5.4 Pro",
143016
- description: "GPT-5.4 Pro — max performance variant (max reasoning)",
143017
- updateArgs: {
143018
- reasoning_effort: "xhigh",
143019
- verbosity: "medium",
143020
- context_window: 272000,
143021
- max_output_tokens: 128000,
143022
- parallel_tool_calls: true
143023
- }
143024
- },
143025
142971
  {
143026
142972
  id: "gpt-5.4-mini-none",
143027
142973
  handle: "openai/gpt-5.4-mini",
@@ -143087,71 +143033,6 @@ var init_models7 = __esm(() => {
143087
143033
  parallel_tool_calls: true
143088
143034
  }
143089
143035
  },
143090
- {
143091
- id: "gpt-5.4-nano-none",
143092
- handle: "openai/gpt-5.4-nano",
143093
- label: "GPT-5.4 Nano",
143094
- description: "Smallest, cheapest GPT-5.4 variant (no reasoning)",
143095
- updateArgs: {
143096
- reasoning_effort: "none",
143097
- verbosity: "low",
143098
- context_window: 272000,
143099
- max_output_tokens: 128000,
143100
- parallel_tool_calls: true
143101
- }
143102
- },
143103
- {
143104
- id: "gpt-5.4-nano-low",
143105
- handle: "openai/gpt-5.4-nano",
143106
- label: "GPT-5.4 Nano",
143107
- description: "Smallest, cheapest GPT-5.4 variant (low reasoning)",
143108
- updateArgs: {
143109
- reasoning_effort: "low",
143110
- verbosity: "low",
143111
- context_window: 272000,
143112
- max_output_tokens: 128000,
143113
- parallel_tool_calls: true
143114
- }
143115
- },
143116
- {
143117
- id: "gpt-5.4-nano-medium",
143118
- handle: "openai/gpt-5.4-nano",
143119
- label: "GPT-5.4 Nano",
143120
- description: "Smallest, cheapest GPT-5.4 variant (med reasoning)",
143121
- updateArgs: {
143122
- reasoning_effort: "medium",
143123
- verbosity: "low",
143124
- context_window: 272000,
143125
- max_output_tokens: 128000,
143126
- parallel_tool_calls: true
143127
- }
143128
- },
143129
- {
143130
- id: "gpt-5.4-nano-high",
143131
- handle: "openai/gpt-5.4-nano",
143132
- label: "GPT-5.4 Nano",
143133
- description: "Smallest, cheapest GPT-5.4 variant (high reasoning)",
143134
- updateArgs: {
143135
- reasoning_effort: "high",
143136
- verbosity: "low",
143137
- context_window: 272000,
143138
- max_output_tokens: 128000,
143139
- parallel_tool_calls: true
143140
- }
143141
- },
143142
- {
143143
- id: "gpt-5.4-nano-xhigh",
143144
- handle: "openai/gpt-5.4-nano",
143145
- label: "GPT-5.4 Nano",
143146
- description: "Smallest, cheapest GPT-5.4 variant (max reasoning)",
143147
- updateArgs: {
143148
- reasoning_effort: "xhigh",
143149
- verbosity: "low",
143150
- context_window: 272000,
143151
- max_output_tokens: 128000,
143152
- parallel_tool_calls: true
143153
- }
143154
- },
143155
143036
  {
143156
143037
  id: "gpt-5.3-codex-none",
143157
143038
  handle: "openai/gpt-5.3-codex",
@@ -143217,45 +143098,6 @@ var init_models7 = __esm(() => {
143217
143098
  parallel_tool_calls: true
143218
143099
  }
143219
143100
  },
143220
- {
143221
- id: "gpt-5-mini-high",
143222
- handle: "openai/gpt-5-mini-2025-08-07",
143223
- label: "GPT-5-Mini",
143224
- description: "GPT-5-Mini (high reasoning)",
143225
- updateArgs: {
143226
- reasoning_effort: "high",
143227
- verbosity: "medium",
143228
- context_window: 272000,
143229
- max_output_tokens: 128000,
143230
- parallel_tool_calls: true
143231
- }
143232
- },
143233
- {
143234
- id: "gpt-5-mini-medium",
143235
- handle: "openai/gpt-5-mini-2025-08-07",
143236
- label: "GPT-5-Mini",
143237
- description: "GPT-5-Mini (medium reasoning)",
143238
- updateArgs: {
143239
- reasoning_effort: "medium",
143240
- verbosity: "medium",
143241
- context_window: 272000,
143242
- max_output_tokens: 128000,
143243
- parallel_tool_calls: true
143244
- }
143245
- },
143246
- {
143247
- id: "gpt-5-nano-medium",
143248
- handle: "openai/gpt-5-nano-2025-08-07",
143249
- label: "GPT-5-Nano",
143250
- description: "GPT-5-Nano (medium reasoning)",
143251
- updateArgs: {
143252
- reasoning_effort: "medium",
143253
- verbosity: "medium",
143254
- context_window: 272000,
143255
- max_output_tokens: 128000,
143256
- parallel_tool_calls: true
143257
- }
143258
- },
143259
143101
  {
143260
143102
  id: "grok-4.5",
143261
143103
  handle: "xai/grok-4.5",
@@ -143268,18 +143110,6 @@ var init_models7 = __esm(() => {
143268
143110
  parallel_tool_calls: true
143269
143111
  }
143270
143112
  },
143271
- {
143272
- id: "deepseek-v4-pro",
143273
- handle: "openrouter/deepseek/deepseek-v4-pro",
143274
- label: "DeepSeek V4 Pro",
143275
- description: "DeepSeek's V4 Pro model",
143276
- updateArgs: {
143277
- context_window: 1048576,
143278
- max_output_tokens: 384000,
143279
- parallel_tool_calls: true
143280
- },
143281
- isFeatured: true
143282
- },
143283
143113
  {
143284
143114
  id: "glm-5.2",
143285
143115
  handle: "zai/glm-5.2",
@@ -143329,17 +143159,6 @@ var init_models7 = __esm(() => {
143329
143159
  parallel_tool_calls: true
143330
143160
  }
143331
143161
  },
143332
- {
143333
- id: "minimax-m2",
143334
- handle: "openrouter/minimax/minimax-m2",
143335
- label: "MiniMax M2",
143336
- description: "MiniMax's M2 model",
143337
- updateArgs: {
143338
- context_window: 160000,
143339
- max_output_tokens: 64000,
143340
- parallel_tool_calls: true
143341
- }
143342
- },
143343
143162
  {
143344
143163
  id: "kimi-k3",
143345
143164
  handle: "moonshot/kimi-k3",
@@ -143352,50 +143171,6 @@ var init_models7 = __esm(() => {
143352
143171
  parallel_tool_calls: true
143353
143172
  }
143354
143173
  },
143355
- {
143356
- id: "kimi-k3-openrouter",
143357
- handle: "openrouter/moonshotai/kimi-k3",
143358
- label: "Kimi K3",
143359
- description: "Moonshot AI's Kimi K3 model for long-context agentic coding and reasoning tasks",
143360
- updateArgs: {
143361
- context_window: 1048576,
143362
- max_output_tokens: 131072,
143363
- parallel_tool_calls: true
143364
- }
143365
- },
143366
- {
143367
- id: "kimi-k2.7",
143368
- handle: "openrouter/moonshotai/kimi-k2.7-code",
143369
- label: "Kimi K2.7 Code",
143370
- description: "Moonshot AI's coding-focused Kimi K2.7 model for long-context agentic programming tasks",
143371
- isFeatured: true,
143372
- updateArgs: {
143373
- context_window: 262144,
143374
- max_output_tokens: 16384,
143375
- parallel_tool_calls: true
143376
- }
143377
- },
143378
- {
143379
- id: "kimi-k2.6",
143380
- handle: "openrouter/moonshotai/kimi-k2.6",
143381
- label: "Kimi K2.6",
143382
- description: "Moonshot AI's next-gen multimodal coding and agent model",
143383
- updateArgs: {
143384
- context_window: 200000,
143385
- max_output_tokens: 64000,
143386
- parallel_tool_calls: true
143387
- }
143388
- },
143389
- {
143390
- id: "deepseek-chat-v3.1",
143391
- handle: "openrouter/deepseek/deepseek-chat-v3.1",
143392
- label: "DeepSeek Chat V3.1",
143393
- description: "DeepSeek V3.1 model",
143394
- updateArgs: {
143395
- context_window: 128000,
143396
- parallel_tool_calls: true
143397
- }
143398
- },
143399
143174
  {
143400
143175
  id: "gemini-3.1",
143401
143176
  handle: "google_ai/gemini-3.1-pro-preview",
@@ -143430,68 +143205,6 @@ var init_models7 = __esm(() => {
143430
143205
  temperature: 1,
143431
143206
  parallel_tool_calls: true
143432
143207
  }
143433
- },
143434
- {
143435
- id: "gemini-3.1-flash-lite",
143436
- handle: "google_ai/gemini-3.1-flash-lite",
143437
- label: "Gemini 3.1 Flash-Lite",
143438
- description: "Google's lightweight Gemini 3.1 Flash-Lite model",
143439
- updateArgs: {
143440
- context_window: 1048576,
143441
- temperature: 1,
143442
- parallel_tool_calls: true
143443
- }
143444
- },
143445
- {
143446
- id: "gpt-4.1",
143447
- handle: "openai/gpt-4.1",
143448
- label: "GPT-4.1",
143449
- description: "OpenAI's most recent non-reasoner model",
143450
- updateArgs: {
143451
- context_window: 1047576,
143452
- parallel_tool_calls: true
143453
- }
143454
- },
143455
- {
143456
- id: "gpt-4.1-mini",
143457
- handle: "openai/gpt-4.1-mini-2025-04-14",
143458
- label: "GPT-4.1-Mini",
143459
- description: "OpenAI's most recent non-reasoner model (mini version)",
143460
- updateArgs: {
143461
- context_window: 1047576,
143462
- parallel_tool_calls: true
143463
- }
143464
- },
143465
- {
143466
- id: "gpt-4.1-nano",
143467
- handle: "openai/gpt-4.1-nano-2025-04-14",
143468
- label: "GPT-4.1-Nano",
143469
- description: "OpenAI's most recent non-reasoner model (nano version)",
143470
- updateArgs: {
143471
- context_window: 1047576,
143472
- parallel_tool_calls: true
143473
- }
143474
- },
143475
- {
143476
- id: "o4-mini",
143477
- handle: "openai/o4-mini",
143478
- label: "o4-mini",
143479
- description: "OpenAI's latest o-series reasoning model",
143480
- updateArgs: {
143481
- context_window: 180000,
143482
- parallel_tool_calls: true
143483
- }
143484
- },
143485
- {
143486
- id: "gemini-3.1-vertex",
143487
- handle: "google_vertex/gemini-3.1-pro-preview",
143488
- label: "Gemini 3.1 Pro",
143489
- description: "Google's latest Gemini 3.1 Pro model (via Vertex AI)",
143490
- updateArgs: {
143491
- context_window: 180000,
143492
- temperature: 1,
143493
- parallel_tool_calls: true
143494
- }
143495
143208
  }
143496
143209
  ]
143497
143210
  };
@@ -145589,7 +145302,9 @@ function buildModelSettings(modelHandle, updateArgs) {
145589
145302
  openaiSettings.provider_type = "chatgpt_oauth";
145590
145303
  }
145591
145304
  if (updateArgs && "reasoning_effort" in updateArgs) {
145592
- openaiSettings.reasoning = updateArgs.reasoning_effort === null ? null : { reasoning_effort: updateArgs.reasoning_effort };
145305
+ openaiSettings.reasoning = updateArgs.reasoning_effort === null ? null : {
145306
+ reasoning_effort: normalizeReasoningEffortForModel(modelHandle, String(updateArgs.reasoning_effort))
145307
+ };
145593
145308
  }
145594
145309
  const verbosity = updateArgs?.verbosity;
145595
145310
  if (verbosity === "low" || verbosity === "medium" || verbosity === "high") {
@@ -145695,7 +145410,9 @@ function buildModelSettings(modelHandle, updateArgs) {
145695
145410
  parallel_tool_calls: typeof updateArgs?.parallel_tool_calls === "boolean" ? updateArgs.parallel_tool_calls : true
145696
145411
  };
145697
145412
  if (updateArgs && "reasoning_effort" in updateArgs) {
145698
- openaiProxySettings.reasoning = updateArgs.reasoning_effort === null ? null : { reasoning_effort: updateArgs.reasoning_effort };
145413
+ openaiProxySettings.reasoning = updateArgs.reasoning_effort === null ? null : {
145414
+ reasoning_effort: normalizeReasoningEffortForModel(modelHandle, String(updateArgs.reasoning_effort))
145415
+ };
145699
145416
  }
145700
145417
  if (typeof updateArgs?.strict === "boolean") {
145701
145418
  openaiProxySettings.strict = updateArgs.strict;
@@ -146005,6 +145722,7 @@ var init_modify = __esm(() => {
146005
145722
  init_backend2();
146006
145723
  init_openai_codex_provider();
146007
145724
  init_debug();
145725
+ init_openai_reasoning_effort();
146008
145726
  init_available_models();
146009
145727
  init_model();
146010
145728
  __modifyTestUtils = {
@@ -154763,6 +154481,10 @@ var init_shell_env = __esm(() => {
154763
154481
  });
154764
154482
 
154765
154483
  // src/tools/impl/github-pull-request-tracker.ts
154484
+ function conversationTags(conversation) {
154485
+ const tags = typeof conversation === "object" && conversation !== null ? Reflect.get(conversation, "tags") : undefined;
154486
+ return Array.isArray(tags) ? tags.filter((tag) => typeof tag === "string") : [];
154487
+ }
154766
154488
  function executableName(value) {
154767
154489
  return value.replaceAll("\\", "/").split("/").pop()?.toLowerCase() ?? "";
154768
154490
  }
@@ -154893,15 +154615,14 @@ function tagFromOutputLine(line) {
154893
154615
  if (!owner || !repo || !number7) {
154894
154616
  return;
154895
154617
  }
154896
- return `github:pull-request:${owner.toLowerCase()}:${repo.toLowerCase()}:${number7}`;
154618
+ return `${GITHUB_PR_TAG_PREFIX}${owner.toLowerCase()}:${repo.toLowerCase()}:${number7}`;
154897
154619
  }
154898
154620
  function appendOutputTail(outputByStream, text, stream11) {
154899
154621
  outputByStream[stream11] = `${outputByStream[stream11]}${text}`.slice(-MAX_TRACKED_OUTPUT_CHARS);
154900
154622
  }
154901
154623
  async function appendConversationTags(backend, conversationId, tags) {
154902
154624
  const conversation = await backend.retrieveConversation(conversationId);
154903
- const currentTags = typeof conversation === "object" && conversation !== null ? Reflect.get(conversation, "tags") : undefined;
154904
- const existingTags = Array.isArray(currentTags) ? currentTags.filter((tag) => typeof tag === "string") : [];
154625
+ const existingTags = conversationTags(conversation);
154905
154626
  const missingTags = tags.filter((tag) => !existingTags.includes(tag));
154906
154627
  if (missingTags.length === 0) {
154907
154628
  return;
@@ -154923,6 +154644,22 @@ function queueConversationTagUpdate(backend, conversationId, tags) {
154923
154644
  });
154924
154645
  return update2;
154925
154646
  }
154647
+ async function copyGitHubPullRequestTags(sourceConversationId, targetConversationId, backend) {
154648
+ if (!sourceConversationId || !targetConversationId || sourceConversationId === "default" || targetConversationId === "default" || sourceConversationId === targetConversationId) {
154649
+ return;
154650
+ }
154651
+ try {
154652
+ const activeBackend = backend ?? getBackend();
154653
+ const sourceConversation = await activeBackend.retrieveConversation(sourceConversationId);
154654
+ const pullRequestTags = conversationTags(sourceConversation).filter((tag) => tag.startsWith(GITHUB_PR_TAG_PREFIX));
154655
+ if (pullRequestTags.length === 0) {
154656
+ return;
154657
+ }
154658
+ await queueConversationTagUpdate(activeBackend, targetConversationId, pullRequestTags);
154659
+ } catch (error54) {
154660
+ debugLog("github-pr-tracking", `Failed to copy PR tags from ${sourceConversationId} to ${targetConversationId}`, error54);
154661
+ }
154662
+ }
154926
154663
  function createGitHubPullRequestOutputTracker(command, options3) {
154927
154664
  if (!isGitHubPullRequestCreateCommand(command)) {
154928
154665
  return;
@@ -154969,7 +154706,7 @@ ${outputByStream.stderr}`.split(/\r\n|\n|\r/)) {
154969
154706
  }
154970
154707
  };
154971
154708
  }
154972
- var ENV_ASSIGNMENT, GITHUB_PR_URL, HEREDOC_AT_LINE_END, MAX_TRACKED_OUTPUT_CHARS = 30000, GH_GLOBAL_FLAGS_WITH_VALUES, conversationTagUpdateTails;
154709
+ var ENV_ASSIGNMENT, GITHUB_PR_URL, GITHUB_PR_TAG_PREFIX = "github:pull-request:", HEREDOC_AT_LINE_END, MAX_TRACKED_OUTPUT_CHARS = 30000, GH_GLOBAL_FLAGS_WITH_VALUES, conversationTagUpdateTails;
154973
154710
  var init_github_pull_request_tracker = __esm(() => {
154974
154711
  init_strip_ansi();
154975
154712
  init_backend2();
@@ -158255,33 +157992,10 @@ var init_background_process_snapshot = __esm(() => {
158255
157992
  });
158256
157993
 
158257
157994
  // src/websocket/listener/constants.ts
158258
- var MAX_RETRY_DURATION_MS, INITIAL_RETRY_DELAY_MS2 = 1000, MAX_RETRY_DELAY_MS = 30000, LISTENER_STREAM_OPEN_TIMEOUT_MS = 30000, LISTENER_HEARTBEAT_INTERVAL_MS = 30000, LISTENER_PONG_TIMEOUT_MS = 90000, SYSTEM_REMINDER_RE, LLM_API_ERROR_MAX_RETRIES = 3, EMPTY_RESPONSE_MAX_RETRIES = 2, MAX_PRE_STREAM_RECOVERY = 2, MAX_POST_STOP_APPROVAL_RECOVERY = 2, PROVIDER_FALLBACK_MAP, PROVIDER_FALLBACK_NOTICE = "Anthropic API error; falling back to Bedrock...";
157995
+ var MAX_RETRY_DURATION_MS, INITIAL_RETRY_DELAY_MS2 = 1000, MAX_RETRY_DELAY_MS = 30000, LISTENER_STREAM_OPEN_TIMEOUT_MS = 30000, LISTENER_HEARTBEAT_INTERVAL_MS = 30000, LISTENER_PONG_TIMEOUT_MS = 90000, SYSTEM_REMINDER_RE, LLM_API_ERROR_MAX_RETRIES = 3, EMPTY_RESPONSE_MAX_RETRIES = 2, MAX_PRE_STREAM_RECOVERY = 2, MAX_POST_STOP_APPROVAL_RECOVERY = 2;
158259
157996
  var init_constants3 = __esm(() => {
158260
157997
  MAX_RETRY_DURATION_MS = 5 * 60 * 1000;
158261
157998
  SYSTEM_REMINDER_RE = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
158262
- PROVIDER_FALLBACK_MAP = {
158263
- "opus-4.7-low": "bedrock-opus-4.7",
158264
- "opus-4.7-medium": "bedrock-opus-4.7",
158265
- "opus-4.7-high": "bedrock-opus-4.7",
158266
- "opus-4.7-xhigh": "bedrock-opus-4.7",
158267
- "opus-4.7-max": "bedrock-opus-4.7",
158268
- "opus-4.6-no-reasoning": "bedrock-opus-4.6",
158269
- "opus-4.6-low": "bedrock-opus-4.6",
158270
- "opus-4.6-medium": "bedrock-opus-4.6",
158271
- "opus-4.6-high": "bedrock-opus-4.6",
158272
- "opus-4.6-xhigh": "bedrock-opus-4.6",
158273
- sonnet: "bedrock-sonnet-5",
158274
- "sonnet-5-no-reasoning": "bedrock-sonnet-5",
158275
- "sonnet-5-low": "bedrock-sonnet-5",
158276
- "sonnet-5-medium": "bedrock-sonnet-5",
158277
- "sonnet-5-xhigh": "bedrock-sonnet-5",
158278
- "sonnet-4.6": "bedrock-sonnet-4.6",
158279
- "sonnet-1m": "bedrock-sonnet-4.6",
158280
- "sonnet-4.6-no-reasoning": "bedrock-sonnet-4.6",
158281
- "sonnet-4.6-low": "bedrock-sonnet-4.6",
158282
- "sonnet-4.6-medium": "bedrock-sonnet-4.6",
158283
- "sonnet-4.6-xhigh": "bedrock-sonnet-4.6"
158284
- };
158285
157999
  });
158286
158000
 
158287
158001
  // src/websocket/listener/device-status-cache.ts
@@ -175734,6 +175448,7 @@ function spawnBackgroundSubagentTask(args) {
175734
175448
  const shouldEmitCompletionNotification = emitCompletionNotification ?? !silentCompletion;
175735
175449
  const resolvedParentScope = resolveNotificationScope(parentScope);
175736
175450
  const spawnSubagentFn = deps?.spawnSubagentImpl ?? spawnSubagent;
175451
+ const copyGitHubPullRequestTagsFn = deps?.copyGitHubPullRequestTagsImpl ?? copyGitHubPullRequestTags;
175737
175452
  const addToMessageQueueFn = deps?.addToMessageQueueImpl ?? addToMessageQueue;
175738
175453
  const formatTaskNotificationFn = deps?.formatTaskNotificationImpl ?? formatTaskNotification;
175739
175454
  const runSubagentStopHooksFn = deps?.runSubagentStopHooksImpl ?? runSubagentStopHooks;
@@ -175762,6 +175477,7 @@ function spawnBackgroundSubagentTask(args) {
175762
175477
  writeTaskTranscriptStart(outputFile, description, subagentType);
175763
175478
  const parentAgentIdForSpawn = resolvedParentScope?.agentId;
175764
175479
  spawnSubagentFn(subagentType, prompt, model, subagentId, abortController.signal, existingAgentId, existingConversationId, maxTurns, forkedContext, parentAgentIdForSpawn, transcriptPath, resolvedParentScope?.conversationId, memoryScope, systemPromptOverride).then(async (result) => {
175480
+ await copyGitHubPullRequestTagsFn(result.conversationId, resolvedParentScope?.conversationId);
175765
175481
  bgTask.status = result.success ? "completed" : "failed";
175766
175482
  if (result.error) {
175767
175483
  bgTask.error = result.error;
@@ -175979,6 +175695,7 @@ You will be notified automatically when this task completes — a <task-notifica
175979
175695
  try {
175980
175696
  const parentAgentIdForSpawn = resolvedParentScope?.agentId;
175981
175697
  const result = await spawnSubagent(subagent_type, prompt, model, subagentId, signal, effectiveAgentId, effectiveConversationId, args.max_turns, config3.fork, parentAgentIdForSpawn, undefined, resolvedParentScope?.conversationId);
175698
+ await copyGitHubPullRequestTags(result.conversationId, resolvedParentScope?.conversationId);
175982
175699
  completeSubagent(subagentId, {
175983
175700
  success: result.success,
175984
175701
  error: result.error,
@@ -176039,6 +175756,7 @@ var init_task = __esm(() => {
176039
175756
  init_settings_manager();
176040
175757
  init_message_queue_bridge();
176041
175758
  init_task_notifications();
175759
+ init_github_pull_request_tracker();
176042
175760
  init_process_manager();
176043
175761
  init_truncation();
176044
175762
  VALID_DEPLOY_TYPES = new Set(["general-purpose"]);
@@ -192320,6 +192038,8 @@ function thinkingLevelSetting(value, preserveMax) {
192320
192038
  const effort = settingString(value);
192321
192039
  if (effort === "max")
192322
192040
  return preserveMax ? "max" : "xhigh";
192041
+ if (effort === "none")
192042
+ return "none";
192323
192043
  return effort === "minimal" || effort === "low" || effort === "medium" || effort === "high" || effort === "xhigh" ? effort : undefined;
192324
192044
  }
192325
192045
  function reasoningForSettings(modelSettings, modelHandle) {
@@ -192329,7 +192049,8 @@ function reasoningForSettings(modelSettings, modelHandle) {
192329
192049
  const nestedReasoning = isRecord(modelSettings.reasoning) ? modelSettings.reasoning : undefined;
192330
192050
  const modelId = modelHandle?.slice(modelHandle.indexOf("/") + 1);
192331
192051
  const preserveMax = modelId?.startsWith("gpt-5.6") === true;
192332
- return thinkingLevelSetting(nestedReasoning?.reasoning_effort, preserveMax) ?? thinkingLevelSetting(modelSettings.effort, preserveMax) ?? thinkingLevelSetting(modelSettings.reasoning_effort, preserveMax);
192052
+ const rawEffort = nestedReasoning?.reasoning_effort ?? modelSettings.effort ?? modelSettings.reasoning_effort;
192053
+ return thinkingLevelSetting(normalizeReasoningEffortForModel(modelHandle, typeof rawEffort === "string" ? rawEffort : undefined), preserveMax);
192333
192054
  }
192334
192055
  function applyPiEnvOverrides(overrides) {
192335
192056
  if (!overrides)
@@ -192591,6 +192312,7 @@ var init_pi_model_factory = __esm(() => {
192591
192312
  init_local_pi_credential_store();
192592
192313
  init_local_provider_auth_store();
192593
192314
  init_local_provider_timeout();
192315
+ init_openai_reasoning_effort();
192594
192316
  init_pi_models_runtime();
192595
192317
  init_pi_provider_mod_registry();
192596
192318
  init_pi_provider_registry();
@@ -235726,6 +235448,26 @@ var init_recent_agent_options = __esm(() => {
235726
235448
  init_settings_manager();
235727
235449
  });
235728
235450
 
235451
+ // src/agent/reasoning-effort-label.ts
235452
+ function catalogHasDistinctMaxTier(params) {
235453
+ const { modelLabel, modelHandle } = params;
235454
+ if (!modelLabel && !modelHandle)
235455
+ return false;
235456
+ return models2.some((model) => {
235457
+ if (model.updateArgs?.reasoning_effort !== "max")
235458
+ return false;
235459
+ if (modelHandle && model.handle === modelHandle)
235460
+ return true;
235461
+ return Boolean(modelLabel && model.label === modelLabel);
235462
+ });
235463
+ }
235464
+ function formatXhighEffortLabel(hasDistinctMaxTier) {
235465
+ return hasDistinctMaxTier ? "Extra High" : "Max";
235466
+ }
235467
+ var init_reasoning_effort_label = __esm(() => {
235468
+ init_model_catalog();
235469
+ });
235470
+
235729
235471
  // src/cli/components/ModelReasoningSelector.tsx
235730
235472
  function formatEffortLabel(effort, hasDistinctMaxTier) {
235731
235473
  if (effort === null)
@@ -235733,7 +235475,7 @@ function formatEffortLabel(effort, hasDistinctMaxTier) {
235733
235475
  if (effort === "none")
235734
235476
  return "Off";
235735
235477
  if (effort === "xhigh")
235736
- return hasDistinctMaxTier ? "Extra-High" : "Max";
235478
+ return formatXhighEffortLabel(hasDistinctMaxTier);
235737
235479
  if (effort === "max")
235738
235480
  return "Max";
235739
235481
  if (effort === "minimal")
@@ -235875,6 +235617,7 @@ function ModelReasoningSelector({
235875
235617
  }
235876
235618
  var import_react27, jsx_dev_runtime8, SOLID_LINE2 = "─", EFFORT_BLOCK = "▌";
235877
235619
  var init_ModelReasoningSelector = __esm(async () => {
235620
+ init_reasoning_effort_label();
235878
235621
  init_use_terminal_width();
235879
235622
  init_colors();
235880
235623
  await __promiseAll([
@@ -239831,195 +239574,7 @@ var init_lifecycle_error = __esm(() => {
239831
239574
  CHANNEL_LIFECYCLE_CONVERSATION_BUSY_TITLE = CONVERSATION_BUSY_TITLE;
239832
239575
  });
239833
239576
 
239834
- // src/channels/transcription/index.ts
239835
- var exports_transcription = {};
239836
- __export(exports_transcription, {
239837
- transcribeAudioFile: () => transcribeAudioFile,
239838
- isTranscriptionConfigured: () => isTranscriptionConfigured
239839
- });
239840
- import { execFileSync as execFileSync4 } from "node:child_process";
239841
- import { mkdtempSync as mkdtempSync3, readFileSync as readFileSync22, rmSync as rmSync8 } from "node:fs";
239842
- import { tmpdir as tmpdir8 } from "node:os";
239843
- import { basename as basename12, extname as extname5, join as join41 } from "node:path";
239844
- function audioMimeTypeForPath(localPath) {
239845
- switch (extname5(localPath).toLowerCase()) {
239846
- case ".flac":
239847
- return "audio/flac";
239848
- case ".m4a":
239849
- return "audio/mp4";
239850
- case ".mp3":
239851
- case ".mpeg":
239852
- case ".mpga":
239853
- return "audio/mpeg";
239854
- case ".mp4":
239855
- return "video/mp4";
239856
- case ".oga":
239857
- case ".ogg":
239858
- return "audio/ogg";
239859
- case ".wav":
239860
- return "audio/wav";
239861
- case ".webm":
239862
- return "audio/webm";
239863
- default:
239864
- return "audio/mp4";
239865
- }
239866
- }
239867
- function prepareOpenAiTranscriptionFile(localPath) {
239868
- if (OPENAI_SUPPORTED_AUDIO_EXTENSIONS.has(extname5(localPath).toLowerCase())) {
239869
- return { localPath };
239870
- }
239871
- const tempDir = mkdtempSync3(join41(tmpdir8(), "letta-transcription-"));
239872
- const convertedPath = join41(tempDir, `${basename12(localPath)}.m4a`);
239873
- try {
239874
- execFileSync4("ffmpeg", [
239875
- "-y",
239876
- "-loglevel",
239877
- "error",
239878
- "-i",
239879
- localPath,
239880
- "-vn",
239881
- "-c:a",
239882
- "aac",
239883
- convertedPath
239884
- ], { stdio: "ignore", timeout: TRANSCRIPTION_TIMEOUT_MS });
239885
- } catch (error54) {
239886
- rmSync8(tempDir, { recursive: true, force: true });
239887
- throw new Error(`Unsupported audio format ${extname5(localPath).replace(/^\./, "") || "unknown"}; ffmpeg conversion failed. ffmpeg is required to transcribe this audio format; install ffmpeg on the channel listener machine. ${error54 instanceof Error ? error54.message : String(error54)}`);
239888
- }
239889
- return {
239890
- localPath: convertedPath,
239891
- cleanup: () => rmSync8(tempDir, { recursive: true, force: true })
239892
- };
239893
- }
239894
- function isTranscriptionConfigured() {
239895
- return !!process.env.OPENAI_API_KEY;
239896
- }
239897
- async function transcribeAudioFile(localPath) {
239898
- const apiKey = process.env.OPENAI_API_KEY;
239899
- if (!apiKey) {
239900
- return {
239901
- success: false,
239902
- error: "OPENAI_API_KEY not set; transcription skipped."
239903
- };
239904
- }
239905
- try {
239906
- const prepared = prepareOpenAiTranscriptionFile(localPath);
239907
- try {
239908
- const buffer = readFileSync22(prepared.localPath);
239909
- const filename = basename12(prepared.localPath);
239910
- const formData = new FormData;
239911
- const blob = new Blob([buffer], {
239912
- type: audioMimeTypeForPath(prepared.localPath)
239913
- });
239914
- formData.append("file", blob, filename);
239915
- formData.append("model", OPENAI_TRANSCRIPTION_MODEL);
239916
- const controller = new AbortController;
239917
- const timeout = setTimeout(() => controller.abort(), TRANSCRIPTION_TIMEOUT_MS);
239918
- try {
239919
- const response = await fetch(OPENAI_TRANSCRIPTION_API_URL, {
239920
- method: "POST",
239921
- headers: { Authorization: `Bearer ${apiKey}` },
239922
- body: formData,
239923
- signal: controller.signal
239924
- });
239925
- if (!response.ok) {
239926
- const errorText = await response.text();
239927
- return {
239928
- success: false,
239929
- error: `OpenAI transcription API error (${response.status}): ${errorText}`
239930
- };
239931
- }
239932
- const data = await response.json();
239933
- return { success: true, text: data.text };
239934
- } finally {
239935
- clearTimeout(timeout);
239936
- }
239937
- } finally {
239938
- prepared.cleanup?.();
239939
- }
239940
- } catch (error54) {
239941
- return {
239942
- success: false,
239943
- error: error54 instanceof Error ? error54.message : String(error54)
239944
- };
239945
- }
239946
- }
239947
- var OPENAI_TRANSCRIPTION_API_URL = "https://api.openai.com/v1/audio/transcriptions", OPENAI_TRANSCRIPTION_MODEL = "gpt-4o-transcribe", TRANSCRIPTION_TIMEOUT_MS = 30000, OPENAI_SUPPORTED_AUDIO_EXTENSIONS;
239948
- var init_transcription = __esm(() => {
239949
- OPENAI_SUPPORTED_AUDIO_EXTENSIONS = new Set([
239950
- ".flac",
239951
- ".m4a",
239952
- ".mp3",
239953
- ".mp4",
239954
- ".mpeg",
239955
- ".mpga",
239956
- ".oga",
239957
- ".ogg",
239958
- ".wav",
239959
- ".webm"
239960
- ]);
239961
- });
239962
-
239963
- // src/channels/telegram/media.ts
239964
- import { randomUUID as randomUUID12 } from "node:crypto";
239965
- import { mkdir as mkdir8, writeFile as writeFile10 } from "node:fs/promises";
239966
- import { basename as basename13, extname as extname6, join as join42 } from "node:path";
239967
- function normalizeTelegramMimeType(mimeType) {
239968
- const normalized = mimeType?.split(";")[0]?.trim().toLowerCase();
239969
- return normalized || undefined;
239970
- }
239971
- function sanitizeTelegramPathSegment(input) {
239972
- const cleaned = input.replace(/[^A-Za-z0-9._-]/g, "_").replace(/^_+|_+$/g, "");
239973
- return cleaned || "attachment";
239974
- }
239975
- function coerceSizeBytes(value) {
239976
- if (typeof value === "number" && Number.isFinite(value)) {
239977
- return value;
239978
- }
239979
- return;
239980
- }
239981
- function isImageMimeType(mimeType) {
239982
- return normalizeTelegramMimeType(mimeType)?.startsWith("image/") ?? false;
239983
- }
239984
- function canInlineTelegramImage(mimeType) {
239985
- const normalized = normalizeTelegramMimeType(mimeType);
239986
- return !!normalized && normalized.startsWith("image/") && normalized !== "image/svg+xml";
239987
- }
239988
- function isAudioMimeType(mimeType) {
239989
- return normalizeTelegramMimeType(mimeType)?.startsWith("audio/") ?? false;
239990
- }
239991
- function isVideoMimeType(mimeType) {
239992
- return normalizeTelegramMimeType(mimeType)?.startsWith("video/") ?? false;
239993
- }
239994
- function isGenericTelegramMimeType(mimeType) {
239995
- const normalized = normalizeTelegramMimeType(mimeType);
239996
- return !normalized || normalized === "application/octet-stream";
239997
- }
239998
- function inferAttachmentKind(params) {
239999
- if (isImageMimeType(params.mimeType)) {
240000
- return "image";
240001
- }
240002
- if (isAudioMimeType(params.mimeType)) {
240003
- return "audio";
240004
- }
240005
- if (isVideoMimeType(params.mimeType)) {
240006
- return "video";
240007
- }
240008
- const lowerName = params.fileName?.toLowerCase();
240009
- if (lowerName) {
240010
- const extension = extname6(lowerName);
240011
- if (IMAGE_EXTENSIONS3.has(extension) || STATIC_STICKER_EXTENSIONS.has(extension)) {
240012
- return "image";
240013
- }
240014
- if (AUDIO_EXTENSIONS.has(extension) || VOICE_EXTENSIONS.has(extension)) {
240015
- return "audio";
240016
- }
240017
- if (VIDEO_EXTENSIONS.has(extension) || ANIMATION_EXTENSIONS.has(extension)) {
240018
- return "video";
240019
- }
240020
- }
240021
- return params.fallback;
240022
- }
239577
+ // src/channels/telegram/message-shapes.ts
240023
239578
  function extractTelegramMessageText(message) {
240024
239579
  if (typeof message.text === "string") {
240025
239580
  return message.text;
@@ -240035,351 +239590,8 @@ function getTelegramSenderName(message) {
240035
239590
  }
240036
239591
  return message.from.username ?? ([message.from.first_name, message.from.last_name].filter(Boolean).join(" ") || undefined);
240037
239592
  }
240038
- function collectTelegramAttachmentCandidates(message) {
240039
- const attachments = [];
240040
- if (Array.isArray(message.photo) && message.photo.length > 0) {
240041
- const photo = message.photo[message.photo.length - 1];
240042
- if (photo?.file_id) {
240043
- attachments.push({
240044
- fileId: photo.file_id,
240045
- kind: "image",
240046
- name: `photo-${photo.file_unique_id ?? photo.file_id}.jpg`,
240047
- mimeType: "image/jpeg",
240048
- sizeBytes: coerceSizeBytes(photo.file_size)
240049
- });
240050
- }
240051
- }
240052
- if (message.document?.file_id) {
240053
- attachments.push({
240054
- fileId: message.document.file_id,
240055
- kind: inferAttachmentKind({
240056
- mimeType: message.document.mime_type,
240057
- fileName: message.document.file_name,
240058
- fallback: "file"
240059
- }),
240060
- name: message.document.file_name,
240061
- mimeType: message.document.mime_type,
240062
- sizeBytes: coerceSizeBytes(message.document.file_size)
240063
- });
240064
- }
240065
- if (message.video?.file_id) {
240066
- attachments.push({
240067
- fileId: message.video.file_id,
240068
- kind: "video",
240069
- name: message.video.file_name ?? `video-${message.video.file_unique_id ?? message.video.file_id}.mp4`,
240070
- mimeType: message.video.mime_type,
240071
- sizeBytes: coerceSizeBytes(message.video.file_size)
240072
- });
240073
- }
240074
- if (message.audio?.file_id) {
240075
- attachments.push({
240076
- fileId: message.audio.file_id,
240077
- kind: "audio",
240078
- name: message.audio.file_name ?? `audio-${message.audio.file_unique_id ?? message.audio.file_id}.mp3`,
240079
- mimeType: message.audio.mime_type,
240080
- sizeBytes: coerceSizeBytes(message.audio.file_size)
240081
- });
240082
- }
240083
- if (message.voice?.file_id) {
240084
- attachments.push({
240085
- fileId: message.voice.file_id,
240086
- kind: "audio",
240087
- name: `voice-${message.voice.file_unique_id ?? message.voice.file_id}.ogg`,
240088
- mimeType: message.voice.mime_type,
240089
- sizeBytes: coerceSizeBytes(message.voice.file_size),
240090
- isVoice: true
240091
- });
240092
- }
240093
- if (message.animation?.file_id) {
240094
- attachments.push({
240095
- fileId: message.animation.file_id,
240096
- kind: "video",
240097
- name: message.animation.file_name ?? `animation-${message.animation.file_unique_id ?? message.animation.file_id}.gif`,
240098
- mimeType: message.animation.mime_type,
240099
- sizeBytes: coerceSizeBytes(message.animation.file_size)
240100
- });
240101
- }
240102
- if (message.sticker?.file_id && !message.sticker.is_animated && !message.sticker.is_video) {
240103
- attachments.push({
240104
- fileId: message.sticker.file_id,
240105
- kind: "image",
240106
- name: `sticker-${message.sticker.file_unique_id ?? message.sticker.file_id}.webp`,
240107
- mimeType: message.sticker.mime_type ?? "image/webp",
240108
- sizeBytes: coerceSizeBytes(message.sticker.file_size)
240109
- });
240110
- }
240111
- return attachments;
240112
- }
240113
- function inferUploadMethodFromMimeType(mimeType) {
240114
- const normalized = normalizeTelegramMimeType(mimeType);
240115
- if (!normalized) {
240116
- return null;
240117
- }
240118
- if (["image/png", "image/jpeg"].includes(normalized)) {
240119
- return "photo";
240120
- }
240121
- if (normalized === "image/gif") {
240122
- return "animation";
240123
- }
240124
- if (normalized === "image/webp") {
240125
- return "document";
240126
- }
240127
- if (normalized.startsWith("video/")) {
240128
- return "video";
240129
- }
240130
- if (["audio/ogg", "audio/opus"].includes(normalized)) {
240131
- return "voice";
240132
- }
240133
- if (normalized.startsWith("audio/")) {
240134
- return "audio";
240135
- }
240136
- return null;
240137
- }
240138
- function detectTelegramUploadMethod(filePath, fileName) {
240139
- const inferredName = (fileName ?? basename13(filePath)).toLowerCase();
240140
- const extension = extname6(inferredName);
240141
- const mimeType = inferMimeTypeFromName(inferredName);
240142
- const byMimeType = inferUploadMethodFromMimeType(mimeType);
240143
- if (byMimeType) {
240144
- return byMimeType;
240145
- }
240146
- if (ANIMATION_EXTENSIONS.has(extension)) {
240147
- return "animation";
240148
- }
240149
- if (VIDEO_EXTENSIONS.has(extension)) {
240150
- return "video";
240151
- }
240152
- if (VOICE_EXTENSIONS.has(extension)) {
240153
- return "voice";
240154
- }
240155
- if (AUDIO_EXTENSIONS.has(extension)) {
240156
- return "audio";
240157
- }
240158
- return "document";
240159
- }
240160
- function inferMimeTypeFromName(name) {
240161
- const normalized = name.toLowerCase();
240162
- const extension = extname6(normalized);
240163
- if (IMAGE_EXTENSIONS3.has(extension)) {
240164
- return extension === ".png" ? "image/png" : "image/jpeg";
240165
- }
240166
- if (ANIMATION_EXTENSIONS.has(extension)) {
240167
- return "image/gif";
240168
- }
240169
- if (STATIC_STICKER_EXTENSIONS.has(extension)) {
240170
- return "image/webp";
240171
- }
240172
- if (extension === ".mp4" || extension === ".m4v") {
240173
- return "video/mp4";
240174
- }
240175
- if (extension === ".mov") {
240176
- return "video/quicktime";
240177
- }
240178
- if (extension === ".webm") {
240179
- return "video/webm";
240180
- }
240181
- if (extension === ".mp3") {
240182
- return "audio/mpeg";
240183
- }
240184
- if (extension === ".m4a") {
240185
- return "audio/mp4";
240186
- }
240187
- if (extension === ".wav") {
240188
- return "audio/wav";
240189
- }
240190
- if (VOICE_EXTENSIONS.has(extension)) {
240191
- return "audio/ogg";
240192
- }
240193
- if (extension === ".pdf") {
240194
- return "application/pdf";
240195
- }
240196
- if (extension === ".txt") {
240197
- return "text/plain";
240198
- }
240199
- if (extension === ".md") {
240200
- return "text/markdown";
240201
- }
240202
- if (extension === ".json") {
240203
- return "application/json";
240204
- }
240205
- return;
240206
- }
240207
- function extensionForMimeType(mimeType) {
240208
- switch (normalizeTelegramMimeType(mimeType)) {
240209
- case "image/png":
240210
- return ".png";
240211
- case "image/jpeg":
240212
- return ".jpg";
240213
- case "image/gif":
240214
- return ".gif";
240215
- case "image/webp":
240216
- return ".webp";
240217
- case "video/mp4":
240218
- return ".mp4";
240219
- case "video/quicktime":
240220
- return ".mov";
240221
- case "video/webm":
240222
- return ".webm";
240223
- case "audio/mpeg":
240224
- return ".mp3";
240225
- case "audio/mp4":
240226
- return ".m4a";
240227
- case "audio/wav":
240228
- case "audio/x-wav":
240229
- case "audio/wave":
240230
- return ".wav";
240231
- case "audio/ogg":
240232
- case "audio/opus":
240233
- return ".ogg";
240234
- case "application/pdf":
240235
- return ".pdf";
240236
- case "text/plain":
240237
- return ".txt";
240238
- case "text/markdown":
240239
- return ".md";
240240
- case "application/json":
240241
- return ".json";
240242
- default:
240243
- return "";
240244
- }
240245
- }
240246
- function buildTelegramFileUrl(token2, filePath) {
240247
- return `https://api.telegram.org/file/bot${token2}/${filePath}`;
240248
- }
240249
- function inferAttachmentFileName(params) {
240250
- const hintedName = params.candidate.name?.trim() || basename13(params.remotePath) || "attachment";
240251
- if (extname6(hintedName)) {
240252
- return hintedName;
240253
- }
240254
- const extension = extensionForMimeType(params.responseMimeType) || extensionForMimeType(params.candidate.mimeType);
240255
- return extension ? `${hintedName}${extension}` : hintedName;
240256
- }
240257
- async function saveTelegramAttachment(params) {
240258
- const inboundDir = join42(getChannelDir("telegram"), "inbound", sanitizeTelegramPathSegment(params.accountId));
240259
- await mkdir8(inboundDir, { recursive: true });
240260
- const filePath = join42(inboundDir, `${Date.now()}-${randomUUID12()}-${sanitizeTelegramPathSegment(params.fileName)}`);
240261
- await writeFile10(filePath, params.buffer);
240262
- return filePath;
240263
- }
240264
- async function fetchTelegramFile(url2, timeoutMs) {
240265
- const controller = new AbortController;
240266
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
240267
- try {
240268
- return await fetch(url2, { signal: controller.signal });
240269
- } finally {
240270
- clearTimeout(timeout);
240271
- }
240272
- }
240273
- async function downloadTelegramAttachment(params) {
240274
- const { candidate } = params;
240275
- if (typeof candidate.sizeBytes === "number" && candidate.sizeBytes > MAX_TELEGRAM_DOWNLOAD_BYTES) {
240276
- console.warn(`[Telegram] Skipping attachment ${candidate.name ?? candidate.fileId}: ${candidate.sizeBytes} bytes exceeds Telegram download limit (${MAX_TELEGRAM_DOWNLOAD_BYTES} bytes).`);
240277
- return null;
240278
- }
240279
- const file3 = await params.bot.api.getFile(candidate.fileId);
240280
- const remotePath = file3.file_path;
240281
- if (!remotePath) {
240282
- console.warn(`[Telegram] getFile returned no file_path for attachment ${candidate.name ?? candidate.fileId}.`);
240283
- return null;
240284
- }
240285
- const response = await fetchTelegramFile(buildTelegramFileUrl(params.token, remotePath), TELEGRAM_DOWNLOAD_TIMEOUT_MS);
240286
- if (!response.ok) {
240287
- console.warn(`[Telegram] Failed to download attachment ${candidate.name ?? candidate.fileId} from ${remotePath}: ${response.status} ${response.statusText}`);
240288
- return null;
240289
- }
240290
- const contentLength = response.headers.get("content-length");
240291
- if (contentLength) {
240292
- const parsedLength = Number(contentLength);
240293
- if (Number.isFinite(parsedLength) && parsedLength > MAX_TELEGRAM_DOWNLOAD_BYTES) {
240294
- console.warn(`[Telegram] Refusing attachment ${candidate.name ?? candidate.fileId}: content-length ${parsedLength} exceeds limit ${MAX_TELEGRAM_DOWNLOAD_BYTES}.`);
240295
- return null;
240296
- }
240297
- }
240298
- const buffer = Buffer.from(await response.arrayBuffer());
240299
- if (buffer.byteLength > MAX_TELEGRAM_DOWNLOAD_BYTES) {
240300
- console.warn(`[Telegram] Refusing attachment ${candidate.name ?? candidate.fileId}: downloaded size ${buffer.byteLength} exceeds limit ${MAX_TELEGRAM_DOWNLOAD_BYTES}.`);
240301
- return null;
240302
- }
240303
- const responseMimeType = normalizeTelegramMimeType(response.headers.get("content-type") ?? undefined);
240304
- const fileName = inferAttachmentFileName({
240305
- candidate,
240306
- remotePath,
240307
- responseMimeType
240308
- });
240309
- const candidateMimeType = normalizeTelegramMimeType(candidate.mimeType);
240310
- const mimeType = (isGenericTelegramMimeType(responseMimeType) ? candidateMimeType : responseMimeType) ?? inferMimeTypeFromName(fileName);
240311
- const kind = inferAttachmentKind({
240312
- mimeType,
240313
- fileName,
240314
- fallback: candidate.kind
240315
- });
240316
- const localPath = await saveTelegramAttachment({
240317
- accountId: params.accountId,
240318
- fileName,
240319
- buffer
240320
- });
240321
- const attachment = {
240322
- id: candidate.fileId,
240323
- name: fileName,
240324
- mimeType,
240325
- sizeBytes: buffer.byteLength,
240326
- kind,
240327
- localPath,
240328
- ...kind === "image" && canInlineTelegramImage(mimeType) && buffer.byteLength <= MAX_TELEGRAM_INLINE_IMAGE_BYTES ? { imageDataBase64: buffer.toString("base64") } : {}
240329
- };
240330
- if (candidate.isVoice && params.transcribeVoice) {
240331
- const { isTranscriptionConfigured: isTranscriptionConfigured2, transcribeAudioFile: transcribeAudioFile2 } = await Promise.resolve().then(() => (init_transcription(), exports_transcription));
240332
- if (isTranscriptionConfigured2()) {
240333
- const result2 = await transcribeAudioFile2(localPath);
240334
- if (result2.success && result2.text) {
240335
- attachment.transcription = result2.text;
240336
- } else if (result2.error) {
240337
- attachment.transcriptionError = result2.error;
240338
- console.warn(`[Telegram] Voice transcription failed for ${fileName}:`, result2.error);
240339
- }
240340
- } else {
240341
- attachment.transcriptionError = "OPENAI_API_KEY not set; transcription skipped.";
240342
- }
240343
- }
240344
- return attachment;
240345
- }
240346
- async function resolveTelegramInboundAttachments(params) {
240347
- const deduped = new Map;
240348
- for (const message of params.messages) {
240349
- for (const candidate of collectTelegramAttachmentCandidates(message)) {
240350
- deduped.set(candidate.fileId, candidate);
240351
- }
240352
- }
240353
- if (deduped.size === 0) {
240354
- return [];
240355
- }
240356
- const resolved = await Promise.all(Array.from(deduped.values()).map((candidate) => downloadTelegramAttachment({
240357
- accountId: params.accountId,
240358
- token: params.token,
240359
- bot: params.bot,
240360
- candidate,
240361
- transcribeVoice: params.transcribeVoice
240362
- }).catch((error54) => {
240363
- const message = error54 instanceof Error ? error54.message : String(error54);
240364
- console.warn(`[Telegram] Attachment download failed for ${candidate.name ?? candidate.fileId}: ${message}`);
240365
- return null;
240366
- })));
240367
- return resolved.filter((attachment) => Boolean(attachment));
240368
- }
240369
- var TELEGRAM_MEDIA_GROUP_FLUSH_MS = 150, TELEGRAM_DOWNLOAD_TIMEOUT_MS = 15000, MAX_TELEGRAM_DOWNLOAD_BYTES, MAX_TELEGRAM_INLINE_IMAGE_BYTES, IMAGE_EXTENSIONS3, ANIMATION_EXTENSIONS, VIDEO_EXTENSIONS, AUDIO_EXTENSIONS, VOICE_EXTENSIONS, STATIC_STICKER_EXTENSIONS;
240370
- var init_media = __esm(() => {
240371
- init_config2();
240372
- MAX_TELEGRAM_DOWNLOAD_BYTES = 50 * 1024 * 1024;
240373
- MAX_TELEGRAM_INLINE_IMAGE_BYTES = 5 * 1024 * 1024;
240374
- IMAGE_EXTENSIONS3 = new Set([".png", ".jpg", ".jpeg"]);
240375
- ANIMATION_EXTENSIONS = new Set([".gif"]);
240376
- VIDEO_EXTENSIONS = new Set([".mp4", ".m4v", ".mov", ".webm"]);
240377
- AUDIO_EXTENSIONS = new Set([".mp3", ".m4a", ".wav"]);
240378
- VOICE_EXTENSIONS = new Set([".ogg", ".oga", ".opus"]);
240379
- STATIC_STICKER_EXTENSIONS = new Set([".webp"]);
240380
- });
240381
239593
 
240382
- // src/channels/telegram/utils.ts
239594
+ // src/channels/telegram/ingress.ts
240383
239595
  function escapeRegExp3(value) {
240384
239596
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
240385
239597
  }
@@ -240418,20 +239630,98 @@ function detectTelegramBotMention(message, botUsername, botDisplayName, text2 =
240418
239630
  text: leadingNameRegex ? stripped.replace(leadingNameRegex, "").trimStart() : stripped.trimStart()
240419
239631
  };
240420
239632
  }
240421
- function resolveTelegramBotConstructor(mod) {
240422
- const Bot = mod.Bot ?? mod.default?.Bot;
240423
- if (!Bot) {
240424
- throw new Error('Installed Telegram runtime did not export "Bot".');
239633
+ function getTelegramChatType(chat2) {
239634
+ return !chat2.type || chat2.type === "private" ? "direct" : "channel";
239635
+ }
239636
+ function getTelegramChatLabel(message) {
239637
+ const title = message.chat.title?.trim();
239638
+ if (title) {
239639
+ return title;
240425
239640
  }
240426
- return Bot;
239641
+ const username = message.chat.username?.trim();
239642
+ if (username) {
239643
+ return username.startsWith("@") ? username : `@${username}`;
239644
+ }
239645
+ return;
240427
239646
  }
240428
- function resolveTelegramInputFileConstructor(mod) {
240429
- const InputFile = mod.InputFile ?? mod.default?.InputFile;
240430
- if (!InputFile) {
240431
- throw new Error('Installed Telegram runtime did not export "InputFile".');
239647
+ function getTelegramMessageThreadId(message) {
239648
+ return message.message_thread_id !== undefined ? String(message.message_thread_id) : null;
239649
+ }
239650
+ function getTelegramReplyContext(message) {
239651
+ const replied = message.reply_to_message;
239652
+ if (!replied) {
239653
+ return;
240432
239654
  }
240433
- return InputFile;
239655
+ const text2 = extractTelegramMessageText(replied).trim();
239656
+ const context3 = {
239657
+ messageId: String(replied.message_id)
239658
+ };
239659
+ if (replied.from?.id !== undefined) {
239660
+ context3.senderId = String(replied.from.id);
239661
+ }
239662
+ const senderName = getTelegramSenderName(replied);
239663
+ if (senderName) {
239664
+ context3.senderName = senderName;
239665
+ }
239666
+ if (text2) {
239667
+ context3.text = text2;
239668
+ }
239669
+ return context3;
239670
+ }
239671
+ function getTelegramReactionToken(reaction) {
239672
+ switch (reaction.type) {
239673
+ case "emoji":
239674
+ return reaction.emoji?.trim() || null;
239675
+ case "custom_emoji":
239676
+ return reaction.custom_emoji_id?.trim() ? `custom_emoji:${reaction.custom_emoji_id.trim()}` : null;
239677
+ case "paid":
239678
+ return "paid";
239679
+ default:
239680
+ return null;
239681
+ }
239682
+ }
239683
+ function getTelegramReactionSenderName(update3) {
239684
+ if (update3.user) {
239685
+ return getTelegramSenderName({
239686
+ from: update3.user
239687
+ });
239688
+ }
239689
+ if (update3.actor_chat?.username?.trim()) {
239690
+ return update3.actor_chat.username.trim();
239691
+ }
239692
+ if (update3.actor_chat?.title?.trim()) {
239693
+ return update3.actor_chat.title.trim();
239694
+ }
239695
+ return;
240434
239696
  }
239697
+ function getTelegramReactionSenderId(update3) {
239698
+ if (update3.user?.id !== undefined) {
239699
+ return String(update3.user.id);
239700
+ }
239701
+ if (update3.actor_chat?.id !== undefined) {
239702
+ return String(update3.actor_chat.id);
239703
+ }
239704
+ return null;
239705
+ }
239706
+ function diffTelegramReactionUpdate(update3) {
239707
+ const oldTokens = new Set(update3.old_reaction.map((reaction) => getTelegramReactionToken(reaction)).filter((value) => typeof value === "string"));
239708
+ const newTokens = new Set(update3.new_reaction.map((reaction) => getTelegramReactionToken(reaction)).filter((value) => typeof value === "string"));
239709
+ const events = [];
239710
+ for (const emoji3 of oldTokens) {
239711
+ if (!newTokens.has(emoji3)) {
239712
+ events.push({ action: "removed", emoji: emoji3 });
239713
+ }
239714
+ }
239715
+ for (const emoji3 of newTokens) {
239716
+ if (!oldTokens.has(emoji3)) {
239717
+ events.push({ action: "added", emoji: emoji3 });
239718
+ }
239719
+ }
239720
+ return events;
239721
+ }
239722
+ var init_ingress = () => {};
239723
+
239724
+ // src/channels/telegram/outbound.ts
240435
239725
  function resolveTelegramOutboundThreadId(msg) {
240436
239726
  return msg.threadId?.trim() || null;
240437
239727
  }
@@ -240545,18 +239835,6 @@ function shouldFallbackTelegramRichMessage(error54) {
240545
239835
  }
240546
239836
  return mentionsRichMessage && text2.includes("bad request");
240547
239837
  }
240548
- function getTelegramReactionToken(reaction) {
240549
- switch (reaction.type) {
240550
- case "emoji":
240551
- return reaction.emoji?.trim() || null;
240552
- case "custom_emoji":
240553
- return reaction.custom_emoji_id?.trim() ? `custom_emoji:${reaction.custom_emoji_id.trim()}` : null;
240554
- case "paid":
240555
- return "paid";
240556
- default:
240557
- return null;
240558
- }
240559
- }
240560
239838
  function parseTelegramReactionInput(reaction) {
240561
239839
  const trimmed = reaction.trim();
240562
239840
  if (!trimmed) {
@@ -240578,66 +239856,21 @@ function parseTelegramReactionInput(reaction) {
240578
239856
  emoji: trimmed
240579
239857
  };
240580
239858
  }
240581
- function getTelegramReactionSenderName(update3) {
240582
- if (update3.user) {
240583
- return getTelegramSenderName({
240584
- from: update3.user
240585
- });
240586
- }
240587
- if (update3.actor_chat?.username?.trim()) {
240588
- return update3.actor_chat.username.trim();
240589
- }
240590
- if (update3.actor_chat?.title?.trim()) {
240591
- return update3.actor_chat.title.trim();
240592
- }
240593
- return;
240594
- }
240595
- function getTelegramReactionSenderId(update3) {
240596
- if (update3.user?.id !== undefined) {
240597
- return String(update3.user.id);
240598
- }
240599
- if (update3.actor_chat?.id !== undefined) {
240600
- return String(update3.actor_chat.id);
240601
- }
240602
- return null;
240603
- }
240604
- function getTelegramChatType(chat2) {
240605
- return !chat2.type || chat2.type === "private" ? "direct" : "channel";
240606
- }
240607
- function getTelegramChatLabel(message) {
240608
- const title = message.chat.title?.trim();
240609
- if (title) {
240610
- return title;
240611
- }
240612
- const username = message.chat.username?.trim();
240613
- if (username) {
240614
- return username.startsWith("@") ? username : `@${username}`;
239859
+
239860
+ // src/channels/telegram/utils.ts
239861
+ function resolveTelegramBotConstructor(mod) {
239862
+ const Bot = mod.Bot ?? mod.default?.Bot;
239863
+ if (!Bot) {
239864
+ throw new Error('Installed Telegram runtime did not export "Bot".');
240615
239865
  }
240616
- return;
240617
- }
240618
- function getTelegramMessageThreadId(message) {
240619
- return message.message_thread_id !== undefined ? String(message.message_thread_id) : null;
239866
+ return Bot;
240620
239867
  }
240621
- function getTelegramReplyContext(message) {
240622
- const replied = message.reply_to_message;
240623
- if (!replied) {
240624
- return;
240625
- }
240626
- const text2 = extractTelegramMessageText(replied).trim();
240627
- const context3 = {
240628
- messageId: String(replied.message_id)
240629
- };
240630
- if (replied.from?.id !== undefined) {
240631
- context3.senderId = String(replied.from.id);
240632
- }
240633
- const senderName = getTelegramSenderName(replied);
240634
- if (senderName) {
240635
- context3.senderName = senderName;
240636
- }
240637
- if (text2) {
240638
- context3.text = text2;
239868
+ function resolveTelegramInputFileConstructor(mod) {
239869
+ const InputFile = mod.InputFile ?? mod.default?.InputFile;
239870
+ if (!InputFile) {
239871
+ throw new Error('Installed Telegram runtime did not export "InputFile".');
240639
239872
  }
240640
- return context3;
239873
+ return InputFile;
240641
239874
  }
240642
239875
  function getTelegramLifecycleErrorReplyKey(source2) {
240643
239876
  if (source2.channel !== "telegram" || !source2.chatId) {
@@ -240658,7 +239891,7 @@ function formatTelegramLifecycleErrorMessage(errorText, runId) {
240658
239891
  var TELEGRAM_LIFECYCLE_ERROR_TEXT_MAX = 3500, TELEGRAM_LIFECYCLE_ERROR_DEDUPE_TTL_MS, TELEGRAM_LIFECYCLE_ERROR_DEDUPE_MAX = 1000, TELEGRAM_LIFECYCLE_ERROR_REPORT_TTL_MS, TELEGRAM_LIFECYCLE_ERROR_REPORT_MAX = 1000, TELEGRAM_REPORT_CALLBACK_PREFIX = "lc_report:", TELEGRAM_TYPING_REFRESH_MS = 4000, TELEGRAM_TYPING_MAX_MS;
240659
239892
  var init_utils5 = __esm(() => {
240660
239893
  init_lifecycle_error();
240661
- init_media();
239894
+ init_ingress();
240662
239895
  TELEGRAM_LIFECYCLE_ERROR_DEDUPE_TTL_MS = 6 * 60 * 60 * 1000;
240663
239896
  TELEGRAM_LIFECYCLE_ERROR_REPORT_TTL_MS = 6 * 60 * 60 * 1000;
240664
239897
  TELEGRAM_TYPING_MAX_MS = 6 * 60 * 60 * 1000;
@@ -241271,7 +240504,7 @@ var init_lifecycle_error_report = __esm(() => {
241271
240504
 
241272
240505
  // src/channels/telegram/debounce.ts
241273
240506
  function resolveTelegramInboundDebounceMs(config3) {
241274
- const raw2 = process.env.LETTA_TELEGRAM_INBOUND_DEBOUNCE_MS;
240507
+ const raw2 = typeof process === "undefined" ? undefined : process.env.LETTA_TELEGRAM_INBOUND_DEBOUNCE_MS;
241275
240508
  if (typeof raw2 === "string" && raw2.trim() !== "") {
241276
240509
  const envOverride = Number(raw2);
241277
240510
  if (Number.isFinite(envOverride) && envOverride >= 0) {
@@ -241293,6 +240526,539 @@ function buildTelegramDebounceKey(input, accountId) {
241293
240526
  var TELEGRAM_DEBOUNCE_DEFAULT_MS = 0, TELEGRAM_DEBOUNCE_MAX_MS = 1e4;
241294
240527
  var init_debounce3 = () => {};
241295
240528
 
240529
+ // src/channels/transcription/index.ts
240530
+ var exports_transcription = {};
240531
+ __export(exports_transcription, {
240532
+ transcribeAudioFile: () => transcribeAudioFile,
240533
+ isTranscriptionConfigured: () => isTranscriptionConfigured
240534
+ });
240535
+ import { execFileSync as execFileSync4 } from "node:child_process";
240536
+ import { mkdtempSync as mkdtempSync3, readFileSync as readFileSync22, rmSync as rmSync8 } from "node:fs";
240537
+ import { tmpdir as tmpdir8 } from "node:os";
240538
+ import { basename as basename12, extname as extname5, join as join41 } from "node:path";
240539
+ function audioMimeTypeForPath(localPath) {
240540
+ switch (extname5(localPath).toLowerCase()) {
240541
+ case ".flac":
240542
+ return "audio/flac";
240543
+ case ".m4a":
240544
+ return "audio/mp4";
240545
+ case ".mp3":
240546
+ case ".mpeg":
240547
+ case ".mpga":
240548
+ return "audio/mpeg";
240549
+ case ".mp4":
240550
+ return "video/mp4";
240551
+ case ".oga":
240552
+ case ".ogg":
240553
+ return "audio/ogg";
240554
+ case ".wav":
240555
+ return "audio/wav";
240556
+ case ".webm":
240557
+ return "audio/webm";
240558
+ default:
240559
+ return "audio/mp4";
240560
+ }
240561
+ }
240562
+ function prepareOpenAiTranscriptionFile(localPath) {
240563
+ if (OPENAI_SUPPORTED_AUDIO_EXTENSIONS.has(extname5(localPath).toLowerCase())) {
240564
+ return { localPath };
240565
+ }
240566
+ const tempDir = mkdtempSync3(join41(tmpdir8(), "letta-transcription-"));
240567
+ const convertedPath = join41(tempDir, `${basename12(localPath)}.m4a`);
240568
+ try {
240569
+ execFileSync4("ffmpeg", [
240570
+ "-y",
240571
+ "-loglevel",
240572
+ "error",
240573
+ "-i",
240574
+ localPath,
240575
+ "-vn",
240576
+ "-c:a",
240577
+ "aac",
240578
+ convertedPath
240579
+ ], { stdio: "ignore", timeout: TRANSCRIPTION_TIMEOUT_MS });
240580
+ } catch (error54) {
240581
+ rmSync8(tempDir, { recursive: true, force: true });
240582
+ throw new Error(`Unsupported audio format ${extname5(localPath).replace(/^\./, "") || "unknown"}; ffmpeg conversion failed. ffmpeg is required to transcribe this audio format; install ffmpeg on the channel listener machine. ${error54 instanceof Error ? error54.message : String(error54)}`);
240583
+ }
240584
+ return {
240585
+ localPath: convertedPath,
240586
+ cleanup: () => rmSync8(tempDir, { recursive: true, force: true })
240587
+ };
240588
+ }
240589
+ function isTranscriptionConfigured() {
240590
+ return !!process.env.OPENAI_API_KEY;
240591
+ }
240592
+ async function transcribeAudioFile(localPath) {
240593
+ const apiKey = process.env.OPENAI_API_KEY;
240594
+ if (!apiKey) {
240595
+ return {
240596
+ success: false,
240597
+ error: "OPENAI_API_KEY not set; transcription skipped."
240598
+ };
240599
+ }
240600
+ try {
240601
+ const prepared = prepareOpenAiTranscriptionFile(localPath);
240602
+ try {
240603
+ const buffer = readFileSync22(prepared.localPath);
240604
+ const filename = basename12(prepared.localPath);
240605
+ const formData = new FormData;
240606
+ const blob = new Blob([buffer], {
240607
+ type: audioMimeTypeForPath(prepared.localPath)
240608
+ });
240609
+ formData.append("file", blob, filename);
240610
+ formData.append("model", OPENAI_TRANSCRIPTION_MODEL);
240611
+ const controller = new AbortController;
240612
+ const timeout = setTimeout(() => controller.abort(), TRANSCRIPTION_TIMEOUT_MS);
240613
+ try {
240614
+ const response = await fetch(OPENAI_TRANSCRIPTION_API_URL, {
240615
+ method: "POST",
240616
+ headers: { Authorization: `Bearer ${apiKey}` },
240617
+ body: formData,
240618
+ signal: controller.signal
240619
+ });
240620
+ if (!response.ok) {
240621
+ const errorText = await response.text();
240622
+ return {
240623
+ success: false,
240624
+ error: `OpenAI transcription API error (${response.status}): ${errorText}`
240625
+ };
240626
+ }
240627
+ const data = await response.json();
240628
+ return { success: true, text: data.text };
240629
+ } finally {
240630
+ clearTimeout(timeout);
240631
+ }
240632
+ } finally {
240633
+ prepared.cleanup?.();
240634
+ }
240635
+ } catch (error54) {
240636
+ return {
240637
+ success: false,
240638
+ error: error54 instanceof Error ? error54.message : String(error54)
240639
+ };
240640
+ }
240641
+ }
240642
+ var OPENAI_TRANSCRIPTION_API_URL = "https://api.openai.com/v1/audio/transcriptions", OPENAI_TRANSCRIPTION_MODEL = "gpt-4o-transcribe", TRANSCRIPTION_TIMEOUT_MS = 30000, OPENAI_SUPPORTED_AUDIO_EXTENSIONS;
240643
+ var init_transcription = __esm(() => {
240644
+ OPENAI_SUPPORTED_AUDIO_EXTENSIONS = new Set([
240645
+ ".flac",
240646
+ ".m4a",
240647
+ ".mp3",
240648
+ ".mp4",
240649
+ ".mpeg",
240650
+ ".mpga",
240651
+ ".oga",
240652
+ ".ogg",
240653
+ ".wav",
240654
+ ".webm"
240655
+ ]);
240656
+ });
240657
+
240658
+ // src/channels/telegram/media.ts
240659
+ import { randomUUID as randomUUID12 } from "node:crypto";
240660
+ import { mkdir as mkdir8, writeFile as writeFile10 } from "node:fs/promises";
240661
+ import { basename as basename13, extname as extname6, join as join42 } from "node:path";
240662
+ function normalizeTelegramMimeType(mimeType) {
240663
+ const normalized = mimeType?.split(";")[0]?.trim().toLowerCase();
240664
+ return normalized || undefined;
240665
+ }
240666
+ function sanitizeTelegramPathSegment(input) {
240667
+ const cleaned = input.replace(/[^A-Za-z0-9._-]/g, "_").replace(/^_+|_+$/g, "");
240668
+ return cleaned || "attachment";
240669
+ }
240670
+ function coerceSizeBytes(value) {
240671
+ if (typeof value === "number" && Number.isFinite(value)) {
240672
+ return value;
240673
+ }
240674
+ return;
240675
+ }
240676
+ function isImageMimeType(mimeType) {
240677
+ return normalizeTelegramMimeType(mimeType)?.startsWith("image/") ?? false;
240678
+ }
240679
+ function canInlineTelegramImage(mimeType) {
240680
+ const normalized = normalizeTelegramMimeType(mimeType);
240681
+ return !!normalized && normalized.startsWith("image/") && normalized !== "image/svg+xml";
240682
+ }
240683
+ function isAudioMimeType(mimeType) {
240684
+ return normalizeTelegramMimeType(mimeType)?.startsWith("audio/") ?? false;
240685
+ }
240686
+ function isVideoMimeType(mimeType) {
240687
+ return normalizeTelegramMimeType(mimeType)?.startsWith("video/") ?? false;
240688
+ }
240689
+ function isGenericTelegramMimeType(mimeType) {
240690
+ const normalized = normalizeTelegramMimeType(mimeType);
240691
+ return !normalized || normalized === "application/octet-stream";
240692
+ }
240693
+ function inferAttachmentKind(params) {
240694
+ if (isImageMimeType(params.mimeType)) {
240695
+ return "image";
240696
+ }
240697
+ if (isAudioMimeType(params.mimeType)) {
240698
+ return "audio";
240699
+ }
240700
+ if (isVideoMimeType(params.mimeType)) {
240701
+ return "video";
240702
+ }
240703
+ const lowerName = params.fileName?.toLowerCase();
240704
+ if (lowerName) {
240705
+ const extension = extname6(lowerName);
240706
+ if (IMAGE_EXTENSIONS3.has(extension) || STATIC_STICKER_EXTENSIONS.has(extension)) {
240707
+ return "image";
240708
+ }
240709
+ if (AUDIO_EXTENSIONS.has(extension) || VOICE_EXTENSIONS.has(extension)) {
240710
+ return "audio";
240711
+ }
240712
+ if (VIDEO_EXTENSIONS.has(extension) || ANIMATION_EXTENSIONS.has(extension)) {
240713
+ return "video";
240714
+ }
240715
+ }
240716
+ return params.fallback;
240717
+ }
240718
+ function collectTelegramAttachmentCandidates(message) {
240719
+ const attachments = [];
240720
+ if (Array.isArray(message.photo) && message.photo.length > 0) {
240721
+ const photo = message.photo[message.photo.length - 1];
240722
+ if (photo?.file_id) {
240723
+ attachments.push({
240724
+ fileId: photo.file_id,
240725
+ kind: "image",
240726
+ name: `photo-${photo.file_unique_id ?? photo.file_id}.jpg`,
240727
+ mimeType: "image/jpeg",
240728
+ sizeBytes: coerceSizeBytes(photo.file_size)
240729
+ });
240730
+ }
240731
+ }
240732
+ if (message.document?.file_id) {
240733
+ attachments.push({
240734
+ fileId: message.document.file_id,
240735
+ kind: inferAttachmentKind({
240736
+ mimeType: message.document.mime_type,
240737
+ fileName: message.document.file_name,
240738
+ fallback: "file"
240739
+ }),
240740
+ name: message.document.file_name,
240741
+ mimeType: message.document.mime_type,
240742
+ sizeBytes: coerceSizeBytes(message.document.file_size)
240743
+ });
240744
+ }
240745
+ if (message.video?.file_id) {
240746
+ attachments.push({
240747
+ fileId: message.video.file_id,
240748
+ kind: "video",
240749
+ name: message.video.file_name ?? `video-${message.video.file_unique_id ?? message.video.file_id}.mp4`,
240750
+ mimeType: message.video.mime_type,
240751
+ sizeBytes: coerceSizeBytes(message.video.file_size)
240752
+ });
240753
+ }
240754
+ if (message.audio?.file_id) {
240755
+ attachments.push({
240756
+ fileId: message.audio.file_id,
240757
+ kind: "audio",
240758
+ name: message.audio.file_name ?? `audio-${message.audio.file_unique_id ?? message.audio.file_id}.mp3`,
240759
+ mimeType: message.audio.mime_type,
240760
+ sizeBytes: coerceSizeBytes(message.audio.file_size)
240761
+ });
240762
+ }
240763
+ if (message.voice?.file_id) {
240764
+ attachments.push({
240765
+ fileId: message.voice.file_id,
240766
+ kind: "audio",
240767
+ name: `voice-${message.voice.file_unique_id ?? message.voice.file_id}.ogg`,
240768
+ mimeType: message.voice.mime_type,
240769
+ sizeBytes: coerceSizeBytes(message.voice.file_size),
240770
+ isVoice: true
240771
+ });
240772
+ }
240773
+ if (message.animation?.file_id) {
240774
+ attachments.push({
240775
+ fileId: message.animation.file_id,
240776
+ kind: "video",
240777
+ name: message.animation.file_name ?? `animation-${message.animation.file_unique_id ?? message.animation.file_id}.gif`,
240778
+ mimeType: message.animation.mime_type,
240779
+ sizeBytes: coerceSizeBytes(message.animation.file_size)
240780
+ });
240781
+ }
240782
+ if (message.sticker?.file_id && !message.sticker.is_animated && !message.sticker.is_video) {
240783
+ attachments.push({
240784
+ fileId: message.sticker.file_id,
240785
+ kind: "image",
240786
+ name: `sticker-${message.sticker.file_unique_id ?? message.sticker.file_id}.webp`,
240787
+ mimeType: message.sticker.mime_type ?? "image/webp",
240788
+ sizeBytes: coerceSizeBytes(message.sticker.file_size)
240789
+ });
240790
+ }
240791
+ return attachments;
240792
+ }
240793
+ function inferUploadMethodFromMimeType(mimeType) {
240794
+ const normalized = normalizeTelegramMimeType(mimeType);
240795
+ if (!normalized) {
240796
+ return null;
240797
+ }
240798
+ if (["image/png", "image/jpeg"].includes(normalized)) {
240799
+ return "photo";
240800
+ }
240801
+ if (normalized === "image/gif") {
240802
+ return "animation";
240803
+ }
240804
+ if (normalized === "image/webp") {
240805
+ return "document";
240806
+ }
240807
+ if (normalized.startsWith("video/")) {
240808
+ return "video";
240809
+ }
240810
+ if (["audio/ogg", "audio/opus"].includes(normalized)) {
240811
+ return "voice";
240812
+ }
240813
+ if (normalized.startsWith("audio/")) {
240814
+ return "audio";
240815
+ }
240816
+ return null;
240817
+ }
240818
+ function detectTelegramUploadMethod(filePath, fileName) {
240819
+ const inferredName = (fileName ?? basename13(filePath)).toLowerCase();
240820
+ const extension = extname6(inferredName);
240821
+ const mimeType = inferMimeTypeFromName(inferredName);
240822
+ const byMimeType = inferUploadMethodFromMimeType(mimeType);
240823
+ if (byMimeType) {
240824
+ return byMimeType;
240825
+ }
240826
+ if (ANIMATION_EXTENSIONS.has(extension)) {
240827
+ return "animation";
240828
+ }
240829
+ if (VIDEO_EXTENSIONS.has(extension)) {
240830
+ return "video";
240831
+ }
240832
+ if (VOICE_EXTENSIONS.has(extension)) {
240833
+ return "voice";
240834
+ }
240835
+ if (AUDIO_EXTENSIONS.has(extension)) {
240836
+ return "audio";
240837
+ }
240838
+ return "document";
240839
+ }
240840
+ function inferMimeTypeFromName(name) {
240841
+ const normalized = name.toLowerCase();
240842
+ const extension = extname6(normalized);
240843
+ if (IMAGE_EXTENSIONS3.has(extension)) {
240844
+ return extension === ".png" ? "image/png" : "image/jpeg";
240845
+ }
240846
+ if (ANIMATION_EXTENSIONS.has(extension)) {
240847
+ return "image/gif";
240848
+ }
240849
+ if (STATIC_STICKER_EXTENSIONS.has(extension)) {
240850
+ return "image/webp";
240851
+ }
240852
+ if (extension === ".mp4" || extension === ".m4v") {
240853
+ return "video/mp4";
240854
+ }
240855
+ if (extension === ".mov") {
240856
+ return "video/quicktime";
240857
+ }
240858
+ if (extension === ".webm") {
240859
+ return "video/webm";
240860
+ }
240861
+ if (extension === ".mp3") {
240862
+ return "audio/mpeg";
240863
+ }
240864
+ if (extension === ".m4a") {
240865
+ return "audio/mp4";
240866
+ }
240867
+ if (extension === ".wav") {
240868
+ return "audio/wav";
240869
+ }
240870
+ if (VOICE_EXTENSIONS.has(extension)) {
240871
+ return "audio/ogg";
240872
+ }
240873
+ if (extension === ".pdf") {
240874
+ return "application/pdf";
240875
+ }
240876
+ if (extension === ".txt") {
240877
+ return "text/plain";
240878
+ }
240879
+ if (extension === ".md") {
240880
+ return "text/markdown";
240881
+ }
240882
+ if (extension === ".json") {
240883
+ return "application/json";
240884
+ }
240885
+ return;
240886
+ }
240887
+ function extensionForMimeType(mimeType) {
240888
+ switch (normalizeTelegramMimeType(mimeType)) {
240889
+ case "image/png":
240890
+ return ".png";
240891
+ case "image/jpeg":
240892
+ return ".jpg";
240893
+ case "image/gif":
240894
+ return ".gif";
240895
+ case "image/webp":
240896
+ return ".webp";
240897
+ case "video/mp4":
240898
+ return ".mp4";
240899
+ case "video/quicktime":
240900
+ return ".mov";
240901
+ case "video/webm":
240902
+ return ".webm";
240903
+ case "audio/mpeg":
240904
+ return ".mp3";
240905
+ case "audio/mp4":
240906
+ return ".m4a";
240907
+ case "audio/wav":
240908
+ case "audio/x-wav":
240909
+ case "audio/wave":
240910
+ return ".wav";
240911
+ case "audio/ogg":
240912
+ case "audio/opus":
240913
+ return ".ogg";
240914
+ case "application/pdf":
240915
+ return ".pdf";
240916
+ case "text/plain":
240917
+ return ".txt";
240918
+ case "text/markdown":
240919
+ return ".md";
240920
+ case "application/json":
240921
+ return ".json";
240922
+ default:
240923
+ return "";
240924
+ }
240925
+ }
240926
+ function buildTelegramFileUrl(token2, filePath) {
240927
+ return `https://api.telegram.org/file/bot${token2}/${filePath}`;
240928
+ }
240929
+ function inferAttachmentFileName(params) {
240930
+ const hintedName = params.candidate.name?.trim() || basename13(params.remotePath) || "attachment";
240931
+ if (extname6(hintedName)) {
240932
+ return hintedName;
240933
+ }
240934
+ const extension = extensionForMimeType(params.responseMimeType) || extensionForMimeType(params.candidate.mimeType);
240935
+ return extension ? `${hintedName}${extension}` : hintedName;
240936
+ }
240937
+ async function saveTelegramAttachment(params) {
240938
+ const inboundDir = join42(getChannelDir("telegram"), "inbound", sanitizeTelegramPathSegment(params.accountId));
240939
+ await mkdir8(inboundDir, { recursive: true });
240940
+ const filePath = join42(inboundDir, `${Date.now()}-${randomUUID12()}-${sanitizeTelegramPathSegment(params.fileName)}`);
240941
+ await writeFile10(filePath, params.buffer);
240942
+ return filePath;
240943
+ }
240944
+ async function fetchTelegramFile(url2, timeoutMs) {
240945
+ const controller = new AbortController;
240946
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
240947
+ try {
240948
+ return await fetch(url2, { signal: controller.signal });
240949
+ } finally {
240950
+ clearTimeout(timeout);
240951
+ }
240952
+ }
240953
+ async function downloadTelegramAttachment(params) {
240954
+ const { candidate } = params;
240955
+ if (typeof candidate.sizeBytes === "number" && candidate.sizeBytes > MAX_TELEGRAM_DOWNLOAD_BYTES) {
240956
+ console.warn(`[Telegram] Skipping attachment ${candidate.name ?? candidate.fileId}: ${candidate.sizeBytes} bytes exceeds Telegram download limit (${MAX_TELEGRAM_DOWNLOAD_BYTES} bytes).`);
240957
+ return null;
240958
+ }
240959
+ const file3 = await params.bot.api.getFile(candidate.fileId);
240960
+ const remotePath = file3.file_path;
240961
+ if (!remotePath) {
240962
+ console.warn(`[Telegram] getFile returned no file_path for attachment ${candidate.name ?? candidate.fileId}.`);
240963
+ return null;
240964
+ }
240965
+ const response = await fetchTelegramFile(buildTelegramFileUrl(params.token, remotePath), TELEGRAM_DOWNLOAD_TIMEOUT_MS);
240966
+ if (!response.ok) {
240967
+ console.warn(`[Telegram] Failed to download attachment ${candidate.name ?? candidate.fileId} from ${remotePath}: ${response.status} ${response.statusText}`);
240968
+ return null;
240969
+ }
240970
+ const contentLength = response.headers.get("content-length");
240971
+ if (contentLength) {
240972
+ const parsedLength = Number(contentLength);
240973
+ if (Number.isFinite(parsedLength) && parsedLength > MAX_TELEGRAM_DOWNLOAD_BYTES) {
240974
+ console.warn(`[Telegram] Refusing attachment ${candidate.name ?? candidate.fileId}: content-length ${parsedLength} exceeds limit ${MAX_TELEGRAM_DOWNLOAD_BYTES}.`);
240975
+ return null;
240976
+ }
240977
+ }
240978
+ const buffer = Buffer.from(await response.arrayBuffer());
240979
+ if (buffer.byteLength > MAX_TELEGRAM_DOWNLOAD_BYTES) {
240980
+ console.warn(`[Telegram] Refusing attachment ${candidate.name ?? candidate.fileId}: downloaded size ${buffer.byteLength} exceeds limit ${MAX_TELEGRAM_DOWNLOAD_BYTES}.`);
240981
+ return null;
240982
+ }
240983
+ const responseMimeType = normalizeTelegramMimeType(response.headers.get("content-type") ?? undefined);
240984
+ const fileName = inferAttachmentFileName({
240985
+ candidate,
240986
+ remotePath,
240987
+ responseMimeType
240988
+ });
240989
+ const candidateMimeType = normalizeTelegramMimeType(candidate.mimeType);
240990
+ const mimeType = (isGenericTelegramMimeType(responseMimeType) ? candidateMimeType : responseMimeType) ?? inferMimeTypeFromName(fileName);
240991
+ const kind = inferAttachmentKind({
240992
+ mimeType,
240993
+ fileName,
240994
+ fallback: candidate.kind
240995
+ });
240996
+ const localPath = await saveTelegramAttachment({
240997
+ accountId: params.accountId,
240998
+ fileName,
240999
+ buffer
241000
+ });
241001
+ const attachment = {
241002
+ id: candidate.fileId,
241003
+ name: fileName,
241004
+ mimeType,
241005
+ sizeBytes: buffer.byteLength,
241006
+ kind,
241007
+ localPath,
241008
+ ...kind === "image" && canInlineTelegramImage(mimeType) && buffer.byteLength <= MAX_TELEGRAM_INLINE_IMAGE_BYTES ? { imageDataBase64: buffer.toString("base64") } : {}
241009
+ };
241010
+ if (candidate.isVoice && params.transcribeVoice) {
241011
+ const { isTranscriptionConfigured: isTranscriptionConfigured2, transcribeAudioFile: transcribeAudioFile2 } = await Promise.resolve().then(() => (init_transcription(), exports_transcription));
241012
+ if (isTranscriptionConfigured2()) {
241013
+ const result2 = await transcribeAudioFile2(localPath);
241014
+ if (result2.success && result2.text) {
241015
+ attachment.transcription = result2.text;
241016
+ } else if (result2.error) {
241017
+ attachment.transcriptionError = result2.error;
241018
+ console.warn(`[Telegram] Voice transcription failed for ${fileName}:`, result2.error);
241019
+ }
241020
+ } else {
241021
+ attachment.transcriptionError = "OPENAI_API_KEY not set; transcription skipped.";
241022
+ }
241023
+ }
241024
+ return attachment;
241025
+ }
241026
+ async function resolveTelegramInboundAttachments(params) {
241027
+ const deduped = new Map;
241028
+ for (const message of params.messages) {
241029
+ for (const candidate of collectTelegramAttachmentCandidates(message)) {
241030
+ deduped.set(candidate.fileId, candidate);
241031
+ }
241032
+ }
241033
+ if (deduped.size === 0) {
241034
+ return [];
241035
+ }
241036
+ const resolved = await Promise.all(Array.from(deduped.values()).map((candidate) => downloadTelegramAttachment({
241037
+ accountId: params.accountId,
241038
+ token: params.token,
241039
+ bot: params.bot,
241040
+ candidate,
241041
+ transcribeVoice: params.transcribeVoice
241042
+ }).catch((error54) => {
241043
+ const message = error54 instanceof Error ? error54.message : String(error54);
241044
+ console.warn(`[Telegram] Attachment download failed for ${candidate.name ?? candidate.fileId}: ${message}`);
241045
+ return null;
241046
+ })));
241047
+ return resolved.filter((attachment) => Boolean(attachment));
241048
+ }
241049
+ var TELEGRAM_MEDIA_GROUP_FLUSH_MS = 150, TELEGRAM_DOWNLOAD_TIMEOUT_MS = 15000, MAX_TELEGRAM_DOWNLOAD_BYTES, MAX_TELEGRAM_INLINE_IMAGE_BYTES, IMAGE_EXTENSIONS3, ANIMATION_EXTENSIONS, VIDEO_EXTENSIONS, AUDIO_EXTENSIONS, VOICE_EXTENSIONS, STATIC_STICKER_EXTENSIONS;
241050
+ var init_media = __esm(() => {
241051
+ init_config2();
241052
+ MAX_TELEGRAM_DOWNLOAD_BYTES = 50 * 1024 * 1024;
241053
+ MAX_TELEGRAM_INLINE_IMAGE_BYTES = 5 * 1024 * 1024;
241054
+ IMAGE_EXTENSIONS3 = new Set([".png", ".jpg", ".jpeg"]);
241055
+ ANIMATION_EXTENSIONS = new Set([".gif"]);
241056
+ VIDEO_EXTENSIONS = new Set([".mp4", ".m4v", ".mov", ".webm"]);
241057
+ AUDIO_EXTENSIONS = new Set([".mp3", ".m4a", ".wav"]);
241058
+ VOICE_EXTENSIONS = new Set([".ogg", ".oga", ".opus"]);
241059
+ STATIC_STICKER_EXTENSIONS = new Set([".webp"]);
241060
+ });
241061
+
241296
241062
  // src/channels/telegram/startup-helpers.ts
241297
241063
  function getStartupTimeoutMs(envName, fallbackMs) {
241298
241064
  const raw2 = process.env[envName];
@@ -241635,19 +241401,7 @@ function createTelegramAdapter(config3) {
241635
241401
  if (!senderId) {
241636
241402
  return;
241637
241403
  }
241638
- const oldTokens = new Set(update3.old_reaction.map((reaction) => getTelegramReactionToken(reaction)).filter((value) => typeof value === "string"));
241639
- const newTokens = new Set(update3.new_reaction.map((reaction) => getTelegramReactionToken(reaction)).filter((value) => typeof value === "string"));
241640
- const events = [];
241641
- for (const emoji3 of oldTokens) {
241642
- if (!newTokens.has(emoji3)) {
241643
- events.push({ action: "removed", emoji: emoji3 });
241644
- }
241645
- }
241646
- for (const emoji3 of newTokens) {
241647
- if (!oldTokens.has(emoji3)) {
241648
- events.push({ action: "added", emoji: emoji3 });
241649
- }
241650
- }
241404
+ const events = diffTelegramReactionUpdate(update3);
241651
241405
  for (const event2 of events) {
241652
241406
  try {
241653
241407
  await adapter.onMessage({
@@ -242014,29 +241768,19 @@ function createTelegramAdapter(config3) {
242014
241768
  var init_adapter2 = __esm(() => {
242015
241769
  init_lifecycle_error_report();
242016
241770
  init_debounce3();
241771
+ init_ingress();
242017
241772
  init_media();
242018
241773
  init_runtime2();
242019
241774
  init_typing_controller();
242020
241775
  init_utils5();
242021
241776
  });
242022
241777
 
242023
- // src/channels/telegram/message-actions.ts
242024
- function richPrivateChatDefaultEnabled(route) {
242025
- const accountId = route.accountId?.trim();
242026
- if (!accountId) {
242027
- return true;
242028
- }
242029
- const account = getChannelAccount("telegram", accountId);
242030
- if (!account || !isTelegramChannelAccount(account)) {
242031
- return true;
242032
- }
242033
- return account.richPrivateChatDefault !== false;
242034
- }
241778
+ // src/channels/telegram/message-action-contract.ts
242035
241779
  function shouldSendTelegramRichMessage(params) {
242036
241780
  if (params.request.action === "send-rich") {
242037
241781
  return true;
242038
241782
  }
242039
- return params.request.action === "send" && params.route.chatType === "direct" && richPrivateChatDefaultEnabled(params.route) && !params.request.mediaPath?.trim();
241783
+ return params.request.action === "send" && params.route.chatType === "direct" && params.richPrivateChatDefaultEnabled(params.route) && !params.request.mediaPath?.trim();
242040
241784
  }
242041
241785
  function resolveTelegramRouteThreadId(ctx) {
242042
241786
  const requestThreadId = ctx.request.threadId?.trim();
@@ -242052,11 +241796,9 @@ function resolveTelegramRouteThreadId(ctx) {
242052
241796
  }
242053
241797
  return ctx.route.chatId.trim().startsWith("-") ? routeThreadId : null;
242054
241798
  }
242055
- var telegramMessageActions;
242056
- var init_message_actions = __esm(() => {
242057
- init_accounts();
242058
- init_types8();
242059
- telegramMessageActions = {
241799
+ function createTelegramMessageActionAdapter(options3 = {}) {
241800
+ const richPrivateChatDefaultEnabled = options3.richPrivateChatDefaultEnabled ?? (() => true);
241801
+ return {
242060
241802
  describeMessageTool() {
242061
241803
  return {
242062
241804
  actions: ["send", "send-rich", "react", "upload-file"]
@@ -242103,7 +241845,11 @@ var init_message_actions = __esm(() => {
242103
241845
  return "Error: Telegram send requires message.";
242104
241846
  }
242105
241847
  const formatted = formatText(request.message ?? "");
242106
- const sendRichMessage = shouldSendTelegramRichMessage({ request, route });
241848
+ const sendRichMessage = shouldSendTelegramRichMessage({
241849
+ request,
241850
+ route,
241851
+ richPrivateChatDefaultEnabled
241852
+ });
242107
241853
  const result2 = await adapter.sendMessage({
242108
241854
  channel: "telegram",
242109
241855
  accountId: route.accountId,
@@ -242120,6 +241866,27 @@ var init_message_actions = __esm(() => {
242120
241866
  return request.mediaPath ? `Attachment sent to telegram (message_id: ${result2.messageId})` : `Message sent to telegram (message_id: ${result2.messageId})`;
242121
241867
  }
242122
241868
  };
241869
+ }
241870
+
241871
+ // src/channels/telegram/message-actions.ts
241872
+ function richPrivateChatDefaultEnabled(route) {
241873
+ const accountId = route.accountId?.trim();
241874
+ if (!accountId) {
241875
+ return true;
241876
+ }
241877
+ const account = getChannelAccount("telegram", accountId);
241878
+ if (!account || !isTelegramChannelAccount(account)) {
241879
+ return true;
241880
+ }
241881
+ return account.richPrivateChatDefault !== false;
241882
+ }
241883
+ var telegramMessageActions;
241884
+ var init_message_actions = __esm(() => {
241885
+ init_accounts();
241886
+ init_types8();
241887
+ telegramMessageActions = createTelegramMessageActionAdapter({
241888
+ richPrivateChatDefaultEnabled
241889
+ });
242123
241890
  });
242124
241891
 
242125
241892
  // src/channels/telegram/setup.ts
@@ -243770,126 +243537,7 @@ var init_approval_controller = __esm(() => {
243770
243537
  init_utils6();
243771
243538
  });
243772
243539
 
243773
- // src/channels/slack/attachment-stream.ts
243774
- import { randomUUID as randomUUID15 } from "node:crypto";
243775
- import { mkdir as mkdir9, open as open2, rename as rename2, rm as rm7 } from "node:fs/promises";
243776
- import { join as join43 } from "node:path";
243777
- function sanitizeFileName(name) {
243778
- const normalized = name.trim().replace(/[^\w.-]+/g, "_");
243779
- return normalized.length > 0 ? normalized : "attachment";
243780
- }
243781
- async function readWithIdleTimeout(reader, idleTimeoutMs, signal) {
243782
- if (signal?.aborted) {
243783
- throw new SlackAttachmentDownloadError("download_failed", "Slack attachment download was aborted.");
243784
- }
243785
- let timeoutHandle;
243786
- let onAbort;
243787
- try {
243788
- return await new Promise((resolve32, reject2) => {
243789
- timeoutHandle = setTimeout(() => {
243790
- reject2(new SlackAttachmentDownloadError("download_failed", `Slack attachment download stalled (no data received for ${idleTimeoutMs}ms).`));
243791
- }, idleTimeoutMs);
243792
- if (signal) {
243793
- onAbort = () => reject2(new SlackAttachmentDownloadError("download_failed", "Slack attachment download was aborted."));
243794
- signal.addEventListener("abort", onAbort, { once: true });
243795
- }
243796
- reader.read().then(resolve32, reject2);
243797
- });
243798
- } finally {
243799
- if (timeoutHandle !== undefined) {
243800
- clearTimeout(timeoutHandle);
243801
- }
243802
- if (signal && onAbort) {
243803
- signal.removeEventListener("abort", onAbort);
243804
- }
243805
- }
243806
- }
243807
- async function saveSlackAttachmentStream(params) {
243808
- const inboundDir = join43(getChannelDir("slack"), "inbound", sanitizeFileName(params.accountId));
243809
- await mkdir9(inboundDir, { recursive: true });
243810
- const filePath = join43(inboundDir, `${Date.now()}-${randomUUID15()}-${sanitizeFileName(params.fileName)}`);
243811
- const temporaryPath = `${filePath}.partial`;
243812
- const fileHandle = await open2(temporaryPath, "wx");
243813
- const reader = params.body.getReader();
243814
- const readIdleTimeoutMs = params.readIdleTimeoutMs ?? SLACK_ATTACHMENT_READ_IDLE_TIMEOUT_MS;
243815
- let sizeBytes = 0;
243816
- let completed = false;
243817
- try {
243818
- while (true) {
243819
- const { done, value } = await readWithIdleTimeout(reader, readIdleTimeoutMs, params.signal);
243820
- if (done) {
243821
- break;
243822
- }
243823
- if (!value?.byteLength) {
243824
- continue;
243825
- }
243826
- sizeBytes += value.byteLength;
243827
- if (params.maxBytes !== undefined && sizeBytes > params.maxBytes) {
243828
- throw new SlackAttachmentDownloadError("exceeds_auto_download_limit", `Slack attachment exceeds automatic download limit (${params.maxBytes} bytes).`);
243829
- }
243830
- let offset = 0;
243831
- while (offset < value.byteLength) {
243832
- const { bytesWritten } = await fileHandle.write(value, offset, value.byteLength - offset);
243833
- if (bytesWritten <= 0) {
243834
- throw new Error("Slack attachment write made no progress.");
243835
- }
243836
- offset += bytesWritten;
243837
- }
243838
- }
243839
- completed = true;
243840
- } finally {
243841
- if (!completed) {
243842
- await reader.cancel().catch(() => {
243843
- return;
243844
- });
243845
- }
243846
- try {
243847
- reader.releaseLock();
243848
- } catch {}
243849
- await fileHandle.close().catch(() => {
243850
- return;
243851
- });
243852
- if (!completed) {
243853
- await rm7(temporaryPath, { force: true }).catch(() => {
243854
- return;
243855
- });
243856
- }
243857
- }
243858
- try {
243859
- await rename2(temporaryPath, filePath);
243860
- } catch (error54) {
243861
- await rm7(temporaryPath, { force: true }).catch(() => {
243862
- return;
243863
- });
243864
- throw error54;
243865
- }
243866
- return { localPath: filePath, sizeBytes };
243867
- }
243868
- var SlackAttachmentDownloadError, SLACK_ATTACHMENT_READ_IDLE_TIMEOUT_MS = 60000;
243869
- var init_attachment_stream = __esm(() => {
243870
- init_config2();
243871
- SlackAttachmentDownloadError = class SlackAttachmentDownloadError extends Error {
243872
- reason;
243873
- constructor(reason, message) {
243874
- super(message);
243875
- this.reason = reason;
243876
- this.name = "SlackAttachmentDownloadError";
243877
- }
243878
- };
243879
- });
243880
-
243881
- // src/channels/slack/media.ts
243882
- import { basename as basename14, extname as extname7 } from "node:path";
243883
- async function mapSlackThreadMessage(message, attachmentOptions, sourceThreadId) {
243884
- const attachments = await resolveSlackMessageAttachments(message, attachmentOptions, sourceThreadId);
243885
- return {
243886
- text: resolveSlackThreadMessageText(message),
243887
- userId: isNonEmptyString6(message.user) ? message.user : undefined,
243888
- botId: isNonEmptyString6(message.bot_id) ? message.bot_id : undefined,
243889
- ts: isNonEmptyString6(message.ts) ? message.ts : undefined,
243890
- ...attachments.length > 0 ? { attachments } : {}
243891
- };
243892
- }
243540
+ // src/channels/slack/attachment-primitives.ts
243893
243541
  function asRecord4(value) {
243894
243542
  return value && typeof value === "object" ? value : null;
243895
243543
  }
@@ -243898,9 +243546,8 @@ function isNonEmptyString6(value) {
243898
243546
  }
243899
243547
  function normalizeSlackFileLike(value) {
243900
243548
  const record5 = asRecord4(value);
243901
- if (!record5) {
243549
+ if (!record5)
243902
243550
  return null;
243903
- }
243904
243551
  return {
243905
243552
  id: isNonEmptyString6(record5.id) ? record5.id : undefined,
243906
243553
  name: isNonEmptyString6(record5.name) ? record5.name : undefined,
@@ -243912,61 +243559,75 @@ function normalizeSlackFileLike(value) {
243912
243559
  }
243913
243560
  function normalizeSlackAttachmentLike(value) {
243914
243561
  const record5 = asRecord4(value);
243915
- if (!record5) {
243562
+ if (!record5)
243916
243563
  return null;
243917
- }
243918
243564
  const files2 = Array.isArray(record5.files) ? record5.files.map((entry) => normalizeSlackFileLike(entry)).filter((entry) => Boolean(entry)) : undefined;
243919
243565
  return {
243920
- text: isNonEmptyString6(record5.text) ? record5.text : undefined,
243921
- fallback: isNonEmptyString6(record5.fallback) ? record5.fallback : undefined,
243922
- pretext: isNonEmptyString6(record5.pretext) ? record5.pretext : undefined,
243923
- author_name: isNonEmptyString6(record5.author_name) ? record5.author_name : undefined,
243924
- title: isNonEmptyString6(record5.title) ? record5.title : undefined,
243925
243566
  image_url: isNonEmptyString6(record5.image_url) ? record5.image_url : undefined,
243926
243567
  files: files2
243927
243568
  };
243928
243569
  }
243929
- function uniqueNonEmptyStrings(values3) {
243930
- const seen = new Set;
243931
- const normalized = [];
243932
- for (const value of values3) {
243933
- const text2 = value?.trim();
243934
- if (!text2 || seen.has(text2)) {
243935
- continue;
243936
- }
243937
- seen.add(text2);
243938
- normalized.push(text2);
243570
+ function collectSlackFiles(rawEvent) {
243571
+ const record5 = asRecord4(rawEvent);
243572
+ if (!record5)
243573
+ return [];
243574
+ const deduped = new Map;
243575
+ const push = (file3) => {
243576
+ if (!file3)
243577
+ return;
243578
+ const key2 = file3.id ?? file3.url_private_download ?? file3.url_private ?? `${file3.name ?? "attachment"}:${file3.mimetype ?? ""}`;
243579
+ deduped.set(key2, file3);
243580
+ };
243581
+ if (Array.isArray(record5.files)) {
243582
+ for (const entry of record5.files)
243583
+ push(normalizeSlackFileLike(entry));
243939
243584
  }
243940
- return normalized;
243585
+ if (Array.isArray(record5.attachments)) {
243586
+ record5.attachments.map((entry) => normalizeSlackAttachmentLike(entry)).filter((entry) => Boolean(entry)).forEach((attachment, index) => {
243587
+ for (const file3 of attachment.files ?? [])
243588
+ push(file3);
243589
+ if (attachment.image_url) {
243590
+ push({
243591
+ id: `attachment-image-${index}`,
243592
+ name: `attachment-image-${index}.png`,
243593
+ url_private: attachment.image_url
243594
+ });
243595
+ }
243596
+ });
243597
+ }
243598
+ return Array.from(deduped.values()).slice(0, MAX_SLACK_ATTACHMENTS);
243941
243599
  }
243942
- function resolveSlackAttachmentText(attachment) {
243943
- const parts = uniqueNonEmptyStrings([
243944
- attachment.pretext,
243945
- attachment.author_name,
243946
- attachment.title,
243947
- attachment.text,
243948
- attachment.fallback
243949
- ]);
243950
- return parts.join(`
243951
- `);
243600
+ function nextCursor(page) {
243601
+ const value = page.response_metadata?.next_cursor;
243602
+ return isNonEmptyString6(value) ? value.trim() : undefined;
243952
243603
  }
243953
- function resolveSlackThreadMessageText(message) {
243954
- const text2 = typeof message.text === "string" ? message.text.trim() : "";
243955
- if (text2) {
243956
- return text2;
243957
- }
243958
- const attachmentTexts = Array.isArray(message.attachments) ? message.attachments.map((entry) => normalizeSlackAttachmentLike(entry)).filter((entry) => Boolean(entry)).map((attachment) => resolveSlackAttachmentText(attachment)).filter(isNonEmptyString6) : [];
243959
- if (attachmentTexts.length > 0) {
243960
- return attachmentTexts.join(`
243961
-
243962
- `);
243963
- }
243964
- const files2 = Array.isArray(message.files) ? message.files.map((entry) => normalizeSlackFileLike(entry)).filter((entry) => Boolean(entry)) : [];
243965
- if (files2.length === 0) {
243966
- return "";
243604
+ async function resolveSlackMessageFiles(params) {
243605
+ if (isNonEmptyString6(params.threadTs)) {
243606
+ let cursor;
243607
+ do {
243608
+ const page2 = await params.client.conversations.replies({
243609
+ channel: params.channelId,
243610
+ ts: params.threadTs,
243611
+ limit: 200,
243612
+ inclusive: true,
243613
+ ...cursor ? { cursor } : {}
243614
+ });
243615
+ const message2 = (page2.messages ?? []).find((entry) => entry.ts === params.messageTs);
243616
+ if (message2)
243617
+ return collectSlackFiles(message2);
243618
+ cursor = nextCursor(page2);
243619
+ } while (cursor);
243620
+ return null;
243967
243621
  }
243968
- const fileNames = files2.map((file3) => file3.name ?? "file").join(", ");
243969
- return `[attached: ${fileNames}]`;
243622
+ const page = await params.client.conversations.history({
243623
+ channel: params.channelId,
243624
+ oldest: params.messageTs,
243625
+ latest: params.messageTs,
243626
+ inclusive: true,
243627
+ limit: 1
243628
+ });
243629
+ const message = (page.messages ?? []).find((entry) => entry.ts === params.messageTs);
243630
+ return message ? collectSlackFiles(message) : null;
243970
243631
  }
243971
243632
  function isAllowedSlackHostname(hostname4) {
243972
243633
  const normalized = hostname4.trim().toLowerCase();
@@ -244015,11 +243676,22 @@ function extensionForMimeType2(mimeType) {
244015
243676
  return "";
244016
243677
  }
244017
243678
  }
243679
+ function pathBaseName(pathname) {
243680
+ const trimmed = pathname.replace(/\/+$/, "");
243681
+ const separator = trimmed.lastIndexOf("/");
243682
+ return trimmed.slice(separator + 1);
243683
+ }
243684
+ function extensionName(name) {
243685
+ const base3 = pathBaseName(name);
243686
+ if (base3 === "..")
243687
+ return "";
243688
+ const index = base3.lastIndexOf(".");
243689
+ return index > 0 ? base3.slice(index) : "";
243690
+ }
244018
243691
  function resolveMimeType(name, fallback) {
244019
- if (fallback) {
243692
+ if (fallback)
244020
243693
  return fallback;
244021
- }
244022
- switch (extname7(name).toLowerCase()) {
243694
+ switch (extensionName(name).toLowerCase()) {
244023
243695
  case ".png":
244024
243696
  return "image/png";
244025
243697
  case ".jpg":
@@ -244056,6 +243728,272 @@ function isGenericSlackMimeType(mimeType) {
244056
243728
  const normalized = mimeType?.trim().toLowerCase();
244057
243729
  return normalized === "application/octet-stream" || normalized === "binary/octet-stream";
244058
243730
  }
243731
+ function resolveSlackFileName(params) {
243732
+ const hintedName = params.file.name ?? (params.url ? pathBaseName(new URL(params.url).pathname) : undefined) ?? `${params.file.id ?? "attachment"}${extensionForMimeType2(params.file.mimetype)}`;
243733
+ return extensionName(hintedName) || !params.mimeType ? hintedName : `${hintedName}${extensionForMimeType2(params.mimeType)}`;
243734
+ }
243735
+ function resolveSlackFileMimeType(params) {
243736
+ const preferredMimeType = params.responseMimeType && !isGenericSlackMimeType(params.responseMimeType) ? params.responseMimeType : params.file.mimetype && !isGenericSlackMimeType(params.file.mimetype) ? params.file.mimetype : undefined;
243737
+ return resolveMimeType(params.fileName, preferredMimeType);
243738
+ }
243739
+ function resolveSlackFileMetadata(params) {
243740
+ const hintedName = resolveSlackFileName({
243741
+ file: params.file,
243742
+ url: params.url
243743
+ });
243744
+ const mimeType = resolveSlackFileMimeType({
243745
+ file: params.file,
243746
+ fileName: hintedName,
243747
+ responseMimeType: params.responseMimeType
243748
+ });
243749
+ return {
243750
+ fileName: resolveSlackFileName({
243751
+ file: params.file,
243752
+ url: params.url,
243753
+ mimeType
243754
+ }),
243755
+ mimeType
243756
+ };
243757
+ }
243758
+ function parseContentLength(response) {
243759
+ const header = response.headers.get("content-length");
243760
+ if (!header)
243761
+ return;
243762
+ const value = Number(header);
243763
+ return Number.isFinite(value) ? value : undefined;
243764
+ }
243765
+ async function fetchSlackFile(params) {
243766
+ const rawUrl = params.file.url_private_download ?? params.file.url_private;
243767
+ if (!rawUrl) {
243768
+ throw new Error("Slack attachment does not include a private download URL.");
243769
+ }
243770
+ const parsed = assertSlackFileUrl(rawUrl);
243771
+ const fetcher = params.fetcher ?? globalThis.fetch;
243772
+ const authHeaders = { Authorization: `Bearer ${params.token}` };
243773
+ const initial2 = await fetcher(parsed.href, {
243774
+ headers: authHeaders,
243775
+ redirect: "manual",
243776
+ ...params.signal ? { signal: params.signal } : {}
243777
+ });
243778
+ let response = initial2;
243779
+ if (initial2.status >= 300 && initial2.status < 400) {
243780
+ const location = initial2.headers.get("location");
243781
+ if (location) {
243782
+ const resolved = new URL(location, parsed.href);
243783
+ response = await fetcher(resolved.href, {
243784
+ ...resolved.origin === parsed.origin ? { headers: authHeaders } : {},
243785
+ redirect: "follow",
243786
+ ...params.signal ? { signal: params.signal } : {}
243787
+ });
243788
+ }
243789
+ }
243790
+ if (!response.ok) {
243791
+ throw new Error(`Slack attachment fetch failed with HTTP ${response.status}.`);
243792
+ }
243793
+ if (!response.body) {
243794
+ throw new Error("Slack attachment response did not include a body.");
243795
+ }
243796
+ const responseMimeType = response.headers.get("content-type")?.split(";")[0]?.trim() || undefined;
243797
+ const metadata = resolveSlackFileMetadata({
243798
+ file: params.file,
243799
+ url: rawUrl,
243800
+ responseMimeType
243801
+ });
243802
+ return {
243803
+ body: response.body,
243804
+ ...metadata,
243805
+ contentLength: parseContentLength(response)
243806
+ };
243807
+ }
243808
+ var MAX_SLACK_ATTACHMENTS = 8, ALLOWED_SLACK_HOST_SUFFIXES;
243809
+ var init_attachment_primitives = __esm(() => {
243810
+ ALLOWED_SLACK_HOST_SUFFIXES = [
243811
+ "slack.com",
243812
+ "slack-edge.com",
243813
+ "slack-files.com"
243814
+ ];
243815
+ });
243816
+
243817
+ // src/channels/slack/attachment-stream.ts
243818
+ import { randomUUID as randomUUID15 } from "node:crypto";
243819
+ import { mkdir as mkdir9, open as open2, rename as rename2, rm as rm7 } from "node:fs/promises";
243820
+ import { join as join43 } from "node:path";
243821
+ function sanitizeFileName(name) {
243822
+ const normalized = name.trim().replace(/[^\w.-]+/g, "_");
243823
+ return normalized.length > 0 ? normalized : "attachment";
243824
+ }
243825
+ async function readWithIdleTimeout(reader, idleTimeoutMs, signal) {
243826
+ if (signal?.aborted) {
243827
+ throw new SlackAttachmentDownloadError("download_failed", "Slack attachment download was aborted.");
243828
+ }
243829
+ let timeoutHandle;
243830
+ let onAbort;
243831
+ try {
243832
+ return await new Promise((resolve32, reject2) => {
243833
+ timeoutHandle = setTimeout(() => {
243834
+ reject2(new SlackAttachmentDownloadError("download_failed", `Slack attachment download stalled (no data received for ${idleTimeoutMs}ms).`));
243835
+ }, idleTimeoutMs);
243836
+ if (signal) {
243837
+ onAbort = () => reject2(new SlackAttachmentDownloadError("download_failed", "Slack attachment download was aborted."));
243838
+ signal.addEventListener("abort", onAbort, { once: true });
243839
+ }
243840
+ reader.read().then(resolve32, reject2);
243841
+ });
243842
+ } finally {
243843
+ if (timeoutHandle !== undefined) {
243844
+ clearTimeout(timeoutHandle);
243845
+ }
243846
+ if (signal && onAbort) {
243847
+ signal.removeEventListener("abort", onAbort);
243848
+ }
243849
+ }
243850
+ }
243851
+ async function saveSlackAttachmentStream(params) {
243852
+ const inboundDir = join43(getChannelDir("slack"), "inbound", sanitizeFileName(params.accountId));
243853
+ await mkdir9(inboundDir, { recursive: true });
243854
+ const filePath = join43(inboundDir, `${Date.now()}-${randomUUID15()}-${sanitizeFileName(params.fileName)}`);
243855
+ const temporaryPath = `${filePath}.partial`;
243856
+ const fileHandle = await open2(temporaryPath, "wx");
243857
+ const reader = params.body.getReader();
243858
+ const readIdleTimeoutMs = params.readIdleTimeoutMs ?? SLACK_ATTACHMENT_READ_IDLE_TIMEOUT_MS;
243859
+ let sizeBytes = 0;
243860
+ let completed = false;
243861
+ try {
243862
+ while (true) {
243863
+ const { done, value } = await readWithIdleTimeout(reader, readIdleTimeoutMs, params.signal);
243864
+ if (done) {
243865
+ break;
243866
+ }
243867
+ if (!value?.byteLength) {
243868
+ continue;
243869
+ }
243870
+ sizeBytes += value.byteLength;
243871
+ if (params.maxBytes !== undefined && sizeBytes > params.maxBytes) {
243872
+ throw new SlackAttachmentDownloadError("exceeds_auto_download_limit", `Slack attachment exceeds automatic download limit (${params.maxBytes} bytes).`);
243873
+ }
243874
+ let offset = 0;
243875
+ while (offset < value.byteLength) {
243876
+ const { bytesWritten } = await fileHandle.write(value, offset, value.byteLength - offset);
243877
+ if (bytesWritten <= 0) {
243878
+ throw new Error("Slack attachment write made no progress.");
243879
+ }
243880
+ offset += bytesWritten;
243881
+ }
243882
+ }
243883
+ completed = true;
243884
+ } finally {
243885
+ if (!completed) {
243886
+ await reader.cancel().catch(() => {
243887
+ return;
243888
+ });
243889
+ }
243890
+ try {
243891
+ reader.releaseLock();
243892
+ } catch {}
243893
+ await fileHandle.close().catch(() => {
243894
+ return;
243895
+ });
243896
+ if (!completed) {
243897
+ await rm7(temporaryPath, { force: true }).catch(() => {
243898
+ return;
243899
+ });
243900
+ }
243901
+ }
243902
+ try {
243903
+ await rename2(temporaryPath, filePath);
243904
+ } catch (error54) {
243905
+ await rm7(temporaryPath, { force: true }).catch(() => {
243906
+ return;
243907
+ });
243908
+ throw error54;
243909
+ }
243910
+ return { localPath: filePath, sizeBytes };
243911
+ }
243912
+ var SlackAttachmentDownloadError, SLACK_ATTACHMENT_READ_IDLE_TIMEOUT_MS = 60000;
243913
+ var init_attachment_stream = __esm(() => {
243914
+ init_config2();
243915
+ SlackAttachmentDownloadError = class SlackAttachmentDownloadError extends Error {
243916
+ reason;
243917
+ constructor(reason, message) {
243918
+ super(message);
243919
+ this.reason = reason;
243920
+ this.name = "SlackAttachmentDownloadError";
243921
+ }
243922
+ };
243923
+ });
243924
+
243925
+ // src/channels/slack/media.ts
243926
+ async function mapSlackThreadMessage(message, attachmentOptions, sourceThreadId) {
243927
+ const attachments = await resolveSlackMessageAttachments(message, attachmentOptions, sourceThreadId);
243928
+ return {
243929
+ text: resolveSlackThreadMessageText(message),
243930
+ userId: isNonEmptyString7(message.user) ? message.user : undefined,
243931
+ botId: isNonEmptyString7(message.bot_id) ? message.bot_id : undefined,
243932
+ ts: isNonEmptyString7(message.ts) ? message.ts : undefined,
243933
+ ...attachments.length > 0 ? { attachments } : {}
243934
+ };
243935
+ }
243936
+ function asRecord5(value) {
243937
+ return value && typeof value === "object" ? value : null;
243938
+ }
243939
+ function isNonEmptyString7(value) {
243940
+ return typeof value === "string" && value.trim().length > 0;
243941
+ }
243942
+ function normalizeSlackAttachmentLike2(value) {
243943
+ const record5 = asRecord5(value);
243944
+ if (!record5) {
243945
+ return null;
243946
+ }
243947
+ return {
243948
+ text: isNonEmptyString7(record5.text) ? record5.text : undefined,
243949
+ fallback: isNonEmptyString7(record5.fallback) ? record5.fallback : undefined,
243950
+ pretext: isNonEmptyString7(record5.pretext) ? record5.pretext : undefined,
243951
+ author_name: isNonEmptyString7(record5.author_name) ? record5.author_name : undefined,
243952
+ title: isNonEmptyString7(record5.title) ? record5.title : undefined
243953
+ };
243954
+ }
243955
+ function uniqueNonEmptyStrings(values3) {
243956
+ const seen = new Set;
243957
+ const normalized = [];
243958
+ for (const value of values3) {
243959
+ const text2 = value?.trim();
243960
+ if (!text2 || seen.has(text2)) {
243961
+ continue;
243962
+ }
243963
+ seen.add(text2);
243964
+ normalized.push(text2);
243965
+ }
243966
+ return normalized;
243967
+ }
243968
+ function resolveSlackAttachmentText(attachment) {
243969
+ const parts = uniqueNonEmptyStrings([
243970
+ attachment.pretext,
243971
+ attachment.author_name,
243972
+ attachment.title,
243973
+ attachment.text,
243974
+ attachment.fallback
243975
+ ]);
243976
+ return parts.join(`
243977
+ `);
243978
+ }
243979
+ function resolveSlackThreadMessageText(message) {
243980
+ const text2 = typeof message.text === "string" ? message.text.trim() : "";
243981
+ if (text2) {
243982
+ return text2;
243983
+ }
243984
+ const attachmentTexts = Array.isArray(message.attachments) ? message.attachments.map((entry) => normalizeSlackAttachmentLike2(entry)).filter((entry) => Boolean(entry)).map((attachment) => resolveSlackAttachmentText(attachment)).filter(isNonEmptyString7) : [];
243985
+ if (attachmentTexts.length > 0) {
243986
+ return attachmentTexts.join(`
243987
+
243988
+ `);
243989
+ }
243990
+ const files2 = collectSlackFiles(message);
243991
+ if (files2.length === 0) {
243992
+ return "";
243993
+ }
243994
+ const fileNames = files2.map((file3) => file3.name ?? "file").join(", ");
243995
+ return `[attached: ${fileNames}]`;
243996
+ }
244059
243997
  function resolveAttachmentKind(mimeType) {
244060
243998
  const normalized = mimeType?.toLowerCase();
244061
243999
  if (!normalized) {
@@ -244072,47 +244010,9 @@ function resolveAttachmentKind(mimeType) {
244072
244010
  }
244073
244011
  return "file";
244074
244012
  }
244075
- async function fetchWithSlackAuth(url2, token2, signal) {
244076
- const parsed = assertSlackFileUrl(url2);
244077
- const authHeaders = { Authorization: `Bearer ${token2}` };
244078
- const initial2 = await fetch(parsed.href, {
244079
- headers: authHeaders,
244080
- redirect: "manual",
244081
- ...signal ? { signal } : {}
244082
- });
244083
- if (initial2.status < 300 || initial2.status >= 400) {
244084
- return initial2;
244085
- }
244086
- const redirectUrl = initial2.headers.get("location");
244087
- if (!redirectUrl) {
244088
- return initial2;
244089
- }
244090
- const resolved = new URL(redirectUrl, parsed.href);
244091
- if (resolved.origin === parsed.origin) {
244092
- return fetch(resolved.href, {
244093
- headers: authHeaders,
244094
- redirect: "follow",
244095
- ...signal ? { signal } : {}
244096
- });
244097
- }
244098
- return fetch(resolved.href, {
244099
- redirect: "follow",
244100
- ...signal ? { signal } : {}
244101
- });
244102
- }
244103
- function resolveSlackAttachmentFileName(params) {
244104
- const hintedName = params.file.name ?? (params.url ? basename14(new URL(params.url).pathname) : undefined) ?? `${params.file.id ?? "attachment"}${extensionForMimeType2(params.file.mimetype)}`;
244105
- return extname7(hintedName) || !params.mimeType ? hintedName : `${hintedName}${extensionForMimeType2(params.mimeType)}`;
244106
- }
244107
- function resolveSlackAttachmentMimeType(params) {
244108
- const preferredMimeType = params.responseMimeType && !isGenericSlackMimeType(params.responseMimeType) ? params.responseMimeType : params.file.mimetype && !isGenericSlackMimeType(params.file.mimetype) ? params.file.mimetype : undefined;
244109
- return resolveMimeType(params.fileName, preferredMimeType);
244110
- }
244111
244013
  function createUndownloadedSlackAttachment(params) {
244112
- const fileName = resolveSlackAttachmentFileName({ file: params.file });
244113
- const mimeType = resolveSlackAttachmentMimeType({
244114
- file: params.file,
244115
- fileName
244014
+ const { fileName, mimeType } = resolveSlackFileMetadata({
244015
+ file: params.file
244116
244016
  });
244117
244017
  return {
244118
244018
  id: params.file.id,
@@ -244134,42 +244034,24 @@ async function materializeSlackAttachment(params) {
244134
244034
  if (!url2) {
244135
244035
  throw new SlackAttachmentDownloadError("missing_download_url", "Slack attachment does not include a private download URL.");
244136
244036
  }
244137
- const response = await fetchWithSlackAuth(url2, params.token, params.signal).catch((error54) => {
244037
+ const fetched = await fetchSlackFile({
244038
+ token: params.token,
244039
+ file: params.file,
244040
+ signal: params.signal
244041
+ }).catch((error54) => {
244138
244042
  throw new SlackAttachmentDownloadError("download_failed", error54 instanceof Error ? error54.message : "Slack attachment fetch failed.");
244139
244043
  });
244140
- if (!response.ok) {
244141
- throw new SlackAttachmentDownloadError("download_failed", `Slack attachment fetch failed with HTTP ${response.status}.`);
244142
- }
244143
- const contentLengthHeader = response.headers.get("content-length");
244144
- const contentLength = contentLengthHeader ? Number(contentLengthHeader) : undefined;
244145
- if (params.maxBytes !== undefined && contentLength !== undefined && Number.isFinite(contentLength) && contentLength > params.maxBytes) {
244146
- await response.body?.cancel().catch(() => {
244044
+ if (params.maxBytes !== undefined && fetched.contentLength !== undefined && fetched.contentLength > params.maxBytes) {
244045
+ await fetched.body.cancel().catch(() => {
244147
244046
  return;
244148
244047
  });
244149
- throw new SlackAttachmentDownloadError("exceeds_auto_download_limit", `Slack attachment is ${contentLength} bytes; automatic download limit is ${params.maxBytes} bytes.`);
244150
- }
244151
- const responseMimeType = response.headers.get("content-type")?.split(";")[0]?.trim() || undefined;
244152
- const hintedName = resolveSlackAttachmentFileName({
244153
- file: params.file,
244154
- url: url2
244155
- });
244156
- const mimeType = resolveSlackAttachmentMimeType({
244157
- file: params.file,
244158
- fileName: hintedName,
244159
- responseMimeType
244160
- });
244161
- const fileName = resolveSlackAttachmentFileName({
244162
- file: params.file,
244163
- url: url2,
244164
- mimeType
244165
- });
244166
- if (!response.body) {
244167
- throw new SlackAttachmentDownloadError("download_failed", "Slack attachment response did not include a body.");
244048
+ throw new SlackAttachmentDownloadError("exceeds_auto_download_limit", `Slack attachment is ${fetched.contentLength} bytes; automatic download limit is ${params.maxBytes} bytes.`);
244168
244049
  }
244050
+ const { fileName, mimeType } = fetched;
244169
244051
  const saved = await saveSlackAttachmentStream({
244170
244052
  accountId: params.accountId,
244171
244053
  fileName,
244172
- body: response.body,
244054
+ body: fetched.body,
244173
244055
  maxBytes: params.maxBytes,
244174
244056
  signal: params.signal
244175
244057
  });
@@ -244200,40 +244082,6 @@ async function materializeSlackAttachment(params) {
244200
244082
  }
244201
244083
  return attachment;
244202
244084
  }
244203
- function collectSlackFiles(rawEvent) {
244204
- const record5 = asRecord4(rawEvent);
244205
- if (!record5) {
244206
- return [];
244207
- }
244208
- const deduped = new Map;
244209
- const push = (file3) => {
244210
- if (!file3) {
244211
- return;
244212
- }
244213
- const key2 = file3.id ?? file3.url_private_download ?? file3.url_private ?? `${file3.name ?? "attachment"}:${file3.mimetype ?? ""}`;
244214
- deduped.set(key2, file3);
244215
- };
244216
- if (Array.isArray(record5.files)) {
244217
- for (const entry of record5.files) {
244218
- push(normalizeSlackFileLike(entry));
244219
- }
244220
- }
244221
- if (Array.isArray(record5.attachments)) {
244222
- record5.attachments.map((entry) => normalizeSlackAttachmentLike(entry)).filter((entry) => Boolean(entry)).forEach((attachment, index) => {
244223
- for (const file3 of attachment.files ?? []) {
244224
- push(file3);
244225
- }
244226
- if (attachment.image_url) {
244227
- push({
244228
- id: `attachment-image-${index}`,
244229
- name: `attachment-image-${index}.png`,
244230
- url_private: attachment.image_url
244231
- });
244232
- }
244233
- });
244234
- }
244235
- return Array.from(deduped.values()).slice(0, MAX_SLACK_ATTACHMENTS);
244236
- }
244237
244085
  async function resolveSlackFilesAsAttachments(params) {
244238
244086
  if (params.files.length === 0) {
244239
244087
  return [];
@@ -244262,7 +244110,7 @@ async function resolveSlackFilesAsAttachments(params) {
244262
244110
  return resolved;
244263
244111
  }
244264
244112
  function resolveSlackThreadAttachmentOptions(params) {
244265
- if (!isNonEmptyString6(params.accountId) || !isNonEmptyString6(params.token)) {
244113
+ if (!isNonEmptyString7(params.accountId) || !isNonEmptyString7(params.token)) {
244266
244114
  return;
244267
244115
  }
244268
244116
  return {
@@ -244289,18 +244137,18 @@ async function resolveSlackMessageAttachments(message, attachmentOptions, source
244289
244137
  token: attachmentOptions.token,
244290
244138
  files: collectSlackFiles(message),
244291
244139
  sourceMessageId: message.ts,
244292
- sourceThreadId: sourceThreadId ?? (isNonEmptyString6(message.thread_ts) ? message.thread_ts : null),
244140
+ sourceThreadId: sourceThreadId ?? (isNonEmptyString7(message.thread_ts) ? message.thread_ts : null),
244293
244141
  transcribeVoice: attachmentOptions.transcribeVoice
244294
244142
  });
244295
244143
  }
244296
244144
  async function resolveSlackInboundAttachments(params) {
244297
- const rawEvent = asRecord4(params.rawEvent);
244145
+ const rawEvent = asRecord5(params.rawEvent);
244298
244146
  return resolveSlackFilesAsAttachments({
244299
244147
  accountId: params.accountId,
244300
244148
  token: params.token,
244301
244149
  files: collectSlackFiles(params.rawEvent),
244302
- sourceMessageId: isNonEmptyString6(rawEvent?.ts) ? rawEvent.ts : undefined,
244303
- sourceThreadId: isNonEmptyString6(rawEvent?.thread_ts) ? rawEvent.thread_ts : null,
244150
+ sourceMessageId: isNonEmptyString7(rawEvent?.ts) ? rawEvent.ts : undefined,
244151
+ sourceThreadId: isNonEmptyString7(rawEvent?.thread_ts) ? rawEvent.thread_ts : null,
244304
244152
  transcribeVoice: params.transcribeVoice
244305
244153
  });
244306
244154
  }
@@ -244309,28 +244157,24 @@ async function resolveSlackCurrentMessageAttachments(params) {
244309
244157
  if (!attachmentOptions) {
244310
244158
  return [];
244311
244159
  }
244312
- const fetchLimit = 200;
244313
- let cursor;
244314
244160
  try {
244315
- do {
244316
- const response = await params.client.conversations.replies({
244317
- channel: params.channelId,
244318
- ts: params.threadTs,
244319
- limit: fetchLimit,
244320
- inclusive: true,
244321
- ...cursor ? { cursor } : {}
244322
- });
244323
- const message = (response.messages ?? []).find((entry) => entry.ts === params.messageTs);
244324
- if (message) {
244325
- return resolveSlackMessageAttachments(message, attachmentOptions, params.threadTs);
244326
- }
244327
- const nextCursor = response.response_metadata?.next_cursor;
244328
- cursor = typeof nextCursor === "string" && nextCursor.trim().length > 0 ? nextCursor.trim() : undefined;
244329
- } while (cursor);
244161
+ const files2 = await resolveSlackMessageFiles({
244162
+ channelId: params.channelId,
244163
+ threadTs: params.threadTs,
244164
+ messageTs: params.messageTs,
244165
+ client: params.client
244166
+ });
244167
+ return files2 ? resolveSlackFilesAsAttachments({
244168
+ accountId: attachmentOptions.accountId,
244169
+ token: attachmentOptions.token,
244170
+ files: files2,
244171
+ sourceMessageId: params.messageTs,
244172
+ sourceThreadId: params.threadTs,
244173
+ transcribeVoice: attachmentOptions.transcribeVoice
244174
+ }) : [];
244330
244175
  } catch {
244331
244176
  return [];
244332
244177
  }
244333
- return [];
244334
244178
  }
244335
244179
  async function resolveSlackThreadStarter(params) {
244336
244180
  try {
@@ -244379,13 +244223,13 @@ async function resolveSlackThreadHistory(params) {
244379
244223
  if (message.ts === params.threadTs) {
244380
244224
  continue;
244381
244225
  }
244382
- const isBotMessage = isNonEmptyString6(message.bot_id);
244226
+ const isBotMessage = isNonEmptyString7(message.bot_id);
244383
244227
  if (params.include === "unrouted-bot") {
244384
244228
  if (!isBotMessage) {
244385
244229
  retained.length = 0;
244386
244230
  continue;
244387
244231
  }
244388
- if (isNonEmptyString6(params.excludeBotId) && message.bot_id === params.excludeBotId) {
244232
+ if (isNonEmptyString7(params.excludeBotId) && message.bot_id === params.excludeBotId) {
244389
244233
  continue;
244390
244234
  }
244391
244235
  if (params.acceptMentionedBots === true && hasSlackMention(message.text ?? "", params.routedBotUserId ?? null)) {
@@ -244393,7 +244237,7 @@ async function resolveSlackThreadHistory(params) {
244393
244237
  continue;
244394
244238
  }
244395
244239
  }
244396
- if (params.include === "bot" && !isNonEmptyString6(message.bot_id)) {
244240
+ if (params.include === "bot" && !isNonEmptyString7(message.bot_id)) {
244397
244241
  continue;
244398
244242
  }
244399
244243
  if (!hasSlackThreadMessageContent(message, attachmentOptions)) {
@@ -244404,8 +244248,8 @@ async function resolveSlackThreadHistory(params) {
244404
244248
  retained.shift();
244405
244249
  }
244406
244250
  }
244407
- const nextCursor = response.response_metadata?.next_cursor;
244408
- cursor = typeof nextCursor === "string" && nextCursor.trim().length > 0 ? nextCursor.trim() : undefined;
244251
+ const nextCursor2 = response.response_metadata?.next_cursor;
244252
+ cursor = typeof nextCursor2 === "string" && nextCursor2.trim().length > 0 ? nextCursor2.trim() : undefined;
244409
244253
  } while (cursor);
244410
244254
  const mapped3 = await Promise.all(retained.map((message) => mapSlackThreadMessage(message, attachmentOptions, params.threadTs)));
244411
244255
  return mapped3.filter(hasHydratedSlackThreadMessageContent);
@@ -244439,57 +244283,21 @@ async function resolveSlackChannelHistory(params) {
244439
244283
  return [];
244440
244284
  }
244441
244285
  }
244442
- var MAX_SLACK_ATTACHMENTS = 8, MAX_SLACK_ATTACHMENT_BYTES, ALLOWED_SLACK_HOST_SUFFIXES;
244286
+ var MAX_SLACK_ATTACHMENT_BYTES;
244443
244287
  var init_media2 = __esm(() => {
244288
+ init_attachment_primitives();
244444
244289
  init_attachment_stream();
244445
244290
  init_utils6();
244446
244291
  MAX_SLACK_ATTACHMENT_BYTES = 20 * 1024 * 1024;
244447
- ALLOWED_SLACK_HOST_SUFFIXES = [
244448
- "slack.com",
244449
- "slack-edge.com",
244450
- "slack-files.com"
244451
- ];
244452
244292
  });
244453
244293
 
244454
244294
  // src/channels/slack/attachment-download.ts
244455
- function isNonEmptyString7(value) {
244456
- return typeof value === "string" && value.trim().length > 0;
244457
- }
244458
- async function resolveCanonicalSlackMessage(params) {
244459
- if (isNonEmptyString7(params.threadTs)) {
244460
- let cursor;
244461
- do {
244462
- const response2 = await params.client.conversations.replies({
244463
- channel: params.channelId,
244464
- ts: params.threadTs,
244465
- limit: 200,
244466
- inclusive: true,
244467
- ...cursor ? { cursor } : {}
244468
- });
244469
- const message = (response2.messages ?? []).find((entry) => entry.ts === params.messageTs);
244470
- if (message) {
244471
- return message;
244472
- }
244473
- const nextCursor = response2.response_metadata?.next_cursor;
244474
- cursor = isNonEmptyString7(nextCursor) ? nextCursor.trim() : undefined;
244475
- } while (cursor);
244476
- return null;
244477
- }
244478
- const response = await params.client.conversations.history({
244479
- channel: params.channelId,
244480
- oldest: params.messageTs,
244481
- latest: params.messageTs,
244482
- inclusive: true,
244483
- limit: 1
244484
- });
244485
- return (response.messages ?? []).find((entry) => entry.ts === params.messageTs) ?? null;
244486
- }
244487
244295
  async function downloadSlackAttachmentById(params) {
244488
- const message = await resolveCanonicalSlackMessage(params);
244489
- if (!message) {
244296
+ const files2 = await resolveSlackMessageFiles(params);
244297
+ if (!files2) {
244490
244298
  throw new Error(`Slack message ${params.messageTs} was not found in chat ${params.channelId}.`);
244491
244299
  }
244492
- const file3 = collectSlackFiles(message).find((entry) => entry.id === params.attachmentId);
244300
+ const file3 = files2.find((entry) => entry.id === params.attachmentId);
244493
244301
  if (!file3) {
244494
244302
  throw new Error(`Slack attachment ${params.attachmentId} is not attached to message ${params.messageTs}.`);
244495
244303
  }
@@ -244503,14 +244311,15 @@ async function downloadSlackAttachmentById(params) {
244503
244311
  });
244504
244312
  }
244505
244313
  var init_attachment_download = __esm(() => {
244314
+ init_attachment_primitives();
244506
244315
  init_media2();
244507
244316
  });
244508
244317
 
244509
244318
  // src/channels/slack/file-upload.ts
244510
244319
  import { readFile as readFile14 } from "node:fs/promises";
244511
- import { basename as basename15, extname as extname8 } from "node:path";
244320
+ import { basename as basename14, extname as extname7 } from "node:path";
244512
244321
  function resolveUploadMimeType(filePath) {
244513
- switch (extname8(filePath).toLowerCase()) {
244322
+ switch (extname7(filePath).toLowerCase()) {
244514
244323
  case ".png":
244515
244324
  return "image/png";
244516
244325
  case ".jpg":
@@ -244535,7 +244344,7 @@ async function uploadSlackFile(slackClient, msg) {
244535
244344
  throw new Error("mediaPath is required for Slack file uploads.");
244536
244345
  }
244537
244346
  const buffer = await readFile14(msg.mediaPath);
244538
- const uploadFileName = msg.fileName ?? basename15(msg.mediaPath);
244347
+ const uploadFileName = msg.fileName ?? basename14(msg.mediaPath);
244539
244348
  const uploadTitle = msg.title ?? uploadFileName;
244540
244349
  const uploadMimeType = resolveUploadMimeType(uploadFileName);
244541
244350
  const uploadUrlResp = await slackClient.files.getUploadURLExternal({
@@ -246703,6 +246512,7 @@ function createSlackAdapter(config3) {
246703
246512
  let running = false;
246704
246513
  let botUserId = null;
246705
246514
  let botId = null;
246515
+ let workspaceName = null;
246706
246516
  let adapter;
246707
246517
  const agentThreadTracker = createAgentThreadTracker();
246708
246518
  const debounce4 = createSlackInboundDebounceController({
@@ -246730,12 +246540,21 @@ function createSlackAdapter(config3) {
246730
246540
  async function ensureApp() {
246731
246541
  if (app)
246732
246542
  return app;
246543
+ const auth = await (await ensureWriteClient()).auth.test();
246544
+ if (!isNonEmptyString4(auth.user_id) || !isNonEmptyString4(auth.bot_id)) {
246545
+ throw new Error("Slack auth.test did not return bot identity fields");
246546
+ }
246547
+ botUserId = auth.user_id;
246548
+ botId = auth.bot_id;
246549
+ workspaceName = isNonEmptyString4(auth.team) ? auth.team : null;
246733
246550
  const bolt = await loadSlackBoltModule();
246734
246551
  const App2 = resolveSlackAppConstructor(bolt);
246735
246552
  const instance = new App2({
246736
246553
  token: config3.botToken,
246737
246554
  appToken: config3.appToken,
246738
- socketMode: true
246555
+ socketMode: true,
246556
+ botUserId,
246557
+ botId
246739
246558
  });
246740
246559
  instance.error(async (error54) => {
246741
246560
  console.error("[Slack] Unhandled app error:", error54);
@@ -246940,13 +246759,9 @@ function createSlackAdapter(config3) {
246940
246759
  if (running)
246941
246760
  return;
246942
246761
  const slackApp = await ensureApp();
246943
- const auth = await slackApp.client.auth.test();
246944
- const authRecord = auth;
246945
- botUserId = isNonEmptyString4(authRecord.user_id) ? authRecord.user_id : null;
246946
- botId = isNonEmptyString4(authRecord.bot_id) ? authRecord.bot_id : null;
246947
246762
  await slackApp.start();
246948
246763
  running = true;
246949
- console.log(`[Slack] App started for workspace ${auth.team ?? "unknown"} (dm_policy: ${config3.dmPolicy})`);
246764
+ console.log(`[Slack] App started for workspace ${workspaceName ?? "unknown"} (dm_policy: ${config3.dmPolicy})`);
246950
246765
  },
246951
246766
  async stop() {
246952
246767
  if (!app || !running)
@@ -246959,6 +246774,7 @@ function createSlackAdapter(config3) {
246959
246774
  writeClientPromise = null;
246960
246775
  botUserId = null;
246961
246776
  botId = null;
246777
+ workspaceName = null;
246962
246778
  status.clear();
246963
246779
  approvals.clear();
246964
246780
  debounce4.clear();
@@ -247391,8 +247207,8 @@ async function listSlackChannels(account, existingClient) {
247391
247207
  name: channel.name
247392
247208
  });
247393
247209
  }
247394
- const nextCursor = response.response_metadata?.next_cursor?.trim();
247395
- cursor = nextCursor ? nextCursor : undefined;
247210
+ const nextCursor2 = response.response_metadata?.next_cursor?.trim();
247211
+ cursor = nextCursor2 ? nextCursor2 : undefined;
247396
247212
  } while (cursor);
247397
247213
  return channels;
247398
247214
  }
@@ -248198,7 +248014,7 @@ var init_typing_controller2 = __esm(() => {
248198
248014
  });
248199
248015
 
248200
248016
  // src/channels/discord/adapter.ts
248201
- import { basename as basename16 } from "node:path";
248017
+ import { basename as basename15 } from "node:path";
248202
248018
  function createDiscordAdapter(config3) {
248203
248019
  let client = null;
248204
248020
  let running = false;
@@ -248741,7 +248557,7 @@ function createDiscordAdapter(config3) {
248741
248557
  files: [
248742
248558
  {
248743
248559
  attachment: msg.mediaPath,
248744
- name: msg.fileName ?? basename16(msg.mediaPath)
248560
+ name: msg.fileName ?? basename15(msg.mediaPath)
248745
248561
  }
248746
248562
  ]
248747
248563
  });
@@ -249101,12 +248917,12 @@ var init_plugin4 = __esm(() => {
249101
248917
 
249102
248918
  // src/channels/whatsapp/attachment-policy.ts
249103
248919
  import { realpathSync as realpathSync5, statSync as statSync12 } from "node:fs";
249104
- import { extname as extname9, isAbsolute as isAbsolute23, relative as relative8, sep as sep6 } from "node:path";
248920
+ import { extname as extname8, isAbsolute as isAbsolute23, relative as relative8, sep as sep6 } from "node:path";
249105
248921
  function deny(reason) {
249106
248922
  return { allowed: false, reason: `Attachment denied: ${reason}` };
249107
248923
  }
249108
248924
  function inferMimeType(mediaPath) {
249109
- return WHATSAPP_ATTACHMENT_MIME_TYPES[extname9(mediaPath).toLowerCase()] ?? "application/octet-stream";
248925
+ return WHATSAPP_ATTACHMENT_MIME_TYPES[extname8(mediaPath).toLowerCase()] ?? "application/octet-stream";
249110
248926
  }
249111
248927
  function inferWhatsAppAttachmentMimeType(mediaPath) {
249112
248928
  return inferMimeType(mediaPath);
@@ -249259,7 +249075,7 @@ var init_attachment_policy = __esm(() => {
249259
249075
  // src/channels/whatsapp/media.ts
249260
249076
  import { randomUUID as randomUUID19 } from "node:crypto";
249261
249077
  import { mkdir as mkdir10, writeFile as writeFile12 } from "node:fs/promises";
249262
- import { basename as basename17, extname as extname10, join as join45 } from "node:path";
249078
+ import { basename as basename16, extname as extname9, join as join45 } from "node:path";
249263
249079
  function unwrapWhatsAppMessageContent(message) {
249264
249080
  if (!message || typeof message !== "object")
249265
249081
  return null;
@@ -249462,8 +249278,8 @@ function buildWhatsAppOutboundPayload(msg, resolvedMedia) {
249462
249278
  return { text: msg.text };
249463
249279
  }
249464
249280
  const mediaPath = resolvedMedia?.mediaPath ?? msg.mediaPath;
249465
- const fileName = resolvedMedia ? basename17(mediaPath) : msg.fileName || basename17(mediaPath);
249466
- const extension = resolvedMedia ? extname10(mediaPath).toLowerCase() : getWhatsAppOutboundMediaExtension(msg);
249281
+ const fileName = resolvedMedia ? basename16(mediaPath) : msg.fileName || basename16(mediaPath);
249282
+ const extension = resolvedMedia ? extname9(mediaPath).toLowerCase() : getWhatsAppOutboundMediaExtension(msg);
249467
249283
  const caption = msg.text?.trim() || msg.title?.trim() || undefined;
249468
249284
  if (WHATSAPP_IMAGE_EXTENSIONS.has(extension)) {
249469
249285
  return { image: { url: mediaPath }, ...caption ? { caption } : {} };
@@ -249486,8 +249302,8 @@ function buildWhatsAppOutboundPayload(msg, resolvedMedia) {
249486
249302
  };
249487
249303
  }
249488
249304
  function getWhatsAppOutboundMediaExtension(msg) {
249489
- const fileNameExtension = extname10(msg.fileName ?? "");
249490
- const mediaPathExtension = extname10(msg.mediaPath ?? "");
249305
+ const fileNameExtension = extname9(msg.fileName ?? "");
249306
+ const mediaPathExtension = extname9(msg.mediaPath ?? "");
249491
249307
  return (fileNameExtension || mediaPathExtension).toLowerCase();
249492
249308
  }
249493
249309
  var DEFAULT_WHATSAPP_MEDIA_MAX_BYTES, WHATSAPP_IMAGE_EXTENSIONS, WHATSAPP_VIDEO_EXTENSIONS, WHATSAPP_VOICE_MEMO_EXTENSIONS;
@@ -249508,7 +249324,7 @@ var init_media4 = __esm(() => {
249508
249324
  });
249509
249325
 
249510
249326
  // src/channels/whatsapp/adapter-helpers.ts
249511
- function asRecord5(value) {
249327
+ function asRecord6(value) {
249512
249328
  return value && typeof value === "object" ? value : {};
249513
249329
  }
249514
249330
  function isWhatsAppReactionMessage(message) {
@@ -249516,12 +249332,12 @@ function isWhatsAppReactionMessage(message) {
249516
249332
  return !!content?.reactionMessage;
249517
249333
  }
249518
249334
  function isWhatsAppConflictDisconnect(update3) {
249519
- const record5 = asRecord5(update3);
249335
+ const record5 = asRecord6(update3);
249520
249336
  if (record5.connection !== "close")
249521
249337
  return false;
249522
- const lastDisconnect = asRecord5(record5.lastDisconnect);
249523
- const error54 = asRecord5(lastDisconnect.error);
249524
- const output = asRecord5(error54.output);
249338
+ const lastDisconnect = asRecord6(record5.lastDisconnect);
249339
+ const error54 = asRecord6(lastDisconnect.error);
249340
+ const output = asRecord6(error54.output);
249525
249341
  const statusCode2 = output.statusCode;
249526
249342
  const message = typeof error54.message === "string" ? error54.message : "";
249527
249343
  return statusCode2 === 440 || /\bconflict\b/i.test(message) || /connection replaced/i.test(message);
@@ -250057,7 +249873,7 @@ function createLidStore(filePath) {
250057
249873
  var init_lid_store = () => {};
250058
249874
 
250059
249875
  // src/channels/whatsapp/message-store.ts
250060
- function asRecord6(value) {
249876
+ function asRecord7(value) {
250061
249877
  return value && typeof value === "object" ? value : {};
250062
249878
  }
250063
249879
  function unrefTimeout(timer) {
@@ -250133,10 +249949,10 @@ function createWhatsAppMessageStore(canonicalizeChatId) {
250133
249949
  const stored = messages.get(messageId);
250134
249950
  if (!stored)
250135
249951
  return false;
250136
- return asRecord6(asRecord6(stored).key).fromMe === true;
249952
+ return asRecord7(asRecord7(stored).key).fromMe === true;
250137
249953
  }
250138
249954
  function getStoredTargetKey(targetJid, messageId) {
250139
- const key2 = asRecord6(asRecord6(messages.get(messageId)).key);
249955
+ const key2 = asRecord7(asRecord7(messages.get(messageId)).key);
250140
249956
  if (typeof key2.id !== "string" || key2.id !== messageId)
250141
249957
  return null;
250142
249958
  const remoteJid = typeof key2.remoteJid === "string" ? stripDeviceSuffix(key2.remoteJid) : "";
@@ -250955,8 +250771,8 @@ function createWhatsAppAdapter(account, dependencies4 = {}) {
250955
250771
  if (closingSocket2)
250956
250772
  typing.clearOwner(closingSocket2);
250957
250773
  clearActiveSocket(false);
250958
- const lastDisconnect2 = asRecord5(update3.lastDisconnect);
250959
- const error55 = asRecord5(lastDisconnect2.error);
250774
+ const lastDisconnect2 = asRecord6(update3.lastDisconnect);
250775
+ const error55 = asRecord6(lastDisconnect2.error);
250960
250776
  running = false;
250961
250777
  stopping = true;
250962
250778
  clearWhatsAppReconnectTimer();
@@ -250976,8 +250792,8 @@ function createWhatsAppAdapter(account, dependencies4 = {}) {
250976
250792
  if (closingSocket)
250977
250793
  typing.clearOwner(closingSocket);
250978
250794
  clearActiveSocket(false);
250979
- const lastDisconnect = asRecord5(update3.lastDisconnect);
250980
- const error54 = asRecord5(lastDisconnect.error);
250795
+ const lastDisconnect = asRecord6(update3.lastDisconnect);
250796
+ const error54 = asRecord6(lastDisconnect.error);
250981
250797
  const now2 = reconnectScheduler.now();
250982
250798
  while (recentDisconnects.length > 0) {
250983
250799
  const oldest = recentDisconnects[0];
@@ -251044,7 +250860,7 @@ function createWhatsAppAdapter(account, dependencies4 = {}) {
251044
250860
  try {
251045
250861
  if (!isActiveBatch(batchSocket, generation2))
251046
250862
  return;
251047
- const record5 = asRecord5(event2);
250863
+ const record5 = asRecord6(event2);
251048
250864
  if (record5.type !== "notify" && record5.type !== "append")
251049
250865
  return;
251050
250866
  const messages = Array.isArray(record5.messages) ? record5.messages : [];
@@ -252069,8 +251885,8 @@ import {
252069
251885
  } from "node:fs";
252070
251886
  import { homedir as homedir27 } from "node:os";
252071
251887
  import {
252072
- basename as basename18,
252073
- extname as extname11,
251888
+ basename as basename17,
251889
+ extname as extname10,
252074
251890
  isAbsolute as isAbsolute25,
252075
251891
  join as join48,
252076
251892
  relative as relative9,
@@ -252103,7 +251919,7 @@ function normalizeSignalMimeType(value) {
252103
251919
  return normalized || undefined;
252104
251920
  }
252105
251921
  function inferSignalMimeTypeFromName(fileName) {
252106
- switch (extname11(fileName).toLowerCase()) {
251922
+ switch (extname10(fileName).toLowerCase()) {
252107
251923
  case ".jpg":
252108
251924
  case ".jpeg":
252109
251925
  return "image/jpeg";
@@ -252141,7 +251957,7 @@ function inferSignalAttachmentKind(params) {
252141
251957
  if (params.mimeType?.startsWith("video/")) {
252142
251958
  return "video";
252143
251959
  }
252144
- switch (extname11(params.fileName).toLowerCase()) {
251960
+ switch (extname10(params.fileName).toLowerCase()) {
252145
251961
  case ".jpg":
252146
251962
  case ".jpeg":
252147
251963
  case ".png":
@@ -252267,7 +252083,7 @@ function signalMimeTypeMatchesFileName(mimeType, filePath) {
252267
252083
  if (!mimeType) {
252268
252084
  return true;
252269
252085
  }
252270
- const extension = extname11(filePath).toLowerCase();
252086
+ const extension = extname10(filePath).toLowerCase();
252271
252087
  if (mimeType.startsWith("image/")) {
252272
252088
  return [".jpg", ".jpeg", ".png", ".gif", ".webp"].includes(extension);
252273
252089
  }
@@ -252328,13 +252144,13 @@ function resolveSignalAttachmentFileName(attachment, sourcePath) {
252328
252144
  if (hintedName && !hintedName.includes("/") && !hintedName.includes("\\")) {
252329
252145
  return hintedName;
252330
252146
  }
252331
- return basename18(sourcePath) || "attachment";
252147
+ return basename17(sourcePath) || "attachment";
252332
252148
  }
252333
252149
  function copySignalAttachment(params) {
252334
252150
  const sourceStat = statSync13(params.sourcePath);
252335
252151
  const sizeBytes = typeof params.attachment.size === "number" && params.attachment.size >= 0 ? params.attachment.size : sourceStat.size;
252336
252152
  if (sizeBytes > params.maxBytes || sourceStat.size > params.maxBytes) {
252337
- console.warn(`[Signal] Skipping attachment ${params.attachment.filename ?? params.attachment.id ?? basename18(params.sourcePath)}: ${Math.max(sizeBytes, sourceStat.size)} bytes exceeds Signal download limit (${params.maxBytes} bytes).`);
252153
+ console.warn(`[Signal] Skipping attachment ${params.attachment.filename ?? params.attachment.id ?? basename17(params.sourcePath)}: ${Math.max(sizeBytes, sourceStat.size)} bytes exceeds Signal download limit (${params.maxBytes} bytes).`);
252338
252154
  return null;
252339
252155
  }
252340
252156
  const fileName = resolveSignalAttachmentFileName(params.attachment, params.sourcePath);
@@ -268889,6 +268705,7 @@ __export(exports_environments2, {
268889
268705
  teleportToEnvironment: () => teleportToEnvironment,
268890
268706
  sendEnvironmentMessage: () => sendEnvironmentMessage,
268891
268707
  resolveEnvironmentConnectionId: () => resolveEnvironmentConnectionId,
268708
+ resolveDesktopEnvironmentConnectionId: () => resolveDesktopEnvironmentConnectionId,
268892
268709
  resolveAgentSandboxConnectionId: () => resolveAgentSandboxConnectionId,
268893
268710
  listEnvironments: () => listEnvironments,
268894
268711
  isEnvironmentOnline: () => isEnvironmentOnline,
@@ -268923,6 +268740,21 @@ function describeEnvironment(environment2) {
268923
268740
  const status = isEnvironmentOnline(environment2) ? "online" : "offline";
268924
268741
  return `${environment2.connectionName} (${environment2.deviceId}, ${status})`;
268925
268742
  }
268743
+ async function resolveDesktopEnvironmentConnectionId(list = listEnvironments) {
268744
+ const response = await list({ limit: 100, onlineOnly: true });
268745
+ const matches3 = response.connections.filter((environment3) => environment3.listenerInstanceId?.startsWith("desktop-direct-cloud:") === true && isEnvironmentOnline(environment3));
268746
+ if (matches3.length === 0) {
268747
+ throw new Error("Desktop Local is unavailable. Open Letta Desktop, enable Remote Access, and wait for its environment to come online.");
268748
+ }
268749
+ if (matches3.length > 1) {
268750
+ throw new Error(`Multiple Desktop environments are online. Run \`letta teleport list\` and choose one by name, device ID, or connection ID. Matched: ${matches3.map(describeEnvironment).join(", ")}`);
268751
+ }
268752
+ const environment2 = matches3[0];
268753
+ if (!environment2?.connectionId) {
268754
+ throw new Error("Desktop Local has no active connection id");
268755
+ }
268756
+ return { connectionId: environment2.connectionId, environment: environment2 };
268757
+ }
268926
268758
  async function resolveEnvironmentConnectionId(selector) {
268927
268759
  const trimmed = selector.trim();
268928
268760
  if (!trimmed) {
@@ -272183,7 +272015,7 @@ var init_listing_shared = __esm(() => {
272183
272015
 
272184
272016
  // node_modules/@letta-ai/trajectory/dist/adapters/claude-code/list.js
272185
272017
  import { homedir as homedir29 } from "node:os";
272186
- import { basename as basename19, join as join52 } from "node:path";
272018
+ import { basename as basename18, join as join52 } from "node:path";
272187
272019
  async function listClaudeCodeTrajectories(root2) {
272188
272020
  const base3 = root2 ?? join52(homedir29(), ".claude", "projects");
272189
272021
  const items3 = [];
@@ -272195,7 +272027,7 @@ async function listClaudeCodeTrajectories(root2) {
272195
272027
  if (!entry.isFile || !entry.name.endsWith(".jsonl"))
272196
272028
  continue;
272197
272029
  const path33 = join52(projectPath, entry.name);
272198
- const listing = listingFromFile(basename19(entry.name, ".jsonl"), path33);
272030
+ const listing = listingFromFile(basename18(entry.name, ".jsonl"), path33);
272199
272031
  if (listing)
272200
272032
  items3.push(listing);
272201
272033
  }
@@ -272208,12 +272040,12 @@ var init_list = __esm(() => {
272208
272040
 
272209
272041
  // node_modules/@letta-ai/trajectory/dist/adapters/codex/list.js
272210
272042
  import { homedir as homedir30 } from "node:os";
272211
- import { basename as basename20, join as join53 } from "node:path";
272043
+ import { basename as basename19, join as join53 } from "node:path";
272212
272044
  async function listCodexTrajectories(root2) {
272213
272045
  const base3 = root2 ?? join53(homedir30(), ".codex", "sessions");
272214
272046
  const items3 = [];
272215
272047
  for (const path33 of collectFiles(base3, ".jsonl", 4)) {
272216
- const listing = listingFromFile(basename20(path33, ".jsonl"), path33);
272048
+ const listing = listingFromFile(basename19(path33, ".jsonl"), path33);
272217
272049
  if (listing)
272218
272050
  items3.push(listing);
272219
272051
  }
@@ -272320,7 +272152,7 @@ var init_list5 = __esm(() => {
272320
272152
  // node_modules/@letta-ai/trajectory/dist/adapters/openclaw/list.js
272321
272153
  import { existsSync as existsSync42 } from "node:fs";
272322
272154
  import { homedir as homedir34 } from "node:os";
272323
- import { basename as basename21, join as join57 } from "node:path";
272155
+ import { basename as basename20, join as join57 } from "node:path";
272324
272156
  async function listOpenClawTrajectories(root2) {
272325
272157
  const base3 = root2 ?? defaultStateDir();
272326
272158
  const items3 = [];
@@ -272333,7 +272165,7 @@ async function listOpenClawTrajectories(root2) {
272333
272165
  if (!entry.isFile || !entry.name.endsWith(".jsonl"))
272334
272166
  continue;
272335
272167
  const path33 = join57(sessionsPath, entry.name);
272336
- const listing = listingFromFile(basename21(entry.name, ".jsonl"), path33);
272168
+ const listing = listingFromFile(basename20(entry.name, ".jsonl"), path33);
272337
272169
  if (listing)
272338
272170
  items3.push(listing);
272339
272171
  }
@@ -274066,9 +273898,9 @@ var init_dream_sources = __esm(() => {
274066
273898
  // src/cli/subcommands/dream-targets.ts
274067
273899
  import { execFileSync as execFileSync7 } from "node:child_process";
274068
273900
  import { mkdir as mkdir12, readFile as readFile19, rm as rm8, writeFile as writeFile14 } from "node:fs/promises";
274069
- import { basename as basename22, dirname as dirname26, join as join61 } from "node:path";
273901
+ import { basename as basename21, dirname as dirname26, join as join61 } from "node:path";
274070
273902
  function resolveDreamTarget(spec) {
274071
- const fileName = basename22(spec);
273903
+ const fileName = basename21(spec);
274072
273904
  if (!fileName) {
274073
273905
  throw new Error(`Invalid --to "${spec}": expected a file path`);
274074
273906
  }
@@ -389373,10 +389205,10 @@ ${lanes.join(`
389373
389205
  }
389374
389206
  function getDefaultLibFilePriority(a2) {
389375
389207
  if (containsPath(defaultLibraryPath, a2.fileName, false)) {
389376
- const basename23 = getBaseFileName(a2.fileName);
389377
- if (basename23 === "lib.d.ts" || basename23 === "lib.es6.d.ts")
389208
+ const basename22 = getBaseFileName(a2.fileName);
389209
+ if (basename22 === "lib.d.ts" || basename22 === "lib.es6.d.ts")
389378
389210
  return 0;
389379
- const name = removeSuffix(removePrefix(basename23, "lib."), ".d.ts");
389211
+ const name = removeSuffix(removePrefix(basename22, "lib."), ".d.ts");
389380
389212
  const index = libs.indexOf(name);
389381
389213
  if (index !== -1)
389382
389214
  return index + 1;
@@ -441939,8 +441771,8 @@ ${options3.prefix}` : `
441939
441771
  }
441940
441772
  };
441941
441773
  for (const file3 of files2) {
441942
- const basename23 = getBaseFileName(file3);
441943
- if (basename23 === "package.json" || basename23 === "bower.json") {
441774
+ const basename22 = getBaseFileName(file3);
441775
+ if (basename22 === "package.json" || basename22 === "bower.json") {
441944
441776
  createProjectWatcher(file3, "FileWatcher");
441945
441777
  continue;
441946
441778
  }
@@ -444817,8 +444649,8 @@ All files are: ${JSON.stringify(names)}`, "Err");
444817
444649
  const fileOrDirectoryPath = removeIgnoredPath(this.toPath(fileOrDirectory));
444818
444650
  if (!fileOrDirectoryPath)
444819
444651
  return;
444820
- const basename23 = getBaseFileName(fileOrDirectoryPath);
444821
- if (((_a8 = result2.affectedModuleSpecifierCacheProjects) == null ? undefined : _a8.size) && (basename23 === "package.json" || basename23 === "node_modules")) {
444652
+ const basename22 = getBaseFileName(fileOrDirectoryPath);
444653
+ if (((_a8 = result2.affectedModuleSpecifierCacheProjects) == null ? undefined : _a8.size) && (basename22 === "package.json" || basename22 === "node_modules")) {
444822
444654
  result2.affectedModuleSpecifierCacheProjects.forEach((project) => {
444823
444655
  var _a22;
444824
444656
  (_a22 = project.getModuleSpecifierCache()) == null || _a22.clear();
@@ -452902,26 +452734,25 @@ function resolveModelForUpdate(payload) {
452902
452734
  }
452903
452735
  };
452904
452736
  }
452905
- function formatEffortSuffix(modelLabel, updateArgs) {
452737
+ function formatEffortSuffix(modelLabel, updateArgs, modelHandle) {
452906
452738
  if (!updateArgs)
452907
452739
  return "";
452908
452740
  const effort = updateArgs.reasoning_effort;
452909
452741
  if (typeof effort !== "string" || effort.length === 0)
452910
452742
  return "";
452911
- const xhighLabel = modelLabel.includes("Fable 5") || modelLabel.includes("Opus 4.7") || modelLabel.includes("Opus 4.8") ? "Extra-High" : "Max";
452912
452743
  const labels = {
452913
452744
  none: "No Reasoning",
452914
452745
  low: "Low",
452915
452746
  medium: "Medium",
452916
452747
  high: "High",
452917
- xhigh: xhighLabel,
452748
+ xhigh: formatXhighEffortLabel(catalogHasDistinctMaxTier({ modelLabel, modelHandle })),
452918
452749
  max: "Max"
452919
452750
  };
452920
452751
  return ` (${labels[effort] ?? effort})`;
452921
452752
  }
452922
452753
  function buildModelUpdateStatusMessage(params) {
452923
- const { modelLabel, toolsetError, updateArgs } = params;
452924
- let message = `Model updated to ${modelLabel}${formatEffortSuffix(modelLabel, updateArgs)}.`;
452754
+ const { modelLabel, toolsetError, updateArgs, modelHandle } = params;
452755
+ let message = `Model updated to ${modelLabel}${formatEffortSuffix(modelLabel, updateArgs, modelHandle)}.`;
452925
452756
  if (toolsetError) {
452926
452757
  message += ` Warning: toolset switch failed (${toolsetError}).`;
452927
452758
  return { message, level: "warning" };
@@ -452992,7 +452823,8 @@ async function applyModelUpdateForRuntime(params) {
452992
452823
  const { message: statusMessage, level: statusLevel } = buildModelUpdateStatusMessage({
452993
452824
  modelLabel: model.label,
452994
452825
  toolsetError,
452995
- updateArgs: model.updateArgs
452826
+ updateArgs: model.updateArgs,
452827
+ modelHandle: model.handle
452996
452828
  });
452997
452829
  emitStatusDelta(socket, scopedRuntime, {
452998
452830
  message: statusMessage,
@@ -453216,6 +453048,7 @@ var init_model_toolset = __esm(async () => {
453216
453048
  init_available_models();
453217
453049
  init_model();
453218
453050
  init_modify();
453051
+ init_reasoning_effort_label();
453219
453052
  init_remote_model_catalog();
453220
453053
  init_backend2();
453221
453054
  init_byok_providers();
@@ -453866,7 +453699,7 @@ async function handleSkillCommand(parsed, socket, safeSocketSend) {
453866
453699
  symlinkSync: symlinkSync2,
453867
453700
  unlinkSync: unlinkSync10
453868
453701
  } = await import("node:fs");
453869
- const { basename: basename23, join: join67 } = await import("node:path");
453702
+ const { basename: basename22, join: join67 } = await import("node:path");
453870
453703
  const lettaHome = process.env.LETTA_HOME || join67(process.env.HOME || process.env.USERPROFILE || "~", ".letta");
453871
453704
  const globalSkillsDir = join67(lettaHome, "skills");
453872
453705
  if (parsed.type === "skill_enable") {
@@ -453890,7 +453723,7 @@ async function handleSkillCommand(parsed, socket, safeSocketSend) {
453890
453723
  }, "listener_skill_send_failed", "listener_skill_command");
453891
453724
  return true;
453892
453725
  }
453893
- const linkName = basename23(parsed.skill_path);
453726
+ const linkName = basename22(parsed.skill_path);
453894
453727
  const linkPath = join67(globalSkillsDir, linkName);
453895
453728
  mkdirSync36(globalSkillsDir, { recursive: true });
453896
453729
  if (existsSync50(linkPath)) {
@@ -456169,6 +456002,65 @@ var init_chunk_log = __esm(() => {
456169
456002
  chunkLog = new ChunkLog;
456170
456003
  });
456171
456004
 
456005
+ // src/cli/helpers/stream-debug.ts
456006
+ function summarizeStreamForDebug(stream12) {
456007
+ if (!stream12 || typeof stream12 !== "object") {
456008
+ return `type=${typeof stream12}`;
456009
+ }
456010
+ const record5 = stream12;
456011
+ const ctor = stream12.constructor?.name;
456012
+ const controller = record5.controller && typeof record5.controller === "object" ? record5.controller : null;
456013
+ const keys3 = Object.keys(record5).slice(0, 8);
456014
+ return [
456015
+ `ctor=${ctor ?? "unknown"}`,
456016
+ `asyncIterator=${typeof record5[Symbol.asyncIterator]}`,
456017
+ `controller=${typeof record5.controller}`,
456018
+ `controllerAbort=${typeof controller?.abort}`,
456019
+ `controllerSignal=${typeof controller?.signal}`,
456020
+ keys3.length > 0 ? `keys=${keys3.join(",")}` : "keys=(none)"
456021
+ ].join(" ");
456022
+ }
456023
+ function summarizeChunkForDebug(chunk2) {
456024
+ if (!chunk2) {
456025
+ return "none";
456026
+ }
456027
+ const record5 = chunk2;
456028
+ const parts = [`message_type=${chunk2.message_type ?? "unknown"}`];
456029
+ for (const key2 of ["run_id", "seq_id", "id", "otid", "tool_call_id"]) {
456030
+ const value = record5[key2];
456031
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
456032
+ parts.push(`${key2}=${value}`);
456033
+ }
456034
+ }
456035
+ if (chunk2.message_type === "stop_reason") {
456036
+ parts.push(`stop_reason=${String(record5.stop_reason ?? "unknown")}`);
456037
+ }
456038
+ const toolCalls = record5.tool_calls;
456039
+ if (Array.isArray(toolCalls)) {
456040
+ parts.push(`tool_calls=${toolCalls.length}`);
456041
+ }
456042
+ return parts.join(" ");
456043
+ }
456044
+ function abortStreamController(stream12, reason) {
456045
+ const controller = stream12.controller;
456046
+ if (!controller || typeof controller !== "object") {
456047
+ debugWarn("drainStream", "stream.controller is unavailable during %s - cannot abort HTTP request (%s)", reason, summarizeStreamForDebug(stream12));
456048
+ return;
456049
+ }
456050
+ const controllerRecord = controller;
456051
+ if (controllerRecord.signal?.aborted) {
456052
+ return;
456053
+ }
456054
+ if (typeof controllerRecord.abort !== "function") {
456055
+ debugWarn("drainStream", "stream.controller.abort is unavailable during %s - cannot abort HTTP request (%s)", reason, summarizeStreamForDebug(stream12));
456056
+ return;
456057
+ }
456058
+ controllerRecord.abort();
456059
+ }
456060
+ var init_stream_debug = __esm(() => {
456061
+ init_debug();
456062
+ });
456063
+
456172
456064
  // src/cli/helpers/stream-processor.ts
456173
456065
  class StreamProcessor {
456174
456066
  seenSeqIdThreshold;
@@ -456377,6 +456269,90 @@ var init_stream_resume = __esm(() => {
456377
456269
  init_client2();
456378
456270
  });
456379
456271
 
456272
+ // src/cli/helpers/stream-stall-reconciler.ts
456273
+ function getStallReconcileMs() {
456274
+ const raw2 = process.env.LETTA_STREAM_STALL_RECONCILE_MS;
456275
+ if (raw2) {
456276
+ const parsed = Number(raw2);
456277
+ if (Number.isFinite(parsed) && parsed > 0) {
456278
+ return parsed;
456279
+ }
456280
+ }
456281
+ return DEFAULT_STREAM_STALL_RECONCILE_MS;
456282
+ }
456283
+ function createStreamStallReconciler(context3) {
456284
+ let timer = null;
456285
+ let fired = false;
456286
+ let cleared = false;
456287
+ let reconciling = false;
456288
+ const arm = () => {
456289
+ if (cleared || fired) {
456290
+ return;
456291
+ }
456292
+ if (timer) {
456293
+ clearTimeout(timer);
456294
+ }
456295
+ timer = setTimeout(onSilenceElapsed, getStallReconcileMs());
456296
+ };
456297
+ const onSilenceElapsed = () => {
456298
+ timer = null;
456299
+ if (cleared || fired || reconciling) {
456300
+ return;
456301
+ }
456302
+ if (context3.getStopReason() !== null) {
456303
+ return;
456304
+ }
456305
+ const runId = context3.getRunId();
456306
+ if (!runId) {
456307
+ arm();
456308
+ return;
456309
+ }
456310
+ reconciling = true;
456311
+ (async () => {
456312
+ let status;
456313
+ try {
456314
+ status = await context3.retrieveRunStatus(runId);
456315
+ } catch {
456316
+ status = undefined;
456317
+ }
456318
+ reconciling = false;
456319
+ if (cleared || fired || context3.getStopReason() !== null) {
456320
+ return;
456321
+ }
456322
+ if (status == null || ACTIVE_RUN_STATUSES.has(status)) {
456323
+ arm();
456324
+ return;
456325
+ }
456326
+ fired = true;
456327
+ debugWarn("drainStream", "Stall reconciler fired: run %s is %s server-side but the stream went silent before its terminal sequence - aborting HTTP read to trigger resume", runId, status);
456328
+ telemetry.trackError("stream_stall_reconciler_fired", `Stream went silent while run reached server-side status ${status}; aborted the dead read to resume`, "stream_drain", { runId });
456329
+ context3.abortHttpRead();
456330
+ })();
456331
+ };
456332
+ return {
456333
+ arm,
456334
+ clear: () => {
456335
+ cleared = true;
456336
+ if (timer) {
456337
+ clearTimeout(timer);
456338
+ timer = null;
456339
+ }
456340
+ },
456341
+ fired: () => fired
456342
+ };
456343
+ }
456344
+ var DEFAULT_STREAM_STALL_RECONCILE_MS = 60000, ACTIVE_RUN_STATUSES;
456345
+ var init_stream_stall_reconciler = __esm(() => {
456346
+ init_telemetry();
456347
+ init_debug();
456348
+ ACTIVE_RUN_STATUSES = new Set([
456349
+ "created",
456350
+ "not_started",
456351
+ "pending",
456352
+ "running"
456353
+ ]);
456354
+ });
456355
+
456380
456356
  // src/cli/helpers/stream-terminal-eof-guard.ts
456381
456357
  function getTerminalEofGraceMs() {
456382
456358
  const raw2 = process.env.LETTA_STREAM_TERMINAL_EOF_GRACE_MS;
@@ -456422,60 +456398,6 @@ var init_stream_terminal_eof_guard = __esm(() => {
456422
456398
  });
456423
456399
 
456424
456400
  // src/cli/helpers/stream.ts
456425
- function summarizeStreamForDebug(stream12) {
456426
- if (!stream12 || typeof stream12 !== "object") {
456427
- return `type=${typeof stream12}`;
456428
- }
456429
- const record5 = stream12;
456430
- const ctor = stream12.constructor?.name;
456431
- const controller = record5.controller && typeof record5.controller === "object" ? record5.controller : null;
456432
- const keys3 = Object.keys(record5).slice(0, 8);
456433
- return [
456434
- `ctor=${ctor ?? "unknown"}`,
456435
- `asyncIterator=${typeof record5[Symbol.asyncIterator]}`,
456436
- `controller=${typeof record5.controller}`,
456437
- `controllerAbort=${typeof controller?.abort}`,
456438
- `controllerSignal=${typeof controller?.signal}`,
456439
- keys3.length > 0 ? `keys=${keys3.join(",")}` : "keys=(none)"
456440
- ].join(" ");
456441
- }
456442
- function summarizeChunkForDebug(chunk2) {
456443
- if (!chunk2) {
456444
- return "none";
456445
- }
456446
- const record5 = chunk2;
456447
- const parts = [`message_type=${chunk2.message_type ?? "unknown"}`];
456448
- for (const key2 of ["run_id", "seq_id", "id", "otid", "tool_call_id"]) {
456449
- const value = record5[key2];
456450
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
456451
- parts.push(`${key2}=${value}`);
456452
- }
456453
- }
456454
- if (chunk2.message_type === "stop_reason") {
456455
- parts.push(`stop_reason=${String(record5.stop_reason ?? "unknown")}`);
456456
- }
456457
- const toolCalls = record5.tool_calls;
456458
- if (Array.isArray(toolCalls)) {
456459
- parts.push(`tool_calls=${toolCalls.length}`);
456460
- }
456461
- return parts.join(" ");
456462
- }
456463
- function abortStreamController(stream12, reason) {
456464
- const controller = stream12.controller;
456465
- if (!controller || typeof controller !== "object") {
456466
- debugWarn("drainStream", "stream.controller is unavailable during %s - cannot abort HTTP request (%s)", reason, summarizeStreamForDebug(stream12));
456467
- return;
456468
- }
456469
- const controllerRecord = controller;
456470
- if (controllerRecord.signal?.aborted) {
456471
- return;
456472
- }
456473
- if (typeof controllerRecord.abort !== "function") {
456474
- debugWarn("drainStream", "stream.controller.abort is unavailable during %s - cannot abort HTTP request (%s)", reason, summarizeStreamForDebug(stream12));
456475
- return;
456476
- }
456477
- controllerRecord.abort();
456478
- }
456479
456401
  async function drainStream(stream12, buffers, refresh, abortSignal, onFirstMessage, onChunkProcessed, contextTracker, seenSeqIdThreshold, isResumeStream, skipCancelToolsOnError) {
456480
456402
  const startTime = performance.now();
456481
456403
  const requestStartTime = getStreamRequestStartTime(stream12) ?? startTime;
@@ -456491,6 +456413,12 @@ async function drainStream(stream12, buffers, refresh, abortSignal, onFirstMessa
456491
456413
  getRunId: () => streamProcessor.lastRunId,
456492
456414
  abortHttpRead: () => abortStreamController(stream12, "terminal_eof_guard")
456493
456415
  });
456416
+ const stallReconciler = createStreamStallReconciler({
456417
+ getRunId: () => streamProcessor.lastRunId,
456418
+ getStopReason: () => streamProcessor.stopReason,
456419
+ retrieveRunStatus: async (runId) => (await getBackend().retrieveRun(runId)).status,
456420
+ abortHttpRead: () => abortStreamController(stream12, "stall_reconciler")
456421
+ });
456494
456422
  const startAbortGen = buffers.abortGeneration || 0;
456495
456423
  const abortHandler = () => {
456496
456424
  abortedViaListener = true;
@@ -456507,7 +456435,9 @@ async function drainStream(stream12, buffers, refresh, abortSignal, onFirstMessa
456507
456435
  if (typeof asyncIterator !== "function") {
456508
456436
  throw new TypeError(`Stream is not async iterable (${summarizeStreamForDebug(stream12)})`);
456509
456437
  }
456438
+ stallReconciler.arm();
456510
456439
  for await (const chunk2 of stream12) {
456440
+ stallReconciler.arm();
456511
456441
  lastChunkDebugSummary = summarizeChunkForDebug(chunk2);
456512
456442
  recordTuiJsonPayload(`stream_chunk:${chunk2.message_type ?? "unknown"}`, chunk2);
456513
456443
  if ((buffers.abortGeneration || 0) !== startAbortGen) {
@@ -456609,6 +456539,7 @@ async function drainStream(stream12, buffers, refresh, abortSignal, onFirstMessa
456609
456539
  queueMicrotask(refresh);
456610
456540
  } finally {
456611
456541
  terminalEofGuard.clear();
456542
+ stallReconciler.clear();
456612
456543
  try {
456613
456544
  chunkLog.flush();
456614
456545
  } catch {}
@@ -456626,6 +456557,11 @@ async function drainStream(stream12, buffers, refresh, abortSignal, onFirstMessa
456626
456557
  "Stream did not close after completing, continued without waiting"
456627
456558
  ]);
456628
456559
  }
456560
+ if (stallReconciler.fired()) {
456561
+ upsertStatusLine(buffers, `stall-reconcile-${startTime}`, [
456562
+ "Stream went silent after the run completed, recovering the missed tail"
456563
+ ]);
456564
+ }
456629
456565
  if (abortedViaListener && !stopReason) {
456630
456566
  stopReason = "cancelled";
456631
456567
  markIncompleteToolsAsCancelled(buffers, true, "user_interrupt");
@@ -456680,7 +456616,8 @@ async function drainStream(stream12, buffers, refresh, abortSignal, onFirstMessa
456680
456616
  lastSeqId: streamProcessor.lastSeqId,
456681
456617
  apiDurationMs,
456682
456618
  fallbackError,
456683
- terminalEofGuardFired: terminalEofGuard.fired()
456619
+ terminalEofGuardFired: terminalEofGuard.fired(),
456620
+ stallReconcilerFired: stallReconciler.fired()
456684
456621
  };
456685
456622
  }
456686
456623
  async function drainStreamWithResume(stream12, buffers, refresh, abortSignal, onFirstMessage, onChunkProcessed, contextTracker, seenSeqIdThreshold, resumePolicy) {
@@ -456872,7 +456809,9 @@ var init_stream = __esm(async () => {
456872
456809
  init_timing();
456873
456810
  init_tui_perf();
456874
456811
  init_chunk_log();
456812
+ init_stream_debug();
456875
456813
  init_stream_resume();
456814
+ init_stream_stall_reconciler();
456876
456815
  init_stream_terminal_eof_guard();
456877
456816
  await __promiseAll([
456878
456817
  init_message(),
@@ -456916,9 +456855,43 @@ async function getMissingRequiredArgs(toolName, parsedArgs, toolContextId) {
456916
456855
  const required6 = schema5?.input_schema?.required || [];
456917
456856
  return required6.filter((key2) => !(key2 in parsedArgs) || parsedArgs[key2] == null);
456918
456857
  }
456919
- function formatMissingRequiredArgsReason(toolName, parsedArgs, missingRequiredArgs) {
456858
+ function formatMissingRequiredArgsReason(toolName, parsedArgs, missingRequiredArgs, argsParse) {
456920
456859
  const received = Object.keys(parsedArgs).join(", ");
456921
- return `${toolName} tool missing required parameter${missingRequiredArgs.length > 1 ? "s" : ""}: ` + `${missingRequiredArgs.join(", ")}. Received parameters: ${received}`;
456860
+ const base3 = `${toolName} tool missing required parameter${missingRequiredArgs.length > 1 ? "s" : ""}: ` + `${missingRequiredArgs.join(", ")}. Received parameters: ${received}`;
456861
+ if (argsParse?.parseFailed) {
456862
+ return `${base3}. The raw arguments (${argsParse.rawLength} chars) were not valid JSON, ` + `so they were lost or truncated in transit. Do not resend an identical call - ` + `re-issue it with the arguments restructured (e.g. write long payloads to a file first).`;
456863
+ }
456864
+ if (argsParse?.argsEmpty) {
456865
+ return `${base3}. The tool call arrived with empty arguments, which usually means they were ` + `dropped in transit rather than omitted by you. Do not resend an identical call - ` + `re-issue it with the arguments restructured (e.g. write long payloads to a file first).`;
456866
+ }
456867
+ return base3;
456868
+ }
456869
+ function parseToolArgs(rawArgs) {
456870
+ const raw2 = rawArgs ?? "";
456871
+ const trimmed = raw2.trim();
456872
+ if (!trimmed) {
456873
+ return {
456874
+ parsedArgs: {},
456875
+ parseFailed: false,
456876
+ argsEmpty: true,
456877
+ rawLength: 0
456878
+ };
456879
+ }
456880
+ const parsed = safeJsonParseOr(trimmed, null);
456881
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
456882
+ return {
456883
+ parsedArgs: {},
456884
+ parseFailed: true,
456885
+ argsEmpty: false,
456886
+ rawLength: raw2.length
456887
+ };
456888
+ }
456889
+ return {
456890
+ parsedArgs: parsed,
456891
+ parseFailed: false,
456892
+ argsEmpty: Object.keys(parsed).length === 0,
456893
+ rawLength: raw2.length
456894
+ };
456922
456895
  }
456923
456896
  async function classifyApprovals(approvals, opts = {}) {
456924
456897
  const needsUserInput = [];
@@ -456938,11 +456911,15 @@ async function classifyApprovals(approvals, opts = {}) {
456938
456911
  });
456939
456912
  continue;
456940
456913
  }
456941
- const parsedArgs = safeJsonParseOr(approval.toolArgs || "{}", {});
456914
+ const argsParse = parseToolArgs(approval.toolArgs);
456915
+ const parsedArgs = argsParse.parsedArgs;
456916
+ if (argsParse.parseFailed) {
456917
+ debugWarn("approval-classification", `Tool call ${approval.toolCallId} (${toolName}) had unparseable arguments ` + `(${argsParse.rawLength} chars); treating as empty`);
456918
+ }
456942
456919
  if (opts.requireArgsForAutoApprove) {
456943
456920
  const missingRequiredArgs = await getMissingRequiredArgs(toolName, parsedArgs, opts.toolContextId);
456944
456921
  if (missingRequiredArgs.length > 0) {
456945
- const denyReason = opts.missingArgsReason ? opts.missingArgsReason(missingRequiredArgs) : formatMissingRequiredArgsReason(toolName, parsedArgs, missingRequiredArgs);
456922
+ const denyReason = opts.missingArgsReason ? opts.missingArgsReason(missingRequiredArgs) : formatMissingRequiredArgsReason(toolName, parsedArgs, missingRequiredArgs, argsParse);
456946
456923
  autoDenied.push({
456947
456924
  approval,
456948
456925
  permission: { decision: "deny", reason: denyReason },
@@ -456988,6 +456965,7 @@ async function classifyApprovals(approvals, opts = {}) {
456988
456965
  return { needsUserInput, autoAllowed, autoDenied };
456989
456966
  }
456990
456967
  var init_approval_classification = __esm(async () => {
456968
+ init_debug();
456991
456969
  await init_manager4();
456992
456970
  });
456993
456971
 
@@ -457833,38 +457811,6 @@ var init_approval_recovery = __esm(() => {
457833
457811
  init_turn_recovery_policy();
457834
457812
  });
457835
457813
 
457836
- // src/websocket/listener/provider-fallback.ts
457837
- function createProviderFallbackState(agent2, overrideModel) {
457838
- const llmConfig = agent2?.llm_config;
457839
- const model = llmConfig?.model;
457840
- if (!model) {
457841
- return { sourceModelId: null, attempted: false, overrideModel };
457842
- }
457843
- const modelInfo = getModelInfoForLlmConfig(model, llmConfig) ?? getModelInfo(model);
457844
- return {
457845
- sourceModelId: modelInfo?.id ?? model,
457846
- attempted: false,
457847
- overrideModel
457848
- };
457849
- }
457850
- function maybeApplyProviderFallback(state, attempt2) {
457851
- if (!state || state.attempted || attempt2 < 2 || !state.sourceModelId) {
457852
- return null;
457853
- }
457854
- const fallbackId = PROVIDER_FALLBACK_MAP[state.sourceModelId];
457855
- const fallbackHandle = fallbackId ? getModelInfo(fallbackId)?.handle : null;
457856
- if (!fallbackHandle) {
457857
- return null;
457858
- }
457859
- state.attempted = true;
457860
- state.overrideModel = fallbackHandle;
457861
- return fallbackHandle;
457862
- }
457863
- var init_provider_fallback = __esm(() => {
457864
- init_model();
457865
- init_constants3();
457866
- });
457867
-
457868
457814
  // src/websocket/listener/skill-injection.ts
457869
457815
  function injectQueuedSkillContent(messages, context3) {
457870
457816
  const skillContents = consumeQueuedSkillContent();
@@ -458158,13 +458104,12 @@ async function resolveStaleApprovals(runtime, socket, turnLease, deps = {}) {
458158
458104
  }
458159
458105
  return null;
458160
458106
  }
458161
- async function sendMessageStreamWithRetry(conversationId, messages, opts, socket, runtime, turnLease, retryOptions = {}) {
458107
+ async function sendMessageStreamWithRetry(conversationId, messages, opts, socket, runtime, turnLease) {
458162
458108
  const abortSignal = turnLease.signal;
458163
458109
  let transientRetries = 0;
458164
458110
  let conversationBusyRetries = 0;
458165
458111
  let preStreamRecoveryAttempts = 0;
458166
458112
  const MAX_CONVERSATION_BUSY_RETRIES = 3;
458167
- let currentOpts = opts;
458168
458113
  const retriedAfterBlockingRunSettled = new Set;
458169
458114
  while (true) {
458170
458115
  if (abortSignal?.aborted) {
@@ -458175,7 +458120,7 @@ async function sendMessageStreamWithRetry(conversationId, messages, opts, socket
458175
458120
  conversation_id: conversationId
458176
458121
  });
458177
458122
  try {
458178
- return await sendMessageStream(conversationId, messages, currentOpts, abortSignal ? { maxRetries: 0, signal: abortSignal } : { maxRetries: 0 });
458123
+ return await sendMessageStream(conversationId, messages, opts, abortSignal ? { maxRetries: 0, signal: abortSignal } : { maxRetries: 0 });
458179
458124
  } catch (preStreamError) {
458180
458125
  if (abortSignal?.aborted) {
458181
458126
  throw new Error("Cancelled by user");
@@ -458214,21 +458159,6 @@ async function sendMessageStreamWithRetry(conversationId, messages, opts, socket
458214
458159
  });
458215
458160
  const attempt2 = transientRetries + 1;
458216
458161
  transientRetries = attempt2;
458217
- const fallbackHandle = maybeApplyProviderFallback(retryOptions.providerFallback, attempt2);
458218
- if (fallbackHandle) {
458219
- currentOpts = { ...currentOpts, overrideModel: fallbackHandle };
458220
- emitRecoverableRetryNotice(socket, runtime, {
458221
- kind: "transient_provider_retry",
458222
- message: PROVIDER_FALLBACK_NOTICE,
458223
- reason: "llm_api_error",
458224
- attempt: attempt2,
458225
- maxAttempts: LLM_API_ERROR_MAX_RETRIES,
458226
- delayMs: 0,
458227
- agentId: runtime.agentId ?? undefined,
458228
- conversationId
458229
- });
458230
- continue;
458231
- }
458232
458162
  const retryAfterMs = preStreamError instanceof APIError ? parseRetryAfterHeaderMs(preStreamError.headers?.get("retry-after")) : null;
458233
458163
  const delayMs = getRetryDelayMs2({
458234
458164
  category: "transient_provider",
@@ -458314,7 +458244,6 @@ async function sendApprovalContinuationWithRetry(conversationId, messages, opts,
458314
458244
  let conversationBusyRetries = 0;
458315
458245
  let preStreamRecoveryAttempts = 0;
458316
458246
  const MAX_CONVERSATION_BUSY_RETRIES = 3;
458317
- let currentOpts = opts;
458318
458247
  const retriedAfterBlockingRunSettled = new Set;
458319
458248
  while (true) {
458320
458249
  if (abortSignal?.aborted) {
@@ -458325,7 +458254,7 @@ async function sendApprovalContinuationWithRetry(conversationId, messages, opts,
458325
458254
  conversation_id: conversationId
458326
458255
  });
458327
458256
  try {
458328
- const stream12 = await sendMessageStream(conversationId, messages, currentOpts, abortSignal ? { maxRetries: 0, signal: abortSignal } : { maxRetries: 0 });
458257
+ const stream12 = await sendMessageStream(conversationId, messages, opts, abortSignal ? { maxRetries: 0, signal: abortSignal } : { maxRetries: 0 });
458329
458258
  return { kind: "stream", stream: stream12 };
458330
458259
  } catch (preStreamError) {
458331
458260
  if (abortSignal?.aborted) {
@@ -458361,21 +458290,6 @@ async function sendApprovalContinuationWithRetry(conversationId, messages, opts,
458361
458290
  });
458362
458291
  const attempt2 = transientRetries + 1;
458363
458292
  transientRetries = attempt2;
458364
- const fallbackHandle = maybeApplyProviderFallback(retryOptions.providerFallback, attempt2);
458365
- if (fallbackHandle) {
458366
- currentOpts = { ...currentOpts, overrideModel: fallbackHandle };
458367
- emitRecoverableRetryNotice(socket, runtime, {
458368
- kind: "transient_provider_retry",
458369
- message: PROVIDER_FALLBACK_NOTICE,
458370
- reason: "llm_api_error",
458371
- attempt: attempt2,
458372
- maxAttempts: LLM_API_ERROR_MAX_RETRIES,
458373
- delayMs: 0,
458374
- agentId: runtime.agentId ?? undefined,
458375
- conversationId
458376
- });
458377
- continue;
458378
- }
458379
458293
  const retryAfterMs = preStreamError instanceof APIError ? parseRetryAfterHeaderMs(preStreamError.headers?.get("retry-after")) : null;
458380
458294
  const delayMs = getRetryDelayMs2({
458381
458295
  category: "transient_provider",
@@ -458468,8 +458382,6 @@ var init_send = __esm(async () => {
458468
458382
  init_cwd();
458469
458383
  init_permission_mode();
458470
458384
  init_protocol_outbound();
458471
- init_provider_fallback();
458472
- init_recoverable_notices();
458473
458385
  init_skill_injection();
458474
458386
  init_turn_input_state();
458475
458387
  init_turn_status();
@@ -459459,7 +459371,7 @@ __export(exports_diff, {
459459
459371
  ADV_DIFF_IGNORE_WHITESPACE: () => ADV_DIFF_IGNORE_WHITESPACE,
459460
459372
  ADV_DIFF_CONTEXT_LINES: () => ADV_DIFF_CONTEXT_LINES
459461
459373
  });
459462
- import { basename as basename23 } from "node:path";
459374
+ import { basename as basename22 } from "node:path";
459463
459375
  function readFileOrNull(p2) {
459464
459376
  try {
459465
459377
  return __require("node:fs").readFileSync(p2, "utf-8");
@@ -459483,7 +459395,7 @@ function applyAllOccurrences(content, oldStr, newStr) {
459483
459395
  return { ok: true, out: content.split(oldStr).join(newStr) };
459484
459396
  }
459485
459397
  function computeAdvancedDiff(input, opts) {
459486
- const fileName = basename23(input.filePath || "");
459398
+ const fileName = basename22(input.filePath || "");
459487
459399
  const fileContent = opts?.oldStrOverride !== undefined ? opts.oldStrOverride : readFileOrNull(input.filePath);
459488
459400
  if (fileContent === null && input.kind !== "write") {
459489
459401
  return { mode: "fallback", reason: "File not readable" };
@@ -459549,7 +459461,7 @@ function computeAdvancedDiff(input, opts) {
459549
459461
  return { mode: "advanced", fileName, oldStr, newStr, hunks };
459550
459462
  }
459551
459463
  function parsePatchToAdvancedDiff(patchLines, filePath) {
459552
- const fileName = basename23(filePath);
459464
+ const fileName = basename22(filePath);
459553
459465
  const hunks = [];
459554
459466
  let currentHunk = null;
459555
459467
  let oldLine = 1;
@@ -459827,8 +459739,8 @@ function isFormatterSegment(tokens) {
459827
459739
  }
459828
459740
  }
459829
459741
  function isShellExecutor2(token2) {
459830
- const basename24 = token2.split("/").pop() ?? token2;
459831
- return ["bash", "sh", "zsh", "dash", "ksh"].includes(basename24.toLowerCase());
459742
+ const basename23 = token2.split("/").pop() ?? token2;
459743
+ return ["bash", "sh", "zsh", "dash", "ksh"].includes(basename23.toLowerCase());
459832
459744
  }
459833
459745
  function normalizeRawCommand(command) {
459834
459746
  if (Array.isArray(command)) {
@@ -460609,7 +460521,7 @@ var init_format_args_display = __esm(async () => {
460609
460521
  });
460610
460522
 
460611
460523
  // src/helpers/diff-preview.ts
460612
- import path41, { basename as basename24 } from "node:path";
460524
+ import path41, { basename as basename23 } from "node:path";
460613
460525
  function parseHunkLinePrefix(raw2) {
460614
460526
  if (raw2.length === 0) {
460615
460527
  return { type: "context", content: "" };
@@ -460715,7 +460627,7 @@ async function computeDiffPreviews(toolName, toolArgs, workingDirectory = getCur
460715
460627
  filePath: resolvedFilePath,
460716
460628
  content: toolArgs.content || ""
460717
460629
  });
460718
- previews.push(toDiffPreview(result2, basename24(filePath)));
460630
+ previews.push(toDiffPreview(result2, basename23(filePath)));
460719
460631
  }
460720
460632
  } else if (isFileEditTool3(toolName)) {
460721
460633
  const filePath = toolArgs.file_path;
@@ -460727,7 +460639,7 @@ async function computeDiffPreviews(toolName, toolArgs, workingDirectory = getCur
460727
460639
  filePath: resolvedFilePath,
460728
460640
  edits: toolArgs.edits
460729
460641
  });
460730
- previews.push(toDiffPreview(result2, basename24(filePath)));
460642
+ previews.push(toDiffPreview(result2, basename23(filePath)));
460731
460643
  } else {
460732
460644
  const result2 = computeAdvancedDiff2({
460733
460645
  kind: "edit",
@@ -460736,7 +460648,7 @@ async function computeDiffPreviews(toolName, toolArgs, workingDirectory = getCur
460736
460648
  newString: toolArgs.new_string || "",
460737
460649
  replaceAll: toolArgs.replace_all
460738
460650
  });
460739
- previews.push(toDiffPreview(result2, basename24(filePath)));
460651
+ previews.push(toDiffPreview(result2, basename23(filePath)));
460740
460652
  }
460741
460653
  }
460742
460654
  } else if (isPatchTool2(toolName) && toolArgs.input) {
@@ -460745,7 +460657,7 @@ async function computeDiffPreviews(toolName, toolArgs, workingDirectory = getCur
460745
460657
  if (op.kind === "add" || op.kind === "update") {
460746
460658
  const result2 = parsePatchToAdvancedDiff2(op.patchLines, op.path);
460747
460659
  if (result2) {
460748
- previews.push(toDiffPreview(result2, basename24(op.path)));
460660
+ previews.push(toDiffPreview(result2, basename23(op.path)));
460749
460661
  }
460750
460662
  }
460751
460663
  }
@@ -460755,7 +460667,7 @@ async function computeDiffPreviews(toolName, toolArgs, workingDirectory = getCur
460755
460667
  if (op.kind === "add" || op.kind === "update") {
460756
460668
  const result2 = parsePatchToAdvancedDiff2(op.patchLines, op.path);
460757
460669
  if (result2) {
460758
- previews.push(toDiffPreview(result2, basename24(op.path)));
460670
+ previews.push(toDiffPreview(result2, basename23(op.path)));
460759
460671
  }
460760
460672
  }
460761
460673
  }
@@ -460798,7 +460710,6 @@ async function handleApprovalStop(params) {
460798
460710
  turnLease,
460799
460711
  processOwnedTurn = false,
460800
460712
  buildSendOptions,
460801
- providerFallback,
460802
460713
  dependencies: dependencies4
460803
460714
  } = params;
460804
460715
  const abortSignal = turnLease.signal;
@@ -461122,7 +461033,7 @@ async function handleApprovalStop(params) {
461122
461033
  ...continuationActingUserId ? { actingUserId: continuationActingUserId } : {},
461123
461034
  ...imageFailureModesByMessageOtid ? { imageFailureModesByMessageOtid } : {},
461124
461035
  ...continuationWasFullyAutoHandled ? { allowResponseStateReuse: true } : {}
461125
- }, socket, runtime, turnLease, { providerFallback });
461036
+ }, socket, runtime, turnLease);
461126
461037
  } catch (error54) {
461127
461038
  if (shouldInterrupt()) {
461128
461039
  return interruptTermination(nextTurnInput, continuationBatchId);
@@ -461746,11 +461657,11 @@ function createTurnInputSender(params) {
461746
461657
  return {
461747
461658
  async send(input) {
461748
461659
  if (isApprovalOnlyInput(input)) {
461749
- return sendApprovalContinuationWithRetry(params.conversationId, input, params.buildSendOptions(), params.socket, params.runtime, params.turnLease, { providerFallback: params.providerFallback });
461660
+ return sendApprovalContinuationWithRetry(params.conversationId, input, params.buildSendOptions(), params.socket, params.runtime, params.turnLease);
461750
461661
  }
461751
461662
  return {
461752
461663
  kind: "stream",
461753
- stream: await sendMessageStreamWithRetry(params.conversationId, input, params.buildSendOptions(), params.socket, params.runtime, params.turnLease, { providerFallback: params.providerFallback })
461664
+ stream: await sendMessageStreamWithRetry(params.conversationId, input, params.buildSendOptions(), params.socket, params.runtime, params.turnLease)
461754
461665
  };
461755
461666
  },
461756
461667
  accept(result2) {
@@ -463007,7 +462918,7 @@ async function handleIncomingMessageInner(msg, socket, runtime, onStatusChange,
463007
462918
  }
463008
462919
  let turnInput = setup.turnInput;
463009
462920
  const inboundUserTranscriptLines = setup.inboundUserTranscriptLines;
463010
- const providerFallback = createProviderFallbackState(setup.getCachedAgent(), setup.overrideModel);
462921
+ const overrideModel = setup.overrideModel;
463011
462922
  let pendingNormalizationInterruptedToolCallIds = setup.pendingNormalizationInterruptedToolCallIds;
463012
462923
  const preparedToolContext = setup.preparedToolContext;
463013
462924
  const buildSendOptions = () => ({
@@ -463021,7 +462932,7 @@ async function handleIncomingMessageInner(msg, socket, runtime, onStatusChange,
463021
462932
  ...turnInput.imageFailureModesByMessageOtid ? {
463022
462933
  imageFailureModesByMessageOtid: turnInput.imageFailureModesByMessageOtid
463023
462934
  } : {},
463024
- ...providerFallback.overrideModel ? { overrideModel: providerFallback.overrideModel } : {},
462935
+ ...overrideModel ? { overrideModel } : {},
463025
462936
  ...msg.actingUserId ? { actingUserId: msg.actingUserId } : {},
463026
462937
  ...pendingNormalizationInterruptedToolCallIds.length > 0 ? {
463027
462938
  approvalNormalization: {
@@ -463035,7 +462946,6 @@ async function handleIncomingMessageInner(msg, socket, runtime, onStatusChange,
463035
462946
  socket,
463036
462947
  runtime,
463037
462948
  turnLease,
463038
- providerFallback,
463039
462949
  buildSendOptions,
463040
462950
  onTerminal: noteFinalization,
463041
462951
  getTurnId: () => activeDequeuedBatchId
@@ -463278,13 +463188,12 @@ async function handleIncomingMessageInner(msg, socket, runtime, onStatusChange,
463278
463188
  if (retriable && llmApiErrorRetries < LLM_API_ERROR_MAX_RETRIES) {
463279
463189
  llmApiErrorRetries += 1;
463280
463190
  const attempt2 = llmApiErrorRetries;
463281
- const fallbackHandle = maybeApplyProviderFallback(providerFallback, attempt2);
463282
- const delayMs = fallbackHandle ? 0 : getRetryDelayMs2({
463191
+ const delayMs = getRetryDelayMs2({
463283
463192
  category: "transient_provider",
463284
463193
  attempt: attempt2,
463285
463194
  detail: errorDetail2
463286
463195
  });
463287
- const retryMessage = fallbackHandle ? PROVIDER_FALLBACK_NOTICE : getRetryStatusMessage(errorDetail2) || `LLM API error encountered, retrying (attempt ${attempt2}/${LLM_API_ERROR_MAX_RETRIES})...`;
463196
+ const retryMessage = getRetryStatusMessage(errorDetail2) || `LLM API error encountered, retrying (attempt ${attempt2}/${LLM_API_ERROR_MAX_RETRIES})...`;
463288
463197
  emitRecoverableRetryNotice(socket, runtime, {
463289
463198
  kind: "transient_provider_retry",
463290
463199
  message: retryMessage,
@@ -463296,9 +463205,7 @@ async function handleIncomingMessageInner(msg, socket, runtime, onStatusChange,
463296
463205
  agentId,
463297
463206
  conversationId
463298
463207
  });
463299
- if (!fallbackHandle) {
463300
- await new Promise((resolve35) => setTimeout(resolve35, delayMs));
463301
- }
463208
+ await new Promise((resolve35) => setTimeout(resolve35, delayMs));
463302
463209
  if (turnAbortSignal.aborted) {
463303
463210
  throw new Error("Cancelled by user");
463304
463211
  }
@@ -463381,8 +463288,7 @@ async function handleIncomingMessageInner(msg, socket, runtime, onStatusChange,
463381
463288
  turnToolContextId,
463382
463289
  turnLease,
463383
463290
  processOwnedTurn: msg.processOwnedTurn === true,
463384
- buildSendOptions,
463385
- providerFallback
463291
+ buildSendOptions
463386
463292
  });
463387
463293
  if (approvalResult.kind === "error") {
463388
463294
  const terminalRunId = runId || runtime.activeRunId;
@@ -463571,7 +463477,6 @@ var init_turn = __esm(async () => {
463571
463477
  init_interrupts();
463572
463478
  init_permission_mode();
463573
463479
  init_protocol_outbound();
463574
- init_provider_fallback();
463575
463480
  init_recoverable_notices();
463576
463481
  init_runtime();
463577
463482
  init_skill_injection();
@@ -465437,7 +465342,7 @@ __export(exports_custom, {
465437
465342
  });
465438
465343
  import { existsSync as existsSync52 } from "node:fs";
465439
465344
  import { readdir as readdir11, readFile as readFile22 } from "node:fs/promises";
465440
- import { basename as basename26, dirname as dirname30, join as join68 } from "node:path";
465345
+ import { basename as basename25, dirname as dirname30, join as join68 } from "node:path";
465441
465346
  async function getCustomCommands() {
465442
465347
  if (cachedCommands !== null) {
465443
465348
  return cachedCommands;
@@ -465497,7 +465402,7 @@ async function findCommandFiles(currentPath, rootPath, commands, source2) {
465497
465402
  async function parseCommandFile(filePath, rootPath, source2) {
465498
465403
  const content = await readFile22(filePath, "utf-8");
465499
465404
  const { frontmatter, body: body3 } = parseFrontmatter(content);
465500
- const id2 = basename26(filePath, ".md");
465405
+ const id2 = basename25(filePath, ".md");
465501
465406
  const relativePath = dirname30(filePath).slice(rootPath.length);
465502
465407
  const namespace = relativePath.replace(/^[/\\]/, "") || undefined;
465503
465408
  let description = getStringField(frontmatter, "description");
@@ -465655,7 +465560,7 @@ var init_init_command = __esm(() => {
465655
465560
 
465656
465561
  // src/cli/helpers/skill-name-frontmatter-repair.ts
465657
465562
  import { readdir as readdir12, readFile as readFile23, stat as stat15, writeFile as writeFile16 } from "node:fs/promises";
465658
- import { basename as basename27, dirname as dirname31, join as join69, relative as relative14 } from "node:path";
465563
+ import { basename as basename26, dirname as dirname31, join as join69, relative as relative14 } from "node:path";
465659
465564
  async function pathExists(path45) {
465660
465565
  try {
465661
465566
  await stat15(path45);
@@ -465762,7 +465667,7 @@ async function repairMissingSkillNameFrontmatter(memoryDir) {
465762
465667
  result2.scanned++;
465763
465668
  try {
465764
465669
  const content = await readFile23(skillFile, "utf8");
465765
- const repair3 = repairSkillNameFrontmatterContent(content, basename27(dirname31(skillFile)));
465670
+ const repair3 = repairSkillNameFrontmatterContent(content, basename26(dirname31(skillFile)));
465766
465671
  if (repair3.reason) {
465767
465672
  result2.skipped.push({ path: displayPath, reason: repair3.reason });
465768
465673
  continue;
@@ -466724,7 +466629,7 @@ var init_boot_working_directory = __esm(() => {
466724
466629
 
466725
466630
  // src/providers/chatgpt-usage-service.ts
466726
466631
  import { hostname as hostname4 } from "node:os";
466727
- function asRecord7(value) {
466632
+ function asRecord8(value) {
466728
466633
  return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
466729
466634
  }
466730
466635
  function getValue(record5, keys3) {
@@ -466738,13 +466643,13 @@ function getValue(record5, keys3) {
466738
466643
  return;
466739
466644
  }
466740
466645
  function getRecord(record5, keys3) {
466741
- return asRecord7(getValue(record5, keys3));
466646
+ return asRecord8(getValue(record5, keys3));
466742
466647
  }
466743
466648
  function getRecordArray(record5, keys3) {
466744
466649
  const value = getValue(record5, keys3);
466745
466650
  if (!Array.isArray(value))
466746
466651
  return [];
466747
- return value.map(asRecord7).filter((item) => !!item);
466652
+ return value.map(asRecord8).filter((item) => !!item);
466748
466653
  }
466749
466654
  function getNumber(record5, keys3) {
466750
466655
  const value = getValue(record5, keys3);
@@ -466787,7 +466692,7 @@ function getTimestampSeconds(record5, keys3) {
466787
466692
  return normalizeTimestampSeconds(getValue(record5, keys3));
466788
466693
  }
466789
466694
  function normalizeUsageWindow(value, label, nowMs) {
466790
- const record5 = asRecord7(value);
466695
+ const record5 = asRecord8(value);
466791
466696
  if (!record5)
466792
466697
  return null;
466793
466698
  const usedPercent = getNumber(record5, [
@@ -466889,7 +466794,7 @@ function normalizeIndividualLimit(raw2, nowMs) {
466889
466794
  };
466890
466795
  }
466891
466796
  function normalizeCloudUsageWindow(value, fallbackLabel, nowMs) {
466892
- const record5 = asRecord7(value);
466797
+ const record5 = asRecord8(value);
466893
466798
  if (!record5)
466894
466799
  return null;
466895
466800
  return normalizeUsageWindow(record5, getString(record5, ["label", "name"]) ?? fallbackLabel, nowMs);
@@ -466920,7 +466825,7 @@ function getRateLimitReachedType(raw2) {
466920
466825
  ]);
466921
466826
  if (typeof value === "string" && value.trim())
466922
466827
  return value.trim();
466923
- const record5 = asRecord7(value);
466828
+ const record5 = asRecord8(value);
466924
466829
  return getString(record5, ["type", "kind"]);
466925
466830
  }
466926
466831
  function formatPercent(value) {
@@ -467013,7 +466918,7 @@ function formatChatGPTUsageQuotaRows(snapshot, now2 = new Date) {
467013
466918
  return rows.length > 0 ? rows : [snapshot.summary.replace(/^Usage:\s*/, "")];
467014
466919
  }
467015
466920
  function normalizeWhamUsageResponse(input) {
467016
- const raw2 = asRecord7(input.raw);
466921
+ const raw2 = asRecord8(input.raw);
467017
466922
  const nowMs = input.nowMs ?? Date.now();
467018
466923
  const fetchedAt = new Date(nowMs).toISOString();
467019
466924
  const rateLimit = getRecord(raw2, ["rate_limit", "rateLimit", "rate_limits", "rateLimits"]) ?? raw2;
@@ -467051,7 +466956,7 @@ function normalizeWhamUsageResponse(input) {
467051
466956
  };
467052
466957
  }
467053
466958
  function normalizeCloudChatGPTUsageResponse(input) {
467054
- const raw2 = asRecord7(input.raw);
466959
+ const raw2 = asRecord8(input.raw);
467055
466960
  if (!raw2)
467056
466961
  return null;
467057
466962
  const nowMs = input.nowMs ?? Date.now();
@@ -467097,7 +467002,7 @@ function retryAfterMsFromBody(raw2) {
467097
467002
  }
467098
467003
  async function readJsonRecord(response) {
467099
467004
  try {
467100
- return asRecord7(await response.json()) ?? null;
467005
+ return asRecord8(await response.json()) ?? null;
467101
467006
  } catch {
467102
467007
  return null;
467103
467008
  }
@@ -470856,7 +470761,7 @@ var init_app_server_openai_common = __esm(() => {
470856
470761
  });
470857
470762
 
470858
470763
  // src/websocket/app-server-openai-tools.ts
470859
- function asRecord8(value) {
470764
+ function asRecord9(value) {
470860
470765
  return value !== null && typeof value === "object" ? value : null;
470861
470766
  }
470862
470767
  function stringValue3(value) {
@@ -470869,7 +470774,7 @@ function extractToolCallFragments(record5) {
470869
470774
  const rawToolCalls = Array.isArray(record5.tool_calls) ? record5.tool_calls : record5.tool_call ? [record5.tool_call] : [];
470870
470775
  const fragments = [];
470871
470776
  for (const rawToolCall of rawToolCalls) {
470872
- const toolCall = asRecord8(rawToolCall);
470777
+ const toolCall = asRecord9(rawToolCall);
470873
470778
  if (!toolCall) {
470874
470779
  continue;
470875
470780
  }
@@ -470914,7 +470819,7 @@ function extractToolReturns(record5) {
470914
470819
  const toolReturnsValue = record5.tool_returns;
470915
470820
  if (Array.isArray(toolReturnsValue)) {
470916
470821
  for (const raw2 of toolReturnsValue) {
470917
- const rec = asRecord8(raw2);
470822
+ const rec = asRecord9(raw2);
470918
470823
  if (!rec) {
470919
470824
  continue;
470920
470825
  }
@@ -472922,7 +472827,9 @@ async function runListenSubcommand(argv) {
472922
472827
  },
472923
472828
  onUnexpectedExit: (error54) => {
472924
472829
  console.error(`[${formatTimestamp3()}] ${error54.message}`);
472925
- exitWithTelemetry(1, "listener_channel_gateway_exited");
472830
+ if (values3.channels) {
472831
+ exitWithTelemetry(1, "listener_channel_gateway_exited");
472832
+ }
472926
472833
  },
472927
472834
  onServiceEvent: (event2) => {
472928
472835
  if (event2.kind === "protocol") {
@@ -476136,7 +476043,7 @@ var init_sandbox_files = __esm(() => {
476136
476043
 
476137
476044
  // src/cli/subcommands/sandbox.ts
476138
476045
  import { readFile as readFile25, stat as stat17, writeFile as writeFile18 } from "node:fs/promises";
476139
- import { basename as basename28, resolve as resolve36 } from "node:path";
476046
+ import { basename as basename27, resolve as resolve36 } from "node:path";
476140
476047
  import { parseArgs as parseArgs13 } from "node:util";
476141
476048
  function printUsage11() {
476142
476049
  console.log(`
@@ -476219,13 +476126,13 @@ async function runSandboxSubcommand(argv, deps = {}) {
476219
476126
  throw new Error(`${localPath2} is not a file`);
476220
476127
  const data2 = await (deps.readLocalFile ?? readFile25)(localPath2);
476221
476128
  const sandbox2 = await ensureSandbox(session.agentId, session.conversationId);
476222
- const result2 = await (deps.uploadFile ?? uploadFileToSandbox)(sandbox2.sandboxId, { blob: new Blob([data2]), name: basename28(localPath2) });
476129
+ const result2 = await (deps.uploadFile ?? uploadFileToSandbox)(sandbox2.sandboxId, { blob: new Blob([data2]), name: basename27(localPath2) });
476223
476130
  console.log(JSON.stringify(result2, null, 2));
476224
476131
  return 0;
476225
476132
  }
476226
476133
  const sandbox = await ensureSandbox(session.agentId, session.conversationId);
476227
476134
  const data = await (deps.downloadFile ?? downloadFileFromSandbox)(sandbox.sandboxId, path48);
476228
- const localPath = resolve36(parsed.values.to ?? basename28(path48));
476135
+ const localPath = resolve36(parsed.values.to ?? basename27(path48));
476229
476136
  await (deps.writeLocalFile ?? writeFile18)(localPath, data);
476230
476137
  console.log(JSON.stringify({ path: localPath, sandboxPath: path48, size: data.byteLength }, null, 2));
476231
476138
  return 0;
@@ -478635,7 +478542,7 @@ import {
478635
478542
  } from "node:fs";
478636
478543
  import { mkdir as mkdir15, readdir as readdir14 } from "node:fs/promises";
478637
478544
  import { tmpdir as tmpdir11 } from "node:os";
478638
- import { basename as basename29, dirname as dirname34, join as join75, normalize as normalize5, resolve as resolve37, sep as sep8 } from "node:path";
478545
+ import { basename as basename28, dirname as dirname34, join as join75, normalize as normalize5, resolve as resolve37, sep as sep8 } from "node:path";
478639
478546
  import { parseArgs as parseArgs16, TextDecoder as TextDecoder2, TextEncoder as TextEncoder2 } from "node:util";
478640
478547
  function printUsage14() {
478641
478548
  console.log(`
@@ -478830,7 +478737,7 @@ function parseDirectSkillFileUrlSpecifier(input) {
478830
478737
  if (url2.protocol !== "https:" && !(url2.protocol === "http:" && isLocalhostHostname(url2.hostname))) {
478831
478738
  return null;
478832
478739
  }
478833
- if (basename29(url2.pathname).toLowerCase() !== "skill.md")
478740
+ if (basename28(url2.pathname).toLowerCase() !== "skill.md")
478834
478741
  return null;
478835
478742
  return { url: url2.toString() };
478836
478743
  }
@@ -479078,7 +478985,7 @@ function getSkillName(sourceDir) {
479078
478985
  const skillMd = readFileSync40(join75(sourceDir, "SKILL.md"), "utf8");
479079
478986
  const { frontmatter } = parseFrontmatter(skillMd);
479080
478987
  const frontmatterName = frontmatter.name;
479081
- const name = typeof frontmatterName === "string" && frontmatterName.trim() ? frontmatterName : basename29(sourceDir);
478988
+ const name = typeof frontmatterName === "string" && frontmatterName.trim() ? frontmatterName : basename28(sourceDir);
479082
478989
  return sanitizeSkillName(name);
479083
478990
  }
479084
478991
  async function installSkillDirectory(params) {
@@ -479104,7 +479011,7 @@ async function installSkillDirectory(params) {
479104
479011
  await mkdir15(skillsDir, { recursive: true });
479105
479012
  cpSync2(sourceDir, targetPath, {
479106
479013
  recursive: true,
479107
- filter: (source2) => basename29(source2) !== ".git"
479014
+ filter: (source2) => basename28(source2) !== ".git"
479108
479015
  });
479109
479016
  return { name, path: normalize5(targetPath) };
479110
479017
  }
@@ -479522,6 +479429,7 @@ function printUsage15() {
479522
479429
  Usage:
479523
479430
  letta teleport list
479524
479431
  letta teleport cloud
479432
+ letta teleport local
479525
479433
  letta teleport <environment>
479526
479434
 
479527
479435
  Notes:
@@ -479531,10 +479439,10 @@ Notes:
479531
479439
  - Requires a Letta Cloud agent and a non-virtual conversation.
479532
479440
  - list: prints accessible online remote environments as JSON.
479533
479441
  - cloud: teleports to the agent's Cloud sandbox.
479442
+ - local: teleports to the one online Desktop environment. Desktop Remote
479443
+ Access must be enabled; if several are online, choose one explicitly.
479534
479444
  - <environment>: teleports to a specific remote environment by name,
479535
479445
  device-id, connection-id, or environment id.
479536
- - Desktop Local is not a teleport target yet. Use the Desktop environment
479537
- picker to switch back to Local.
479538
479446
  - Output is JSON only.
479539
479447
  `.trim());
479540
479448
  }
@@ -479599,7 +479507,7 @@ function isTeleportableRemoteEnvironment(environment2) {
479599
479507
  }
479600
479508
  function assertTeleportableRemoteEnvironment(environment2) {
479601
479509
  if (!isTeleportableRemoteEnvironment(environment2)) {
479602
- throw new Error("Desktop Local is not a teleport target yet. Use the Desktop environment picker to switch back to Local.");
479510
+ throw new Error("The Desktop-local connection is not Cloud-routable. Use `letta teleport local` to resolve its Remote Access environment.");
479603
479511
  }
479604
479512
  }
479605
479513
  async function initializeTeleportSettings() {
@@ -479637,8 +479545,12 @@ async function runTeleportSubcommand(argv, deps = {}) {
479637
479545
  conversationId: session.conversationId
479638
479546
  });
479639
479547
  targetConnectionId = result3.connectionId;
479548
+ } else if (action3 === "local") {
479549
+ const resolve38 = deps.resolveDesktopEnvironmentConnectionId ?? resolveDesktopEnvironmentConnectionId;
479550
+ const result3 = await resolve38();
479551
+ targetConnectionId = result3.connectionId;
479640
479552
  } else if (action3 === "back") {
479641
- throw new Error("Teleport back is not supported yet. Use the Desktop environment picker to switch back to Local.");
479553
+ throw new Error("Teleport back is not supported. Use `letta teleport local` or choose an explicit environment.");
479642
479554
  } else {
479643
479555
  const resolve38 = deps.resolveEnvironmentConnectionId ?? resolveEnvironmentConnectionId;
479644
479556
  const resolved = await resolve38(action3);
@@ -479762,7 +479674,7 @@ import {
479762
479674
  stat as stat19,
479763
479675
  writeFile as writeFile19
479764
479676
  } from "node:fs/promises";
479765
- import { basename as basename30, join as join77 } from "node:path";
479677
+ import { basename as basename29, join as join77 } from "node:path";
479766
479678
  function fileTimestamp(startedAt) {
479767
479679
  if (!startedAt)
479768
479680
  return "unknown-date";
@@ -479930,10 +479842,10 @@ async function runTrajectoryExport(options3) {
479930
479842
  if (!supported.includes(explicit.source)) {
479931
479843
  throw new Error(`Unknown source "${explicit.source}" in --transcript. The installed trajectory package supports: ${supported.join(", ")}.`);
479932
479844
  }
479933
- await exportTranscript(explicit.source, basename30(explicit.path).replace(/\.[^.]+$/, ""), explicit.path, () => readFile27(explicit.path, "utf-8"));
479845
+ await exportTranscript(explicit.source, basename29(explicit.path).replace(/\.[^.]+$/, ""), explicit.path, () => readFile27(explicit.path, "utf-8"));
479934
479846
  }
479935
479847
  for (const checkpoint2 of options3.deepagents ?? []) {
479936
- await exportCheckpoint(checkpoint2, `${basename30(checkpoint2.path)}-${checkpoint2.threadId}`);
479848
+ await exportCheckpoint(checkpoint2, `${basename29(checkpoint2.path)}-${checkpoint2.threadId}`);
479937
479849
  }
479938
479850
  manifest2.sessions.sort((a2, b3) => (a2.startedAt ?? "").localeCompare(b3.startedAt ?? ""));
479939
479851
  await writeFile19(join77(options3.outDir, MANIFEST_NAME), JSON.stringify(manifest2, null, 2), "utf-8");
@@ -481786,7 +481698,7 @@ class ChannelRichDraftStreamerImpl {
481786
481698
  if (this.disposed) {
481787
481699
  return;
481788
481700
  }
481789
- const record5 = asRecord9(chunk2);
481701
+ const record5 = asRecord10(chunk2);
481790
481702
  const messageType = stringValue4(record5?.message_type);
481791
481703
  if (messageType === "tool_return_message") {
481792
481704
  const toolCallId = stringValue4(record5?.tool_call_id);
@@ -482009,15 +481921,15 @@ function extractRetryAfterMs(error54) {
482009
481921
  return null;
482010
481922
  }
482011
481923
  function findRetryAfterCandidates(error54) {
482012
- const record5 = asRecord9(error54);
481924
+ const record5 = asRecord10(error54);
482013
481925
  if (!record5) {
482014
481926
  return [];
482015
481927
  }
482016
481928
  return [
482017
- asRecord9(record5.parameters)?.retry_after,
482018
- asRecord9(record5.response)?.parameters && asRecord9(asRecord9(record5.response)?.parameters)?.retry_after,
482019
- asRecord9(record5.payload)?.parameters && asRecord9(asRecord9(record5.payload)?.parameters)?.retry_after,
482020
- asRecord9(record5.error)?.parameters && asRecord9(asRecord9(record5.error)?.parameters)?.retry_after
481929
+ asRecord10(record5.parameters)?.retry_after,
481930
+ asRecord10(record5.response)?.parameters && asRecord10(asRecord10(record5.response)?.parameters)?.retry_after,
481931
+ asRecord10(record5.payload)?.parameters && asRecord10(asRecord10(record5.payload)?.parameters)?.retry_after,
481932
+ asRecord10(record5.error)?.parameters && asRecord10(asRecord10(record5.error)?.parameters)?.retry_after
482021
481933
  ];
482022
481934
  }
482023
481935
  function extractChannelSendRichDraftIntent(argumentsText, source2, options3 = {}) {
@@ -482081,7 +481993,7 @@ function extractToolCallFragments2(record5) {
482081
481993
  const rawToolCalls = Array.isArray(record5.tool_calls) ? record5.tool_calls : record5.tool_call ? [record5.tool_call] : [];
482082
481994
  const fragments = [];
482083
481995
  for (const rawToolCall of rawToolCalls) {
482084
- const toolCall = asRecord9(rawToolCall);
481996
+ const toolCall = asRecord10(rawToolCall);
482085
481997
  const toolCallId = stringValue4(toolCall?.tool_call_id);
482086
481998
  if (!toolCallId) {
482087
481999
  continue;
@@ -482206,7 +482118,7 @@ function buildDraftId(seed) {
482206
482118
  const positive = hash4 >>> 1;
482207
482119
  return positive === 0 ? 1 : positive;
482208
482120
  }
482209
- function asRecord9(value) {
482121
+ function asRecord10(value) {
482210
482122
  return value && typeof value === "object" ? value : null;
482211
482123
  }
482212
482124
  function stringValue4(value) {
@@ -483020,11 +482932,11 @@ class ChannelGateway {
483020
482932
  }
483021
482933
  async performRuntimeRegistration(state, delivery) {
483022
482934
  const tool2 = await this.hooks.buildExternalTool(delivery.runtime, delivery.sources);
483023
- const conversationTags = channelTagsForSources(delivery.sources);
482935
+ const conversationTags2 = channelTagsForSources(delivery.sources);
483024
482936
  const signature = JSON.stringify({
483025
482937
  mode: delivery.defaultPermissionMode ?? null,
483026
482938
  tool: tool2,
483027
- conversationTags
482939
+ conversationTags: conversationTags2
483028
482940
  });
483029
482941
  if (state.registrationSignature === signature && state.registration) {
483030
482942
  return state.registration;
@@ -483032,7 +482944,7 @@ class ChannelGateway {
483032
482944
  const registration = this.client.runtimeStart({
483033
482945
  agent_id: delivery.runtime.agent_id,
483034
482946
  conversation_id: delivery.runtime.conversation_id,
483035
- ...conversationTags.length > 0 ? { conversation_source_tags: conversationTags } : {},
482947
+ ...conversationTags2.length > 0 ? { conversation_source_tags: conversationTags2 } : {},
483036
482948
  ...delivery.defaultPermissionMode ? { mode: delivery.defaultPermissionMode } : {},
483037
482949
  recover_approvals: true,
483038
482950
  force_device_status: false,
@@ -488314,10 +488226,10 @@ class Protocol {
488314
488226
  });
488315
488227
  this.setRequestHandler(ListTasksRequestSchema, async (request2, extra) => {
488316
488228
  try {
488317
- const { tasks: tasks2, nextCursor } = await this._taskStore.listTasks(request2.params?.cursor, extra.sessionId);
488229
+ const { tasks: tasks2, nextCursor: nextCursor2 } = await this._taskStore.listTasks(request2.params?.cursor, extra.sessionId);
488318
488230
  return {
488319
488231
  tasks: tasks2,
488320
- nextCursor,
488232
+ nextCursor: nextCursor2,
488321
488233
  _meta: {}
488322
488234
  };
488323
488235
  } catch (error54) {
@@ -497645,7 +497557,7 @@ var init_mcp_client = __esm(() => {
497645
497557
  init_streamableHttp();
497646
497558
  DEFAULT_CLIENT_INFO = {
497647
497559
  name: "letta-code",
497648
- version: "0.30.25"
497560
+ version: "0.30.26"
497649
497561
  };
497650
497562
  });
497651
497563
 
@@ -498068,7 +497980,7 @@ var init_mcp_runtime = __esm(async () => {
498068
497980
 
498069
497981
  // src/skills/builtin/creating-skills/scripts/validate-skill.ts
498070
497982
  import { existsSync as existsSync62, readFileSync as readFileSync41 } from "node:fs";
498071
- import { basename as basename31, join as join81, resolve as resolve38 } from "node:path";
497983
+ import { basename as basename30, join as join81, resolve as resolve38 } from "node:path";
498072
497984
  import { fileURLToPath as fileURLToPath11 } from "node:url";
498073
497985
  function parseQuotedScalar(value) {
498074
497986
  if (value.startsWith('"')) {
@@ -498228,7 +498140,7 @@ function validateSkill(skillPath) {
498228
498140
  message: `Name is too long (${trimmedName.length} characters). Maximum is ${MAX_SKILL_NAME_LENGTH} characters.`
498229
498141
  };
498230
498142
  }
498231
- const dirName = basename31(skillPath);
498143
+ const dirName = basename30(skillPath);
498232
498144
  if (trimmedName !== dirName) {
498233
498145
  warnings.push(`Name '${trimmedName}' doesn't match directory name '${dirName}'. For portability, these should match.`);
498234
498146
  }
@@ -500037,8 +499949,6 @@ ${loadedContents.join(`
500037
499949
  let llmApiErrorRetries = 0;
500038
499950
  let emptyResponseRetries = 0;
500039
499951
  let conversationBusyRetries = 0;
500040
- let providerFallbackAttempted = false;
500041
- let overrideModelHandle;
500042
499952
  markMilestone("HEADLESS_FIRST_STREAM_START");
500043
499953
  measureSinceMilestone("headless-setup-total", "HEADLESS_CLIENT_READY");
500044
499954
  const checkMaxTurns = async () => {
@@ -500106,7 +500016,7 @@ ${loadedContents.join(`
500106
500016
  const turnToolContext = await prepareHeadlessToolExecutionContext({
500107
500017
  agentId: agent2.id,
500108
500018
  conversationId,
500109
- overrideModel: overrideModelHandle ?? preparedEffectiveModel,
500019
+ overrideModel: preparedEffectiveModel,
500110
500020
  cachedAgent,
500111
500021
  modContext: createHeadlessModContext({
500112
500022
  agent: agent2,
@@ -500120,7 +500030,6 @@ ${loadedContents.join(`
500120
500030
  availableTools = turnToolContext.availableTools;
500121
500031
  stream12 = await sendMessageStream(conversationId, currentInput, {
500122
500032
  agentId: agent2.id,
500123
- overrideModel: overrideModelHandle,
500124
500033
  preparedToolContext: turnToolContext.preparedToolContext.preparedToolContext
500125
500034
  }, { maxRetries: 0, signal: sigintSignal });
500126
500035
  turnToolContextId = getStreamToolContextId(stream12);
@@ -500187,26 +500096,6 @@ ${loadedContents.join(`
500187
500096
  if (preStreamAction === "retry_transient") {
500188
500097
  const attempt2 = llmApiErrorRetries + 1;
500189
500098
  llmApiErrorRetries = attempt2;
500190
- if (attempt2 >= 2 && !providerFallbackAttempted && model) {
500191
- const fallbackId = PROVIDER_FALLBACK_MAP2[model];
500192
- const fallbackHandle = fallbackId ? getModelInfo(fallbackId)?.handle : undefined;
500193
- if (fallbackHandle) {
500194
- providerFallbackAttempted = true;
500195
- overrideModelHandle = fallbackHandle;
500196
- if (outputFormat === "stream-json") {
500197
- console.log(JSON.stringify({
500198
- type: "status",
500199
- message: "Anthropic API error; falling back to Bedrock...",
500200
- session_id: sessionId,
500201
- uuid: `fallback-${randomUUID35()}`
500202
- }));
500203
- } else {
500204
- console.error("Anthropic API error; falling back to Bedrock...");
500205
- }
500206
- conversationBusyRetries = 0;
500207
- continue;
500208
- }
500209
- }
500210
500099
  const retryAfterMs2 = preStreamError instanceof APIError ? parseRetryAfterHeaderMs(preStreamError.headers?.get("retry-after")) : null;
500211
500100
  const delayMs = getRetryDelayMs2({
500212
500101
  category: "transient_provider",
@@ -500430,26 +500319,6 @@ ${loadedContents.join(`
500430
500319
  if (llmApiErrorRetries < LLM_API_ERROR_MAX_RETRIES2) {
500431
500320
  const attempt2 = llmApiErrorRetries + 1;
500432
500321
  llmApiErrorRetries = attempt2;
500433
- if (attempt2 >= 2 && !providerFallbackAttempted && model) {
500434
- const fallbackId = PROVIDER_FALLBACK_MAP2[model];
500435
- const fallbackHandle = fallbackId ? getModelInfo(fallbackId)?.handle : undefined;
500436
- if (fallbackHandle) {
500437
- providerFallbackAttempted = true;
500438
- overrideModelHandle = fallbackHandle;
500439
- if (outputFormat === "stream-json") {
500440
- console.log(JSON.stringify({
500441
- type: "status",
500442
- message: "Anthropic API error; falling back to Bedrock...",
500443
- session_id: sessionId,
500444
- uuid: `fallback-${randomUUID35()}`
500445
- }));
500446
- } else {
500447
- console.error("Anthropic API error; falling back to Bedrock...");
500448
- }
500449
- currentInput = refreshInputOtidsForNewRequest(currentInput);
500450
- continue;
500451
- }
500452
- }
500453
500322
  const delayMs = getRetryDelayMs2({
500454
500323
  category: "transient_provider",
500455
500324
  attempt: attempt2,
@@ -501806,7 +501675,7 @@ async function runBidirectionalMode(agent2, conversationId, _outputFormat, inclu
501806
501675
  setMessageQueueAdder(null);
501807
501676
  await exitBidirectional(0, "headless_bidirectional_stdin_closed");
501808
501677
  }
501809
- var LLM_API_ERROR_MAX_RETRIES2 = 3, EMPTY_RESPONSE_MAX_RETRIES2 = 2, HEADLESS_STREAM_RESUME_POLICY, PROVIDER_FALLBACK_MAP2, CONVERSATION_BUSY_MAX_RETRIES = 3, __headlessTestUtils;
501678
+ var LLM_API_ERROR_MAX_RETRIES2 = 3, EMPTY_RESPONSE_MAX_RETRIES2 = 2, HEADLESS_STREAM_RESUME_POLICY, CONVERSATION_BUSY_MAX_RETRIES = 3, __headlessTestUtils;
501810
501679
  var init_headless = __esm(async () => {
501811
501680
  init_error();
501812
501681
  init_telemetry();
@@ -501863,29 +501732,6 @@ var init_headless = __esm(async () => {
501863
501732
  maxAttempts: 20,
501864
501733
  maxDelayMs: 2000
501865
501734
  };
501866
- PROVIDER_FALLBACK_MAP2 = {
501867
- "opus-4.7-low": "bedrock-opus-4.7",
501868
- "opus-4.7-medium": "bedrock-opus-4.7",
501869
- "opus-4.7-high": "bedrock-opus-4.7",
501870
- "opus-4.7-xhigh": "bedrock-opus-4.7",
501871
- "opus-4.7-max": "bedrock-opus-4.7",
501872
- "opus-4.6-no-reasoning": "bedrock-opus-4.6",
501873
- "opus-4.6-low": "bedrock-opus-4.6",
501874
- "opus-4.6-medium": "bedrock-opus-4.6",
501875
- "opus-4.6-high": "bedrock-opus-4.6",
501876
- "opus-4.6-xhigh": "bedrock-opus-4.6",
501877
- sonnet: "bedrock-sonnet-5",
501878
- "sonnet-5-no-reasoning": "bedrock-sonnet-5",
501879
- "sonnet-5-low": "bedrock-sonnet-5",
501880
- "sonnet-5-medium": "bedrock-sonnet-5",
501881
- "sonnet-5-xhigh": "bedrock-sonnet-5",
501882
- "sonnet-4.6": "bedrock-sonnet-4.6",
501883
- "sonnet-1m": "bedrock-sonnet-4.6",
501884
- "sonnet-4.6-no-reasoning": "bedrock-sonnet-4.6",
501885
- "sonnet-4.6-low": "bedrock-sonnet-4.6",
501886
- "sonnet-4.6-medium": "bedrock-sonnet-4.6",
501887
- "sonnet-4.6-xhigh": "bedrock-sonnet-4.6"
501888
- };
501889
501735
  __headlessTestUtils = {
501890
501736
  trackTelemetryUserInputFromContent,
501891
501737
  shouldTrackTelemetryForQueuedMessage,
@@ -501915,7 +501761,7 @@ function buildConversationModelCarryoverUpdate(params) {
501915
501761
  const updateArgs = {
501916
501762
  ...modelInfo?.updateArgs ?? {}
501917
501763
  };
501918
- const reasoningEffort = carryoverLlmConfig?.reasoning_effort;
501764
+ const reasoningEffort = normalizeReasoningEffortForModel(modelHandle, carryoverLlmConfig?.reasoning_effort);
501919
501765
  if (typeof reasoningEffort === "string" && updateArgs.reasoning_effort === undefined) {
501920
501766
  updateArgs.reasoning_effort = reasoningEffort;
501921
501767
  }
@@ -501936,6 +501782,7 @@ function buildConversationModelCarryoverUpdate(params) {
501936
501782
  var init_conversation_model_carryover = __esm(() => {
501937
501783
  init_model();
501938
501784
  init_model_handles();
501785
+ init_openai_reasoning_effort();
501939
501786
  });
501940
501787
 
501941
501788
  // src/agent/reconcile-existing-agent-state.ts
@@ -502303,7 +502150,7 @@ var BYTES_PER_TOKEN = 4;
502303
502150
 
502304
502151
  // src/cli/helpers/window-title-config.ts
502305
502152
  import { homedir as homedir45 } from "node:os";
502306
- import { basename as basename32, resolve as resolve40 } from "node:path";
502153
+ import { basename as basename31, resolve as resolve40 } from "node:path";
502307
502154
  function isWindowTitleField(value) {
502308
502155
  return WINDOW_TITLE_FIELDS.includes(value);
502309
502156
  }
@@ -502462,7 +502309,7 @@ function terminalTitleProjectName(data) {
502462
502309
  if (!directory)
502463
502310
  return null;
502464
502311
  const resolved = resolve40(directory);
502465
- const name = basename32(resolved) || formatDirectoryDisplay(resolved) || resolved;
502312
+ const name = basename31(resolved) || formatDirectoryDisplay(resolved) || resolved;
502466
502313
  return truncateTerminalTitlePart(name, 24);
502467
502314
  }
502468
502315
  function titleDirectory(data) {
@@ -545518,31 +545365,8 @@ var init_AppView = __esm(async () => {
545518
545365
  });
545519
545366
 
545520
545367
  // src/cli/app/constants.ts
545521
- var CLEAR_SCREEN_AND_HOME = "\x1B[2J\x1B[H", MIN_RESIZE_DELTA = 2, RESIZE_SETTLE_MS = 250, MIN_CLEAR_INTERVAL_MS = 750, STABLE_WIDTH_SETTLE_MS = 180, TOOL_CALL_COMMIT_DEFER_MS = 50, ANIMATION_RESUME_HYSTERESIS_ROWS = 2, EAGER_CANCEL = true, LLM_API_ERROR_MAX_RETRIES3 = 3, EMPTY_RESPONSE_MAX_RETRIES3 = 2, TEMP_QUOTA_OVERRIDE_MODEL = "letta/auto", PROVIDER_FALLBACK_MAP3, CONVERSATION_BUSY_MAX_RETRIES2 = 3, INTERRUPT_MESSAGE = "Interrupted – tell the agent what to do differently. Something went wrong? Use /feedback to report issues.", ERROR_FEEDBACK_HINT = "Something went wrong? Use /feedback to report issues.", PROVIDER_STATUS_PAGES, APPROVAL_OPTIONS_HEIGHT = 8, APPROVAL_PREVIEW_BUFFER = 4, MIN_WRAP_WIDTH = 10, TEXT_WRAP_GUTTER = 6, DIFF_WRAP_GUTTER = 12, SHELL_PREVIEW_MAX_LINES = 3;
545368
+ var CLEAR_SCREEN_AND_HOME = "\x1B[2J\x1B[H", MIN_RESIZE_DELTA = 2, RESIZE_SETTLE_MS = 250, MIN_CLEAR_INTERVAL_MS = 750, STABLE_WIDTH_SETTLE_MS = 180, TOOL_CALL_COMMIT_DEFER_MS = 50, ANIMATION_RESUME_HYSTERESIS_ROWS = 2, EAGER_CANCEL = true, LLM_API_ERROR_MAX_RETRIES3 = 3, EMPTY_RESPONSE_MAX_RETRIES3 = 2, TEMP_QUOTA_OVERRIDE_MODEL = "letta/auto", CONVERSATION_BUSY_MAX_RETRIES2 = 3, INTERRUPT_MESSAGE = "Interrupted – tell the agent what to do differently. Something went wrong? Use /feedback to report issues.", ERROR_FEEDBACK_HINT = "Something went wrong? Use /feedback to report issues.", PROVIDER_STATUS_PAGES, APPROVAL_OPTIONS_HEIGHT = 8, APPROVAL_PREVIEW_BUFFER = 4, MIN_WRAP_WIDTH = 10, TEXT_WRAP_GUTTER = 6, DIFF_WRAP_GUTTER = 12, SHELL_PREVIEW_MAX_LINES = 3;
545522
545369
  var init_constants7 = __esm(() => {
545523
- PROVIDER_FALLBACK_MAP3 = {
545524
- "opus-4.7-low": "bedrock-opus-4.7",
545525
- "opus-4.7-medium": "bedrock-opus-4.7",
545526
- "opus-4.7-high": "bedrock-opus-4.7",
545527
- "opus-4.7-xhigh": "bedrock-opus-4.7",
545528
- "opus-4.7-max": "bedrock-opus-4.7",
545529
- "opus-4.6-no-reasoning": "bedrock-opus-4.6",
545530
- "opus-4.6-low": "bedrock-opus-4.6",
545531
- "opus-4.6-medium": "bedrock-opus-4.6",
545532
- "opus-4.6-high": "bedrock-opus-4.6",
545533
- "opus-4.6-xhigh": "bedrock-opus-4.6",
545534
- sonnet: "bedrock-sonnet-5",
545535
- "sonnet-5-no-reasoning": "bedrock-sonnet-5",
545536
- "sonnet-5-low": "bedrock-sonnet-5",
545537
- "sonnet-5-medium": "bedrock-sonnet-5",
545538
- "sonnet-5-xhigh": "bedrock-sonnet-5",
545539
- "sonnet-4.6": "bedrock-sonnet-4.6",
545540
- "sonnet-1m": "bedrock-sonnet-4.6",
545541
- "sonnet-4.6-no-reasoning": "bedrock-sonnet-4.6",
545542
- "sonnet-4.6-low": "bedrock-sonnet-4.6",
545543
- "sonnet-4.6-medium": "bedrock-sonnet-4.6",
545544
- "sonnet-4.6-xhigh": "bedrock-sonnet-4.6"
545545
- };
545546
545370
  PROVIDER_STATUS_PAGES = {
545547
545371
  anthropic: {
545548
545372
  name: "Anthropic",
@@ -545675,13 +545499,11 @@ function getErrorHintForStopReason(stopReason, currentModelId, modelEndpointType
545675
545499
  }
545676
545500
  const isAutoModel = currentModelId?.startsWith("auto") ?? false;
545677
545501
  const statusInfo = modelEndpointType && !isAutoModel ? PROVIDER_STATUS_PAGES[modelEndpointType] : undefined;
545678
- const bedrockOpusSuggestion = modelEndpointType === "anthropic" && currentModelId?.startsWith("opus-4.7") && getModelInfo("bedrock-opus-4.7") ? "Opus 4.7 via Amazon Bedrock" : modelEndpointType === "anthropic" && currentModelId?.startsWith("opus-4.6") && getModelInfo("bedrock-opus-4.6") ? "Opus 4.6 via Amazon Bedrock" : null;
545679
- const modelSwapSuffix = bedrockOpusSuggestion ? ` (e.g. ${bedrockOpusSuggestion})` : "";
545680
545502
  if (statusInfo) {
545681
545503
  return [
545682
545504
  `Downstream provider (${statusInfo.name}) is experiencing errors — check ${statusInfo.url} for additional information`,
545683
545505
  `(note that the official status page may not be reliable / up-to-date).`,
545684
- `Use /model to swap to a model from a different provider${modelSwapSuffix}, or try again later.`
545506
+ `Use /model to swap to a model from a different provider, or try again later.`
545685
545507
  ].join(" ");
545686
545508
  }
545687
545509
  return `Downstream provider is experiencing errors. Use /model to swap to a model from a different provider, or try again later.`;
@@ -547742,7 +547564,6 @@ function useConversationLoop(ctx) {
547742
547564
  precomputedDiffsRef,
547743
547565
  prepareScopedToolExecutionContext,
547744
547566
  processingConversationRef,
547745
- providerFallbackAttemptedRef,
547746
547567
  queueApprovalResults,
547747
547568
  queueSnapshotRef,
547748
547569
  quotaAutoSwapAttemptedRef,
@@ -547946,7 +547767,6 @@ function useConversationLoop(ctx) {
547946
547767
  emptyResponseRetriesRef.current = 0;
547947
547768
  conversationBusyRetriesRef.current = 0;
547948
547769
  quotaAutoSwapAttemptedRef.current = false;
547949
- providerFallbackAttemptedRef.current = false;
547950
547770
  }
547951
547771
  let currentRunId;
547952
547772
  let preserveTranscriptStartForApproval = false;
@@ -548149,26 +547969,6 @@ function useConversationLoop(ctx) {
548149
547969
  if (preStreamAction === "retry_transient") {
548150
547970
  llmApiErrorRetriesRef.current += 1;
548151
547971
  const attempt2 = llmApiErrorRetriesRef.current;
548152
- if (attempt2 >= 2 && !providerFallbackAttemptedRef.current && currentModelId) {
548153
- const fallbackId = PROVIDER_FALLBACK_MAP3[currentModelId];
548154
- const fallbackHandle = fallbackId ? getModelInfo(fallbackId)?.handle : undefined;
548155
- if (fallbackHandle) {
548156
- providerFallbackAttemptedRef.current = true;
548157
- setTempModelOverride(fallbackHandle);
548158
- const statusId = uid("status");
548159
- buffersRef.current.byId.set(statusId, {
548160
- kind: "status",
548161
- id: statusId,
548162
- lines: ["Anthropic API error; falling back to Bedrock..."]
548163
- });
548164
- buffersRef.current.order.push(statusId);
548165
- refreshDerived();
548166
- buffersRef.current.interrupted = false;
548167
- conversationBusyRetriesRef.current = 0;
548168
- restorePinnedPermissionMode();
548169
- continue;
548170
- }
548171
- }
548172
547972
  const retryAfterMs2 = preStreamError instanceof APIError ? parseRetryAfterHeaderMs(preStreamError.headers?.get("retry-after")) : null;
548173
547973
  const delayMs = getRetryDelayMs2({
548174
547974
  category: "transient_provider",
@@ -548376,7 +548176,6 @@ function useConversationLoop(ctx) {
548376
548176
  llmApiErrorRetriesRef.current = 0;
548377
548177
  emptyResponseRetriesRef.current = 0;
548378
548178
  conversationBusyRetriesRef.current = 0;
548379
- providerFallbackAttemptedRef.current = false;
548380
548179
  lastDequeuedMessageRef.current = null;
548381
548180
  lastSentInputRef.current = null;
548382
548181
  pendingInterruptRecoveryConversationIdRef.current = null;
@@ -549004,26 +548803,6 @@ ${feedback}
549004
548803
  if (retriable && llmApiErrorRetriesRef.current < LLM_API_ERROR_MAX_RETRIES3) {
549005
548804
  llmApiErrorRetriesRef.current += 1;
549006
548805
  const attempt2 = llmApiErrorRetriesRef.current;
549007
- if (attempt2 >= 2 && !providerFallbackAttemptedRef.current && currentModelId) {
549008
- const fallbackId = PROVIDER_FALLBACK_MAP3[currentModelId];
549009
- const fallbackHandle = fallbackId ? getModelInfo(fallbackId)?.handle : undefined;
549010
- if (fallbackHandle) {
549011
- providerFallbackAttemptedRef.current = true;
549012
- setTempModelOverride(fallbackHandle);
549013
- const statusId = uid("status");
549014
- buffersRef.current.byId.set(statusId, {
549015
- kind: "status",
549016
- id: statusId,
549017
- lines: ["Anthropic API error; falling back to Bedrock..."]
549018
- });
549019
- buffersRef.current.order.push(statusId);
549020
- refreshDerived();
549021
- currentInput = refreshInputOtidsForNewRequest(currentInput);
549022
- highestSeqIdSeen = null;
549023
- buffersRef.current.interrupted = false;
549024
- continue;
549025
- }
549026
- }
549027
548806
  const delayMs = getRetryDelayMs2({
549028
548807
  category: "transient_provider",
549029
548808
  attempt: attempt2,
@@ -554520,7 +554299,7 @@ __export(exports_worktree_diff_list, {
554520
554299
  listWorktreeDiffOptions: () => listWorktreeDiffOptions
554521
554300
  });
554522
554301
  import { execFile as execFileCb9 } from "node:child_process";
554523
- import { basename as basename33 } from "node:path";
554302
+ import { basename as basename32 } from "node:path";
554524
554303
  import { promisify as promisify19 } from "node:util";
554525
554304
  async function runGit9(cwd2, args) {
554526
554305
  try {
@@ -554569,7 +554348,7 @@ function parseWorktreeList(output, currentPath) {
554569
554348
  if (current?.path) {
554570
554349
  worktrees.push({
554571
554350
  path: current.path,
554572
- name: basename33(current.path),
554351
+ name: basename32(current.path),
554573
554352
  branch: current.branch ?? "detached",
554574
554353
  head: current.head ?? "",
554575
554354
  isCurrent: current.path === currentPath,
@@ -554595,7 +554374,7 @@ function parseWorktreeList(output, currentPath) {
554595
554374
  if (current?.path) {
554596
554375
  worktrees.push({
554597
554376
  path: current.path,
554598
- name: basename33(current.path),
554377
+ name: basename32(current.path),
554599
554378
  branch: current.branch ?? "detached",
554600
554379
  head: current.head ?? "",
554601
554380
  isCurrent: current.path === currentPath,
@@ -558077,7 +557856,6 @@ function App2({
558077
557856
  const userCancelledRef = import_react121.useRef(false);
558078
557857
  const llmApiErrorRetriesRef = import_react121.useRef(0);
558079
557858
  const quotaAutoSwapAttemptedRef = import_react121.useRef(false);
558080
- const providerFallbackAttemptedRef = import_react121.useRef(false);
558081
557859
  const emptyResponseRetriesRef = import_react121.useRef(0);
558082
557860
  const conversationBusyRetriesRef = import_react121.useRef(0);
558083
557861
  const [queueDisplay, setQueueDisplay] = import_react121.useState([]);
@@ -559596,7 +559374,6 @@ Memory may be stale. Try running: git -C ${getScopedMemoryFilesystemRoot(agentId
559596
559374
  precomputedDiffsRef,
559597
559375
  prepareScopedToolExecutionContext,
559598
559376
  processingConversationRef,
559599
- providerFallbackAttemptedRef,
559600
559377
  queueApprovalResults,
559601
559378
  queueSnapshotRef,
559602
559379
  quotaAutoSwapAttemptedRef,
@@ -560991,7 +560768,7 @@ SUBCOMMANDS
560991
560768
  letta agents list [--query <text> | --name <name> | --tags <tags>]
560992
560769
  letta environments list [--online-only]
560993
560770
  letta environments current
560994
- letta teleport list|cloud|<environment>
560771
+ letta teleport list|cloud|local|<environment>
560995
560772
  letta messages search --query <text> [--all-agents]
560996
560773
  letta messages list [--agent <id>]
560997
560774
  letta messages transcript --conversation <id> [--out <path>]
@@ -564679,4 +564456,4 @@ function registerBunOAuthFlows() {
564679
564456
  registerBunOAuthFlows();
564680
564457
  await init_src5().then(() => exports_src2);
564681
564458
 
564682
- //# debugId=32AC4320176955AF64756E2164756E21
564459
+ //# debugId=BBC1C9C009AB28B764756E2164756E21