@letta-ai/letta-code 0.23.5 → 0.23.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/letta.js CHANGED
@@ -3269,7 +3269,7 @@ var package_default;
3269
3269
  var init_package = __esm(() => {
3270
3270
  package_default = {
3271
3271
  name: "@letta-ai/letta-code",
3272
- version: "0.23.5",
3272
+ version: "0.23.7",
3273
3273
  description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
3274
3274
  type: "module",
3275
3275
  bin: {
@@ -8391,7 +8391,7 @@ var init_models2 = __esm(() => {
8391
8391
  }
8392
8392
  },
8393
8393
  {
8394
- id: "opus",
8394
+ id: "opus-4.6-high",
8395
8395
  handle: "anthropic/claude-opus-4-6",
8396
8396
  label: "Opus 4.6",
8397
8397
  description: "Anthropic's (legacy) best model (high reasoning)",
@@ -8459,7 +8459,7 @@ var init_models2 = __esm(() => {
8459
8459
  }
8460
8460
  },
8461
8461
  {
8462
- id: "opus-4.7",
8462
+ id: "opus",
8463
8463
  handle: "anthropic/claude-opus-4-7",
8464
8464
  label: "Opus 4.7",
8465
8465
  description: "Anthropic's best model (med reasoning)",
@@ -8487,20 +8487,6 @@ var init_models2 = __esm(() => {
8487
8487
  parallel_tool_calls: true
8488
8488
  }
8489
8489
  },
8490
- {
8491
- id: "opus-4.7-medium",
8492
- handle: "anthropic/claude-opus-4-7",
8493
- label: "Opus 4.7",
8494
- description: "Opus 4.7 (med reasoning)",
8495
- updateArgs: {
8496
- context_window: 200000,
8497
- max_output_tokens: 128000,
8498
- reasoning_effort: "medium",
8499
- enable_reasoner: true,
8500
- max_reasoning_tokens: 12000,
8501
- parallel_tool_calls: true
8502
- }
8503
- },
8504
8490
  {
8505
8491
  id: "opus-4.7-high",
8506
8492
  handle: "anthropic/claude-opus-4-7",
@@ -10261,6 +10247,11 @@ function getModelInfoForLlmConfig(modelHandle, llmConfig) {
10261
10247
  const match = candidates.find((m) => m.updateArgs?.reasoning_effort === effort);
10262
10248
  if (match)
10263
10249
  return match;
10250
+ if (effort === "max") {
10251
+ const legacyXHighMatch = candidates.find((m) => m.updateArgs?.reasoning_effort === "xhigh");
10252
+ if (legacyXHighMatch)
10253
+ return legacyXHighMatch;
10254
+ }
10264
10255
  }
10265
10256
  if (llmConfig?.enable_reasoner === false) {
10266
10257
  const match = candidates.find((m) => m.updateArgs?.enable_reasoner === false);
@@ -10377,7 +10368,8 @@ var init_model = __esm(() => {
10377
10368
  "low",
10378
10369
  "medium",
10379
10370
  "high",
10380
- "xhigh"
10371
+ "xhigh",
10372
+ "max"
10381
10373
  ];
10382
10374
  RESUME_REFRESH_FIELDS = [
10383
10375
  "max_output_tokens",
@@ -37858,6 +37850,11 @@ function getModelInfoForLlmConfig2(modelHandle, llmConfig) {
37858
37850
  const match = candidates.find((m) => m.updateArgs?.reasoning_effort === effort);
37859
37851
  if (match)
37860
37852
  return match;
37853
+ if (effort === "max") {
37854
+ const legacyXHighMatch = candidates.find((m) => m.updateArgs?.reasoning_effort === "xhigh");
37855
+ if (legacyXHighMatch)
37856
+ return legacyXHighMatch;
37857
+ }
37861
37858
  }
37862
37859
  if (llmConfig?.enable_reasoner === false) {
37863
37860
  const match = candidates.find((m) => m.updateArgs?.enable_reasoner === false);
@@ -37974,7 +37971,8 @@ var init_model2 = __esm(() => {
37974
37971
  "low",
37975
37972
  "medium",
37976
37973
  "high",
37977
- "xhigh"
37974
+ "xhigh",
37975
+ "max"
37978
37976
  ];
37979
37977
  RESUME_REFRESH_FIELDS2 = [
37980
37978
  "max_output_tokens",
@@ -38252,6 +38250,9 @@ __export(exports_modify, {
38252
38250
  updateAgentLLMConfig: () => updateAgentLLMConfig2,
38253
38251
  recompileAgentSystemPrompt: () => recompileAgentSystemPrompt
38254
38252
  });
38253
+ function supportsDistinctAnthropicXHighEffort2(modelHandle) {
38254
+ return modelHandle.includes("claude-opus-4-7");
38255
+ }
38255
38256
  function buildModelSettings2(modelHandle, updateArgs) {
38256
38257
  const isOpenAI = modelHandle.startsWith("openai/") || modelHandle.startsWith(`${OPENAI_CODEX_PROVIDER_NAME}/`);
38257
38258
  const isAnthropic = modelHandle.startsWith("anthropic/") || modelHandle.startsWith("claude-pro-max/") || modelHandle.startsWith("minimax/");
@@ -38285,10 +38286,13 @@ function buildModelSettings2(modelHandle, updateArgs) {
38285
38286
  parallel_tool_calls: true
38286
38287
  };
38287
38288
  const effort = updateArgs?.reasoning_effort;
38289
+ const hasDistinctXHigh = supportsDistinctAnthropicXHighEffort2(modelHandle);
38288
38290
  if (effort === "low" || effort === "medium" || effort === "high") {
38289
38291
  anthropicSettings.effort = effort;
38290
38292
  } else if (effort === "xhigh") {
38291
- anthropicSettings.effort = "max";
38293
+ anthropicSettings.effort = hasDistinctXHigh ? "xhigh" : "max";
38294
+ } else if (effort === "max") {
38295
+ anthropicSettings.effort = effort;
38292
38296
  }
38293
38297
  if (updateArgs?.enable_reasoner !== undefined || typeof updateArgs?.max_reasoning_tokens === "number") {
38294
38298
  anthropicSettings.thinking = {
@@ -38341,10 +38345,13 @@ function buildModelSettings2(modelHandle, updateArgs) {
38341
38345
  parallel_tool_calls: true
38342
38346
  };
38343
38347
  const effort = updateArgs?.reasoning_effort;
38348
+ const hasDistinctXHigh = supportsDistinctAnthropicXHighEffort2(modelHandle);
38344
38349
  if (effort === "low" || effort === "medium" || effort === "high") {
38345
38350
  bedrockSettings.effort = effort;
38346
38351
  } else if (effort === "xhigh") {
38347
- bedrockSettings.effort = "max";
38352
+ bedrockSettings.effort = hasDistinctXHigh ? "xhigh" : "max";
38353
+ } else if (effort === "max") {
38354
+ bedrockSettings.effort = effort;
38348
38355
  }
38349
38356
  if (updateArgs?.enable_reasoner !== undefined || typeof updateArgs?.max_reasoning_tokens === "number") {
38350
38357
  bedrockSettings.thinking = {
@@ -39802,6 +39809,9 @@ function normalizeLoadedAccount(account) {
39802
39809
  if (next.channel === "telegram" && (next.displayName === "Telegram bot" || next.displayName === "Migrated Telegram bot") || next.channel === "slack" && (next.displayName === "Slack app" || next.displayName === "Migrated Slack app")) {
39803
39810
  next.displayName = undefined;
39804
39811
  }
39812
+ if (next.channel === "slack") {
39813
+ next.defaultPermissionMode = next.defaultPermissionMode ?? "default";
39814
+ }
39805
39815
  return next;
39806
39816
  }
39807
39817
  function makeDefaultLegacyAccount(channelId) {
@@ -39836,6 +39846,7 @@ function makeDefaultLegacyAccount(channelId) {
39836
39846
  dmPolicy: config.dmPolicy,
39837
39847
  allowedUsers: [...config.allowedUsers],
39838
39848
  agentId: null,
39849
+ defaultPermissionMode: "default",
39839
39850
  createdAt: now,
39840
39851
  updatedAt: now
39841
39852
  };
@@ -40127,6 +40138,357 @@ var init_types = __esm(() => {
40127
40138
  SUPPORTED_CHANNEL_IDS = ["telegram", "slack"];
40128
40139
  });
40129
40140
 
40141
+ // src/channels/interactive.ts
40142
+ function normalizeWhitespace(text) {
40143
+ return text.replace(/\s+/g, " ").trim();
40144
+ }
40145
+ function isAffirmativeResponse(text) {
40146
+ const normalized = normalizeWhitespace(text).toLowerCase();
40147
+ return [
40148
+ "approve",
40149
+ "approved",
40150
+ "allow",
40151
+ "yes",
40152
+ "y",
40153
+ "ok",
40154
+ "okay",
40155
+ "continue",
40156
+ "go ahead",
40157
+ "looks good",
40158
+ "lgtm",
40159
+ "sgtm",
40160
+ "ship it"
40161
+ ].includes(normalized);
40162
+ }
40163
+ function isNegativeResponse(text) {
40164
+ const normalized = normalizeWhitespace(text).toLowerCase();
40165
+ return [
40166
+ "deny",
40167
+ "denied",
40168
+ "reject",
40169
+ "rejected",
40170
+ "no",
40171
+ "n",
40172
+ "cancel",
40173
+ "skip",
40174
+ "keep planning"
40175
+ ].includes(normalized);
40176
+ }
40177
+ function stripApprovalPrefix(text) {
40178
+ return normalizeWhitespace(text.replace(/^(approve|allow|yes|y|ok|okay|deny|reject|no|n)\s*[:-]?\s*/i, ""));
40179
+ }
40180
+ function summarizeControlRequestInput(input) {
40181
+ const serialized = JSON.stringify(input, null, 2);
40182
+ if (!serialized || serialized === "{}") {
40183
+ return null;
40184
+ }
40185
+ if (serialized.length <= 1200) {
40186
+ return serialized;
40187
+ }
40188
+ return `${serialized.slice(0, 1197).trimEnd()}...`;
40189
+ }
40190
+ function summarizePlanPreview(planContent) {
40191
+ const normalized = planContent.trim();
40192
+ if (!normalized) {
40193
+ return "";
40194
+ }
40195
+ const maxLength = 1800;
40196
+ if (normalized.length <= maxLength) {
40197
+ return normalized;
40198
+ }
40199
+ return `${normalized.slice(0, maxLength).trimEnd()}
40200
+
40201
+ [Plan preview truncated for channel delivery.]`;
40202
+ }
40203
+ function buildQuestionPrompt(question, index) {
40204
+ const lines = [
40205
+ `${index + 1}. ${question.question ?? `Question ${index + 1}`}`
40206
+ ];
40207
+ const options = question.options ?? [];
40208
+ options.forEach((option, optionIndex) => {
40209
+ const label = option.label?.trim() || `Option ${optionIndex + 1}`;
40210
+ const description = option.description?.trim();
40211
+ lines.push(description ? ` ${optionIndex + 1}) ${label} — ${description}` : ` ${optionIndex + 1}) ${label}`);
40212
+ });
40213
+ if (question.multiSelect) {
40214
+ lines.push(" Choose one or more options. Separate multiple answers with commas.");
40215
+ }
40216
+ return lines;
40217
+ }
40218
+ function matchQuestionOption(question, text) {
40219
+ const trimmed = normalizeWhitespace(text);
40220
+ const options = question.options ?? [];
40221
+ if (!trimmed || options.length === 0) {
40222
+ return trimmed;
40223
+ }
40224
+ const numberMatch = trimmed.match(/^(\d+)$/);
40225
+ if (numberMatch?.[1]) {
40226
+ const option = options[Number(numberMatch[1]) - 1];
40227
+ if (option?.label?.trim()) {
40228
+ return option.label.trim();
40229
+ }
40230
+ }
40231
+ const exactLabel = options.find((option) => option.label && normalizeWhitespace(option.label).toLowerCase() === trimmed.toLowerCase());
40232
+ if (exactLabel?.label?.trim()) {
40233
+ return exactLabel.label.trim();
40234
+ }
40235
+ return trimmed;
40236
+ }
40237
+ function matchQuestionAnswer(question, text) {
40238
+ if (!question.multiSelect) {
40239
+ return matchQuestionOption(question, text);
40240
+ }
40241
+ const normalized = normalizeWhitespace(text);
40242
+ if (!normalized) {
40243
+ return normalized;
40244
+ }
40245
+ const selections = normalized.replace(/\band\b/gi, ",").split(/\s*(?:,|\/|;)\s*/).map((entry) => normalizeWhitespace(entry)).filter(Boolean);
40246
+ if (selections.length <= 1) {
40247
+ return matchQuestionOption(question, normalized);
40248
+ }
40249
+ const matchedSelections = Array.from(new Set(selections.map((selection) => matchQuestionOption(question, selection)).filter(Boolean)));
40250
+ return matchedSelections.length > 0 ? matchedSelections.join(", ") : normalized;
40251
+ }
40252
+ function parseNumberedAnswers(rawText, questions) {
40253
+ const matches = Array.from(rawText.matchAll(/(?:^|\n)\s*(\d+)[).:-]\s*(.+?)(?=(?:\n\s*\d+[).:-]\s*)|$)/gs));
40254
+ if (matches.length === 0) {
40255
+ return null;
40256
+ }
40257
+ const answers = {};
40258
+ for (const match of matches) {
40259
+ const questionIndex = Number(match[1]) - 1;
40260
+ const question = questions[questionIndex];
40261
+ const answerText = match[2]?.trim();
40262
+ if (!question?.question || !answerText) {
40263
+ continue;
40264
+ }
40265
+ answers[question.question] = matchQuestionAnswer(question, answerText);
40266
+ }
40267
+ return Object.keys(answers).length > 0 ? answers : null;
40268
+ }
40269
+ function buildAllowResponse(requestId, decision) {
40270
+ return {
40271
+ request_id: requestId,
40272
+ decision
40273
+ };
40274
+ }
40275
+ function buildDenyResponse(requestId, message) {
40276
+ return {
40277
+ request_id: requestId,
40278
+ decision: {
40279
+ behavior: "deny",
40280
+ message
40281
+ }
40282
+ };
40283
+ }
40284
+ function getAskUserQuestionInput(input) {
40285
+ return input;
40286
+ }
40287
+ function formatAskUserQuestionPrompt(event) {
40288
+ const input = getAskUserQuestionInput(event.input);
40289
+ const questions = (input.questions ?? []).filter((question) => normalizeWhitespace(question.question ?? ""));
40290
+ const lines = [
40291
+ "The agent needs an answer before it can continue.",
40292
+ "",
40293
+ ...questions.flatMap((question, index) => buildQuestionPrompt(question, index)),
40294
+ ""
40295
+ ];
40296
+ if (questions.length <= 1) {
40297
+ const singleQuestion = questions[0];
40298
+ lines.push(singleQuestion?.multiSelect ? "Reply with one or more option numbers/labels separated by commas, or just send a freeform answer in your next message." : "Reply with an option number/label, or just send a freeform answer in your next message.");
40299
+ } else {
40300
+ lines.push("Reply with numbered lines, for example:", "1: your answer", "2: your answer", "", "You can also use option numbers or option labels. For multi-select questions, separate multiple answers with commas.");
40301
+ }
40302
+ return lines.join(`
40303
+ `);
40304
+ }
40305
+ function formatEnterPlanModePrompt() {
40306
+ return [
40307
+ "The agent wants to enter plan mode before making changes.",
40308
+ "",
40309
+ "Reply `approve` to let it plan first, or reply `deny` to skip planning and continue normally."
40310
+ ].join(`
40311
+ `);
40312
+ }
40313
+ function formatExitPlanModePrompt(event) {
40314
+ const lines = [
40315
+ "The agent is ready to leave plan mode and start implementing."
40316
+ ];
40317
+ if (event.planContent?.trim()) {
40318
+ lines.push("", "Proposed plan:", summarizePlanPreview(event.planContent));
40319
+ if (event.planFilePath?.trim()) {
40320
+ lines.push("", `Plan file: ${event.planFilePath.trim()}`);
40321
+ }
40322
+ }
40323
+ lines.push("", "Reply `approve` to accept the plan and start coding.", "Reply with feedback instead if you want the agent to keep planning.");
40324
+ return lines.join(`
40325
+ `);
40326
+ }
40327
+ function formatGenericToolApprovalPrompt(event) {
40328
+ const inputSummary = summarizeControlRequestInput(event.input);
40329
+ const lines = [`The agent wants approval to run \`${event.toolName}\`.`];
40330
+ if (inputSummary) {
40331
+ lines.push("", "Tool input:", inputSummary);
40332
+ }
40333
+ lines.push("", "Reply `approve` to allow it.", "Reply with feedback instead if you want to deny it.");
40334
+ return lines.join(`
40335
+ `);
40336
+ }
40337
+ function formatChannelControlRequestPrompt(event) {
40338
+ switch (event.kind) {
40339
+ case "ask_user_question":
40340
+ return formatAskUserQuestionPrompt(event);
40341
+ case "enter_plan_mode":
40342
+ return formatEnterPlanModePrompt();
40343
+ case "exit_plan_mode":
40344
+ return formatExitPlanModePrompt(event);
40345
+ case "generic_tool_approval":
40346
+ return formatGenericToolApprovalPrompt(event);
40347
+ default: {
40348
+ const exhaustiveCheck = event.kind;
40349
+ return exhaustiveCheck;
40350
+ }
40351
+ }
40352
+ }
40353
+ function parseAskUserQuestionResponse(event, rawText) {
40354
+ const input = getAskUserQuestionInput(event.input);
40355
+ const questions = (input.questions ?? []).filter((question) => normalizeWhitespace(question.question ?? ""));
40356
+ if (questions.length === 0) {
40357
+ return {
40358
+ type: "reprompt",
40359
+ message: "I couldn't find the original question payload. Please ask the agent to try again."
40360
+ };
40361
+ }
40362
+ if (questions.length === 1) {
40363
+ const [question] = questions;
40364
+ if (!question?.question) {
40365
+ return {
40366
+ type: "reprompt",
40367
+ message: "I couldn't find the original question text. Please ask the agent to try again."
40368
+ };
40369
+ }
40370
+ const answer = matchQuestionAnswer(question, rawText);
40371
+ return {
40372
+ type: "response",
40373
+ response: buildAllowResponse(event.requestId, {
40374
+ behavior: "allow",
40375
+ updated_input: {
40376
+ ...event.input,
40377
+ answers: {
40378
+ ...input.answers ?? {},
40379
+ [question.question]: answer
40380
+ }
40381
+ }
40382
+ })
40383
+ };
40384
+ }
40385
+ const numberedAnswers = parseNumberedAnswers(rawText, questions);
40386
+ if (!numberedAnswers) {
40387
+ return {
40388
+ type: "reprompt",
40389
+ message: `Please answer with numbered lines so I can map each reply to the right question.
40390
+ Example:
40391
+ 1: your answer
40392
+ 2: your answer`
40393
+ };
40394
+ }
40395
+ const missingQuestions = questions.filter((question) => question.question && !Object.hasOwn(numberedAnswers, question.question));
40396
+ if (missingQuestions.length > 0) {
40397
+ return {
40398
+ type: "reprompt",
40399
+ message: `I still need answers for: ${missingQuestions.map((question) => question.question).join(", ")}`
40400
+ };
40401
+ }
40402
+ return {
40403
+ type: "response",
40404
+ response: buildAllowResponse(event.requestId, {
40405
+ behavior: "allow",
40406
+ updated_input: {
40407
+ ...event.input,
40408
+ answers: {
40409
+ ...input.answers ?? {},
40410
+ ...numberedAnswers
40411
+ }
40412
+ }
40413
+ })
40414
+ };
40415
+ }
40416
+ function parseEnterPlanModeResponse(event, rawText) {
40417
+ if (isAffirmativeResponse(rawText)) {
40418
+ return {
40419
+ type: "response",
40420
+ response: buildAllowResponse(event.requestId, {
40421
+ behavior: "allow"
40422
+ })
40423
+ };
40424
+ }
40425
+ if (isNegativeResponse(rawText)) {
40426
+ return {
40427
+ type: "response",
40428
+ response: buildDenyResponse(event.requestId, "User chose to skip plan mode and continue implementing directly.")
40429
+ };
40430
+ }
40431
+ return {
40432
+ type: "reprompt",
40433
+ message: "Reply `approve` to let the agent enter plan mode, or `deny` to skip planning."
40434
+ };
40435
+ }
40436
+ function parseExitPlanModeResponse(event, rawText) {
40437
+ if (isAffirmativeResponse(rawText)) {
40438
+ return {
40439
+ type: "response",
40440
+ response: buildAllowResponse(event.requestId, {
40441
+ behavior: "allow"
40442
+ })
40443
+ };
40444
+ }
40445
+ const feedback = stripApprovalPrefix(rawText);
40446
+ return {
40447
+ type: "response",
40448
+ response: buildDenyResponse(event.requestId, feedback || "Please keep planning and revise the proposal.")
40449
+ };
40450
+ }
40451
+ function parseGenericToolApprovalResponse(event, rawText) {
40452
+ if (isAffirmativeResponse(rawText)) {
40453
+ const message = stripApprovalPrefix(rawText);
40454
+ return {
40455
+ type: "response",
40456
+ response: buildAllowResponse(event.requestId, {
40457
+ behavior: "allow",
40458
+ ...message ? { message } : {}
40459
+ })
40460
+ };
40461
+ }
40462
+ const feedback = stripApprovalPrefix(rawText);
40463
+ return {
40464
+ type: "response",
40465
+ response: buildDenyResponse(event.requestId, feedback || "Denied by channel user.")
40466
+ };
40467
+ }
40468
+ function parseChannelControlRequestResponse(event, rawText) {
40469
+ const trimmed = rawText.trim();
40470
+ if (!trimmed) {
40471
+ return {
40472
+ type: "reprompt",
40473
+ message: formatChannelControlRequestPrompt(event)
40474
+ };
40475
+ }
40476
+ switch (event.kind) {
40477
+ case "ask_user_question":
40478
+ return parseAskUserQuestionResponse(event, trimmed);
40479
+ case "enter_plan_mode":
40480
+ return parseEnterPlanModeResponse(event, trimmed);
40481
+ case "exit_plan_mode":
40482
+ return parseExitPlanModeResponse(event, trimmed);
40483
+ case "generic_tool_approval":
40484
+ return parseGenericToolApprovalResponse(event, trimmed);
40485
+ default: {
40486
+ const exhaustiveCheck = event.kind;
40487
+ return exhaustiveCheck;
40488
+ }
40489
+ }
40490
+ }
40491
+
40130
40492
  // src/channels/telegram/media.ts
40131
40493
  import { randomUUID as randomUUID2 } from "node:crypto";
40132
40494
  import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
@@ -40656,14 +41018,27 @@ function getPackageManagerExecutable(packageManager) {
40656
41018
  }
40657
41019
  return packageManager;
40658
41020
  }
41021
+ function resolveInstallPlatform() {
41022
+ return platformOverride ?? process.platform;
41023
+ }
40659
41024
  function getInstallArgs(packageManager, installPackages) {
41025
+ const noBinLinks = resolveInstallPlatform() === "win32" && packageManager !== "bun";
40660
41026
  switch (packageManager) {
40661
41027
  case "bun":
40662
41028
  return ["add", "--no-save", ...installPackages];
40663
41029
  case "pnpm":
40664
- return ["add", ...installPackages];
41030
+ return [
41031
+ "add",
41032
+ ...noBinLinks ? ["--no-bin-links"] : [],
41033
+ ...installPackages
41034
+ ];
40665
41035
  case "npm":
40666
- return ["install", "--no-save", ...installPackages];
41036
+ return [
41037
+ "install",
41038
+ "--no-save",
41039
+ ...noBinLinks ? ["--no-bin-links"] : [],
41040
+ ...installPackages
41041
+ ];
40667
41042
  }
40668
41043
  }
40669
41044
  async function installChannelRuntime(channelId) {
@@ -41097,6 +41472,13 @@ function createTelegramAdapter(config) {
41097
41472
  } : undefined;
41098
41473
  await telegramBot.api.sendMessage(chatId, text, reply_parameters ? { reply_parameters } : {});
41099
41474
  },
41475
+ async handleControlRequestEvent(event) {
41476
+ const telegramBot = await ensureBot();
41477
+ const reply_parameters = event.source.messageId || event.source.threadId ? {
41478
+ message_id: Number(event.source.threadId ?? event.source.messageId)
41479
+ } : undefined;
41480
+ await telegramBot.api.sendMessage(event.source.chatId, formatChannelControlRequestPrompt(event), reply_parameters ? { reply_parameters } : {});
41481
+ },
41100
41482
  onMessage: undefined
41101
41483
  };
41102
41484
  return adapter;
@@ -41296,6 +41678,14 @@ var init_plugin = __esm(() => {
41296
41678
  import { randomUUID as randomUUID4 } from "node:crypto";
41297
41679
  import { mkdir as mkdir4, readFile as readFile3, writeFile as writeFile4 } from "node:fs/promises";
41298
41680
  import { basename as basename3, extname as extname2, join as join12 } from "node:path";
41681
+ function mapSlackThreadMessage(message) {
41682
+ return {
41683
+ text: resolveSlackThreadMessageText(message),
41684
+ userId: isNonEmptyString(message.user) ? message.user : undefined,
41685
+ botId: isNonEmptyString(message.bot_id) ? message.bot_id : undefined,
41686
+ ts: isNonEmptyString(message.ts) ? message.ts : undefined
41687
+ };
41688
+ }
41299
41689
  function asRecord(value) {
41300
41690
  return value && typeof value === "object" ? value : null;
41301
41691
  }
@@ -41569,12 +41959,31 @@ async function resolveSlackThreadHistory(params) {
41569
41959
  const nextCursor = response.response_metadata?.next_cursor;
41570
41960
  cursor = typeof nextCursor === "string" && nextCursor.trim().length > 0 ? nextCursor.trim() : undefined;
41571
41961
  } while (cursor);
41572
- return retained.map((message) => ({
41573
- text: resolveSlackThreadMessageText(message),
41574
- userId: isNonEmptyString(message.user) ? message.user : undefined,
41575
- botId: isNonEmptyString(message.bot_id) ? message.bot_id : undefined,
41576
- ts: isNonEmptyString(message.ts) ? message.ts : undefined
41577
- }));
41962
+ return retained.map(mapSlackThreadMessage);
41963
+ } catch {
41964
+ return [];
41965
+ }
41966
+ }
41967
+ async function resolveSlackChannelHistory(params) {
41968
+ const maxMessages = params.limit ?? 20;
41969
+ if (!Number.isFinite(maxMessages) || maxMessages <= 0) {
41970
+ return [];
41971
+ }
41972
+ const fetchLimit = Math.min(Math.max(maxMessages * 3, maxMessages), 100);
41973
+ try {
41974
+ const response = await params.client.conversations.history({
41975
+ channel: params.channelId,
41976
+ latest: params.beforeTs,
41977
+ inclusive: false,
41978
+ limit: fetchLimit
41979
+ });
41980
+ const retained = (response.messages ?? []).filter((message) => {
41981
+ if (message.ts === params.beforeTs) {
41982
+ return false;
41983
+ }
41984
+ return Boolean(resolveSlackThreadMessageText(message));
41985
+ }).slice(0, fetchLimit).reverse();
41986
+ return retained.slice(-maxMessages).map(mapSlackThreadMessage);
41578
41987
  } catch {
41579
41988
  return [];
41580
41989
  }
@@ -41744,6 +42153,13 @@ function buildSlackThreadLabel(msg, starterText) {
41744
42153
  }
41745
42154
  return roomLabel ? `Slack thread${roomLabel}` : `Slack thread ${msg.chatId}`;
41746
42155
  }
42156
+ function buildSlackChannelContextLabel(msg) {
42157
+ if (msg.chatType !== "channel") {
42158
+ return;
42159
+ }
42160
+ const roomLabel = isNonEmptyString2(msg.chatLabel) && msg.chatLabel !== msg.chatId ? ` in ${msg.chatLabel}` : "";
42161
+ return roomLabel ? `Slack channel context${roomLabel} before thread start` : `Slack channel context before thread start`;
42162
+ }
41747
42163
  async function resolveSlackAccountDisplayName(botToken, appToken) {
41748
42164
  const bolt = await loadSlackBoltModule();
41749
42165
  const App2 = resolveSlackAppConstructor(bolt);
@@ -42222,22 +42638,42 @@ function createSlackAdapter(config) {
42222
42638
  });
42223
42639
  rememberMessageThread(response.ts, options?.replyToMessageId ?? response.ts ?? null);
42224
42640
  },
42641
+ async handleControlRequestEvent(event) {
42642
+ await ensureApp();
42643
+ const slackClient = await ensureWriteClient();
42644
+ const response = await slackClient.chat.postMessage({
42645
+ channel: event.source.chatId,
42646
+ text: formatChannelControlRequestPrompt(event),
42647
+ ...event.source.threadId ?? event.source.messageId ? { thread_ts: event.source.threadId ?? event.source.messageId } : {}
42648
+ });
42649
+ rememberMessageThread(response.ts, event.source.threadId ?? event.source.messageId ?? response.ts ?? null);
42650
+ },
42225
42651
  async prepareInboundMessage(msg, options) {
42226
- if (!options?.isFirstRouteTurn || msg.channel !== "slack" || msg.chatType !== "channel" || !isNonEmptyString2(msg.threadId) || !isNonEmptyString2(msg.messageId) || msg.threadId === msg.messageId) {
42652
+ if (!options?.isFirstRouteTurn || msg.channel !== "slack" || msg.chatType !== "channel" || !isNonEmptyString2(msg.threadId) || !isNonEmptyString2(msg.messageId)) {
42653
+ return msg;
42654
+ }
42655
+ const shouldHydrateExistingThreadContext = msg.threadId !== msg.messageId;
42656
+ const shouldHydrateChannelBootstrapContext = msg.isMention === true && msg.threadId === msg.messageId;
42657
+ if (!shouldHydrateExistingThreadContext && !shouldHydrateChannelBootstrapContext) {
42227
42658
  return msg;
42228
42659
  }
42229
42660
  const slackApp = await ensureApp();
42230
- const starter = await resolveSlackThreadStarter({
42661
+ const starter = shouldHydrateExistingThreadContext ? await resolveSlackThreadStarter({
42231
42662
  channelId: msg.chatId,
42232
42663
  threadTs: msg.threadId,
42233
42664
  client: slackApp.client
42234
- });
42235
- const history = await resolveSlackThreadHistory({
42665
+ }) : null;
42666
+ const history = shouldHydrateExistingThreadContext ? await resolveSlackThreadHistory({
42236
42667
  channelId: msg.chatId,
42237
42668
  threadTs: msg.threadId,
42238
42669
  client: slackApp.client,
42239
42670
  currentMessageTs: msg.messageId,
42240
42671
  limit: INITIAL_SLACK_THREAD_HISTORY_LIMIT
42672
+ }) : await resolveSlackChannelHistory({
42673
+ channelId: msg.chatId,
42674
+ beforeTs: msg.messageId,
42675
+ client: slackApp.client,
42676
+ limit: INITIAL_SLACK_THREAD_HISTORY_LIMIT
42241
42677
  });
42242
42678
  if (!starter && history.length === 0) {
42243
42679
  return msg;
@@ -42266,7 +42702,7 @@ function createSlackAdapter(config) {
42266
42702
  return {
42267
42703
  ...msg,
42268
42704
  threadContext: {
42269
- label: buildSlackThreadLabel(msg, starter?.text),
42705
+ label: shouldHydrateExistingThreadContext ? buildSlackThreadLabel(msg, starter?.text) : buildSlackChannelContextLabel(msg),
42270
42706
  ...starter ? {
42271
42707
  starter: {
42272
42708
  messageId: starter.ts,
@@ -42436,6 +42872,7 @@ DM Policy — who can message this app directly?
42436
42872
  botToken,
42437
42873
  appToken,
42438
42874
  agentId: null,
42875
+ defaultPermissionMode: "default",
42439
42876
  dmPolicy: policy,
42440
42877
  allowedUsers,
42441
42878
  createdAt: now,
@@ -43278,6 +43715,14 @@ function buildChannelTurnSource(route, msg) {
43278
43715
  conversationId: route.conversationId
43279
43716
  };
43280
43717
  }
43718
+ function getChannelApprovalScopeKey(params) {
43719
+ return [
43720
+ params.channel,
43721
+ params.accountId ?? LEGACY_CHANNEL_ACCOUNT_ID,
43722
+ params.chatId,
43723
+ params.threadId ?? ""
43724
+ ].join(":");
43725
+ }
43281
43726
  function getChannelRegistry() {
43282
43727
  return instance;
43283
43728
  }
@@ -43295,7 +43740,10 @@ class ChannelRegistry {
43295
43740
  ready = false;
43296
43741
  messageHandler = null;
43297
43742
  eventHandler = null;
43743
+ approvalResponseHandler = null;
43298
43744
  buffer = [];
43745
+ pendingControlRequestsById = new Map;
43746
+ pendingControlRequestIdByScope = new Map;
43299
43747
  constructor() {
43300
43748
  if (instance) {
43301
43749
  throw new Error("ChannelRegistry is a singleton — use getChannelRegistry()");
@@ -43365,9 +43813,57 @@ class ChannelRegistry {
43365
43813
  setMessageHandler(handler) {
43366
43814
  this.messageHandler = handler;
43367
43815
  }
43816
+ setApprovalResponseHandler(handler) {
43817
+ this.approvalResponseHandler = handler;
43818
+ }
43368
43819
  setEventHandler(handler) {
43369
43820
  this.eventHandler = handler;
43370
43821
  }
43822
+ async registerPendingControlRequest(event) {
43823
+ const scopeKey = getChannelApprovalScopeKey({
43824
+ channel: event.source.channel,
43825
+ accountId: event.source.accountId,
43826
+ chatId: event.source.chatId,
43827
+ threadId: event.source.threadId
43828
+ });
43829
+ const existingRequestId = this.pendingControlRequestIdByScope.get(scopeKey);
43830
+ if (existingRequestId) {
43831
+ this.clearPendingControlRequest(existingRequestId);
43832
+ }
43833
+ this.pendingControlRequestsById.set(event.requestId, event);
43834
+ this.pendingControlRequestIdByScope.set(scopeKey, event.requestId);
43835
+ const adapter = this.getAdapter(event.source.channel, event.source.accountId ?? LEGACY_CHANNEL_ACCOUNT_ID);
43836
+ if (!adapter) {
43837
+ return;
43838
+ }
43839
+ try {
43840
+ if (adapter.handleControlRequestEvent) {
43841
+ await adapter.handleControlRequestEvent(event);
43842
+ return;
43843
+ }
43844
+ await adapter.sendDirectReply(event.source.chatId, formatChannelControlRequestPrompt(event), {
43845
+ replyToMessageId: event.source.threadId ?? event.source.messageId
43846
+ });
43847
+ } catch (error) {
43848
+ console.error(`[Channels] Failed to deliver control request prompt for ${event.source.channel}/${event.source.accountId ?? LEGACY_CHANNEL_ACCOUNT_ID}:`, error instanceof Error ? error.message : error);
43849
+ }
43850
+ }
43851
+ clearPendingControlRequest(requestId) {
43852
+ const pending = this.pendingControlRequestsById.get(requestId);
43853
+ if (!pending) {
43854
+ return;
43855
+ }
43856
+ this.pendingControlRequestsById.delete(requestId);
43857
+ const scopeKey = getChannelApprovalScopeKey({
43858
+ channel: pending.source.channel,
43859
+ accountId: pending.source.accountId,
43860
+ chatId: pending.source.chatId,
43861
+ threadId: pending.source.threadId
43862
+ });
43863
+ if (this.pendingControlRequestIdByScope.get(scopeKey) === requestId) {
43864
+ this.pendingControlRequestIdByScope.delete(scopeKey);
43865
+ }
43866
+ }
43371
43867
  setReady() {
43372
43868
  this.ready = true;
43373
43869
  this.flushBuffer();
@@ -43459,6 +43955,7 @@ class ChannelRegistry {
43459
43955
  this.ready = false;
43460
43956
  this.messageHandler = null;
43461
43957
  this.eventHandler = null;
43958
+ this.approvalResponseHandler = null;
43462
43959
  }
43463
43960
  async stopAll() {
43464
43961
  for (const adapter of Array.from(this.adapters.values())) {
@@ -43469,13 +43966,63 @@ class ChannelRegistry {
43469
43966
  this.ready = false;
43470
43967
  this.messageHandler = null;
43471
43968
  this.eventHandler = null;
43969
+ this.approvalResponseHandler = null;
43970
+ this.pendingControlRequestsById.clear();
43971
+ this.pendingControlRequestIdByScope.clear();
43472
43972
  instance = null;
43473
43973
  }
43974
+ async tryHandlePendingControlRequest(adapter, msg) {
43975
+ const scopeKey = getChannelApprovalScopeKey({
43976
+ channel: msg.channel,
43977
+ accountId: msg.accountId,
43978
+ chatId: msg.chatId,
43979
+ threadId: msg.threadId
43980
+ });
43981
+ const requestId = this.pendingControlRequestIdByScope.get(scopeKey);
43982
+ if (!requestId) {
43983
+ return false;
43984
+ }
43985
+ const pending = this.pendingControlRequestsById.get(requestId);
43986
+ if (!pending) {
43987
+ this.pendingControlRequestIdByScope.delete(scopeKey);
43988
+ return false;
43989
+ }
43990
+ const parsed = parseChannelControlRequestResponse(pending, msg.text);
43991
+ if (parsed.type === "reprompt") {
43992
+ await adapter.sendDirectReply(msg.chatId, parsed.message, {
43993
+ replyToMessageId: msg.threadId ?? msg.messageId
43994
+ });
43995
+ return true;
43996
+ }
43997
+ if (!this.approvalResponseHandler) {
43998
+ await adapter.sendDirectReply(msg.chatId, "I’m reconnecting to Letta Code right now, so I couldn’t use that reply yet. Please send it again in a moment.", {
43999
+ replyToMessageId: msg.threadId ?? msg.messageId
44000
+ });
44001
+ return true;
44002
+ }
44003
+ const handled = await this.approvalResponseHandler({
44004
+ runtime: {
44005
+ agent_id: pending.source.agentId,
44006
+ conversation_id: pending.source.conversationId
44007
+ },
44008
+ response: parsed.response
44009
+ });
44010
+ this.clearPendingControlRequest(requestId);
44011
+ if (!handled) {
44012
+ await adapter.sendDirectReply(msg.chatId, "That approval prompt expired before I could use your reply. Please ask the agent to try again.", {
44013
+ replyToMessageId: msg.threadId ?? msg.messageId
44014
+ });
44015
+ }
44016
+ return true;
44017
+ }
43474
44018
  async handleInboundMessage(msg) {
43475
44019
  const accountId = msg.accountId ?? LEGACY_CHANNEL_ACCOUNT_ID;
43476
44020
  const adapter = this.getAdapter(msg.channel, accountId);
43477
44021
  if (!adapter)
43478
44022
  return;
44023
+ if (await this.tryHandlePendingControlRequest(adapter, msg)) {
44024
+ return;
44025
+ }
43479
44026
  const config = getChannelAccount(msg.channel, accountId);
43480
44027
  if (!config)
43481
44028
  return;
@@ -43558,6 +44105,16 @@ class ChannelRegistry {
43558
44105
  updatedAt: now
43559
44106
  };
43560
44107
  addRoute(msg.channel, route);
44108
+ if (config.defaultPermissionMode !== "default") {
44109
+ this.eventHandler?.({
44110
+ type: "slack_conversation_created",
44111
+ channelId: "slack",
44112
+ accountId: config.accountId,
44113
+ agentId: config.agentId,
44114
+ conversationId,
44115
+ defaultPermissionMode: config.defaultPermissionMode
44116
+ });
44117
+ }
43561
44118
  return route;
43562
44119
  }
43563
44120
  async ensureSlackRoute(adapter, msg, config) {
@@ -52520,6 +53077,7 @@ function createConversationRuntime(listener, agentId, conversationId) {
52520
53077
  key: runtimeKey,
52521
53078
  agentId: normalizedAgentId,
52522
53079
  conversationId: normalizedConversationId,
53080
+ activeChannelTurnSources: null,
52523
53081
  messageQueue: Promise.resolve(),
52524
53082
  pendingApprovalResolvers: new Map,
52525
53083
  recoveredApprovalState: null,
@@ -74741,7 +75299,12 @@ async function executeTool(name, args, options) {
74741
75299
  enhancedArgs = { ...enhancedArgs, signal: options.signal };
74742
75300
  }
74743
75301
  if (options?.onOutput) {
74744
- enhancedArgs = { ...enhancedArgs, onOutput: options.onOutput };
75302
+ enhancedArgs = {
75303
+ ...enhancedArgs,
75304
+ onOutput: (chunk, stream2) => {
75305
+ options.onOutput?.(scrubSecretsFromString(chunk), stream2);
75306
+ }
75307
+ };
74745
75308
  }
74746
75309
  enhancedArgs = substituteSecretsInArgs(enhancedArgs);
74747
75310
  }
@@ -75241,6 +75804,7 @@ function toAccountSnapshot(account) {
75241
75804
  hasBotToken: account.botToken.trim().length > 0,
75242
75805
  hasAppToken: account.appToken.trim().length > 0,
75243
75806
  agentId: account.agentId,
75807
+ defaultPermissionMode: account.defaultPermissionMode ?? "default",
75244
75808
  createdAt: account.createdAt,
75245
75809
  updatedAt: account.updatedAt
75246
75810
  };
@@ -75273,6 +75837,7 @@ function createAccountFromPatch(channelId, accountId, patch) {
75273
75837
  botToken: patch.botToken ?? "",
75274
75838
  appToken: patch.appToken ?? "",
75275
75839
  agentId: patch.agentId ?? null,
75840
+ defaultPermissionMode: patch.defaultPermissionMode ?? "default",
75276
75841
  dmPolicy: patch.dmPolicy ?? "open",
75277
75842
  allowedUsers: patch.allowedUsers ?? [],
75278
75843
  createdAt: now,
@@ -75300,6 +75865,7 @@ function mergeAccountPatch(existing, patch) {
75300
75865
  botToken: patch.botToken ?? existing.botToken,
75301
75866
  appToken: patch.appToken ?? existing.appToken,
75302
75867
  agentId: patch.agentId ?? existing.agentId,
75868
+ defaultPermissionMode: patch.defaultPermissionMode ?? existing.defaultPermissionMode ?? "default",
75303
75869
  dmPolicy: patch.dmPolicy ?? existing.dmPolicy,
75304
75870
  allowedUsers: patch.allowedUsers ?? existing.allowedUsers,
75305
75871
  updatedAt: nextUpdatedAt
@@ -77195,13 +77761,58 @@ function writeCronFile(data) {
77195
77761
  writeFileSync14(tmp, JSON.stringify(data, null, 2), { flush: true });
77196
77762
  renameSync2(tmp, path20);
77197
77763
  }
77198
- function isProcessAlive(pid) {
77764
+ function readLinuxProcessIdentity(pid) {
77765
+ try {
77766
+ const stat5 = readFileSync15(`/proc/${pid}/stat`, "utf8");
77767
+ const endCommand = stat5.lastIndexOf(")");
77768
+ if (endCommand === -1) {
77769
+ return null;
77770
+ }
77771
+ const fields = stat5.slice(endCommand + 2).trim().split(/\s+/);
77772
+ const startTicks = fields[19] ?? null;
77773
+ if (!startTicks) {
77774
+ return null;
77775
+ }
77776
+ let bootId = null;
77777
+ try {
77778
+ bootId = readFileSync15("/proc/sys/kernel/random/boot_id", "utf8").trim() || null;
77779
+ } catch {}
77780
+ return { startTicks, bootId };
77781
+ } catch {
77782
+ return null;
77783
+ }
77784
+ }
77785
+ function readProcessIdentity(pid) {
77786
+ if (readProcessIdentityOverride) {
77787
+ return readProcessIdentityOverride(pid);
77788
+ }
77789
+ return readLinuxProcessIdentity(pid);
77790
+ }
77791
+ function captureProcessIdentity(pid) {
77792
+ const identity = readProcessIdentity(pid);
77793
+ return {
77794
+ process_start_ticks: identity?.startTicks ?? null,
77795
+ boot_id: identity?.bootId ?? null
77796
+ };
77797
+ }
77798
+ function isProcessAlive(pid, owner) {
77199
77799
  try {
77200
77800
  process.kill(pid, 0);
77201
- return true;
77202
77801
  } catch {
77203
77802
  return false;
77204
77803
  }
77804
+ if (owner) {
77805
+ const identity = readProcessIdentity(pid);
77806
+ if (identity) {
77807
+ if (owner.boot_id && identity.bootId && owner.boot_id !== identity.bootId) {
77808
+ return false;
77809
+ }
77810
+ if (owner.process_start_ticks && identity.startTicks && owner.process_start_ticks !== identity.startTicks) {
77811
+ return false;
77812
+ }
77813
+ }
77814
+ }
77815
+ return true;
77205
77816
  }
77206
77817
  function readLockOwner(lockDir) {
77207
77818
  try {
@@ -77224,7 +77835,7 @@ function isLockStale(lockDir) {
77224
77835
  return true;
77225
77836
  }
77226
77837
  }
77227
- const pidDead = !isProcessAlive(owner.pid);
77838
+ const pidDead = !isProcessAlive(owner.pid, owner);
77228
77839
  const isOld = Date.now() - owner.acquired_at > LOCK_STALE_AGE_MS;
77229
77840
  return pidDead && isOld;
77230
77841
  }
@@ -77243,7 +77854,8 @@ function acquireLock() {
77243
77854
  const owner = {
77244
77855
  pid: process.pid,
77245
77856
  token,
77246
- acquired_at: Date.now()
77857
+ acquired_at: Date.now(),
77858
+ ...captureProcessIdentity(process.pid)
77247
77859
  };
77248
77860
  writeLockOwner(lockDir, owner);
77249
77861
  return {
@@ -77336,7 +77948,7 @@ function addTask(input) {
77336
77948
  prompt: input.prompt,
77337
77949
  status: "active",
77338
77950
  created_at: now.toISOString(),
77339
- expires_at: input.recurring ? new Date(now.getTime() + DEFAULT_TTL_MS).toISOString() : null,
77951
+ expires_at: null,
77340
77952
  last_fired_at: null,
77341
77953
  fire_count: 0,
77342
77954
  cancel_reason: null,
@@ -77348,7 +77960,7 @@ function addTask(input) {
77348
77960
  data.tasks.push(task2);
77349
77961
  writeCronFile(data);
77350
77962
  let warning;
77351
- if (!data.scheduler_owner || !isProcessAlive(data.scheduler_owner.pid)) {
77963
+ if (!data.scheduler_owner || !isProcessAlive(data.scheduler_owner.pid, data.scheduler_owner)) {
77352
77964
  warning = "No letta server is currently running. This task will only execute when a WS listener is active.";
77353
77965
  }
77354
77966
  return { task: task2, warning };
@@ -77396,15 +78008,17 @@ function claimSchedulerLease() {
77396
78008
  const data = readCronFile();
77397
78009
  const token = randomBytes(4).toString("hex");
77398
78010
  if (data.scheduler_owner) {
77399
- const { pid, token: existingToken } = data.scheduler_owner;
77400
- if (isProcessAlive(pid)) {
78011
+ const existingOwner = data.scheduler_owner;
78012
+ const { pid, token: existingToken } = existingOwner;
78013
+ if (isProcessAlive(pid, existingOwner)) {
77401
78014
  throw new Error(`Scheduler lease held by PID ${pid} (token ${existingToken}). Cannot claim.`);
77402
78015
  }
77403
78016
  }
77404
78017
  data.scheduler_owner = {
77405
78018
  pid: process.pid,
77406
78019
  token,
77407
- started_at: new Date().toISOString()
78020
+ started_at: new Date().toISOString(),
78021
+ ...captureProcessIdentity(process.pid)
77408
78022
  };
77409
78023
  writeCronFile(data);
77410
78024
  return token;
@@ -77464,10 +78078,9 @@ function getCronFileMtime() {
77464
78078
  return 0;
77465
78079
  }
77466
78080
  }
77467
- var CRON_FILE_NAME = "crons.json", LOCK_DIR_NAME = "crons.lock", LOCK_TOKEN_FILE = "owner.json", LOCK_TIMEOUT_MS = 5000, LOCK_RETRY_MS = 50, LOCK_STALE_AGE_MS = 30000, MAX_ACTIVE_TASKS_PER_AGENT = 50, TASK_ID_BYTES = 4, DEFAULT_TTL_MS, GC_AGE_MS;
78081
+ var CRON_FILE_NAME = "crons.json", LOCK_DIR_NAME = "crons.lock", LOCK_TOKEN_FILE = "owner.json", LOCK_TIMEOUT_MS = 5000, LOCK_RETRY_MS = 50, LOCK_STALE_AGE_MS = 30000, MAX_ACTIVE_TASKS_PER_AGENT = 50, TASK_ID_BYTES = 4, GC_AGE_MS, readProcessIdentityOverride = null;
77468
78082
  var init_cronFile = __esm(() => {
77469
78083
  init_parseInterval();
77470
- DEFAULT_TTL_MS = 3 * 24 * 60 * 60 * 1000;
77471
78084
  GC_AGE_MS = 24 * 60 * 60 * 1000;
77472
78085
  });
77473
78086
 
@@ -81751,6 +82364,18 @@ var MIN_SPLIT_LENGTH = 1500;
81751
82364
  function isInteractiveApprovalTool(toolName) {
81752
82365
  return INTERACTIVE_APPROVAL_TOOLS.has(toolName);
81753
82366
  }
82367
+ function getInteractiveApprovalKind(toolName) {
82368
+ switch (toolName) {
82369
+ case "AskUserQuestion":
82370
+ return "ask_user_question";
82371
+ case "EnterPlanMode":
82372
+ return "enter_plan_mode";
82373
+ case "ExitPlanMode":
82374
+ return "exit_plan_mode";
82375
+ default:
82376
+ return null;
82377
+ }
82378
+ }
81754
82379
  function isHeadlessAutoAllowTool(toolName) {
81755
82380
  return HEADLESS_AUTO_ALLOW_TOOLS.has(toolName);
81756
82381
  }
@@ -84514,6 +85139,25 @@ function normalizeInterruptOutputLines(value) {
84514
85139
  const combinedLength = filtered.reduce((sum, entry) => sum + entry.length, 0);
84515
85140
  return combinedLength <= INTERRUPT_TOOL_RETURN_MAX_CHARS ? filtered : undefined;
84516
85141
  }
85142
+ function appendStreamingOutputWithCap(current, chunk) {
85143
+ if (chunk.length === 0) {
85144
+ return current;
85145
+ }
85146
+ const next = `${current}${chunk}`;
85147
+ if (next.length <= STREAMING_TOOL_OUTPUT_MAX_CHARS) {
85148
+ return next;
85149
+ }
85150
+ return next.slice(next.length - STREAMING_TOOL_OUTPUT_MAX_CHARS);
85151
+ }
85152
+ function normalizeStreamingOutputLines(text) {
85153
+ if (text.length === 0) {
85154
+ return;
85155
+ }
85156
+ const lines = text.replace(/\r\n/g, `
85157
+ `).split(`
85158
+ `).filter((line) => line.length > 0);
85159
+ return lines.length > 0 ? lines : undefined;
85160
+ }
84517
85161
  function asToolReturnStatus(value) {
84518
85162
  if (value === "success" || value === "error") {
84519
85163
  return value;
@@ -84727,6 +85371,54 @@ function emitToolExecutionFinishedEvents(socket, runtime, params) {
84727
85371
  });
84728
85372
  }
84729
85373
  }
85374
+ function createToolExecutionOutputEmitter(socket, runtime, params) {
85375
+ const outputByToolCallId = new Map;
85376
+ return (toolCallId, chunk, isStderr = false) => {
85377
+ if (!toolCallId || chunk.length === 0) {
85378
+ return;
85379
+ }
85380
+ const existing = outputByToolCallId.get(toolCallId);
85381
+ const outputState = existing ?? {
85382
+ messageId: `message-tool-return-stream-${toolCallId}`,
85383
+ stdout: "",
85384
+ stderr: ""
85385
+ };
85386
+ if (isStderr) {
85387
+ outputState.stderr = appendStreamingOutputWithCap(outputState.stderr, chunk);
85388
+ } else {
85389
+ outputState.stdout = appendStreamingOutputWithCap(outputState.stdout, chunk);
85390
+ }
85391
+ outputByToolCallId.set(toolCallId, outputState);
85392
+ const stdout = normalizeStreamingOutputLines(outputState.stdout);
85393
+ const stderr = normalizeStreamingOutputLines(outputState.stderr);
85394
+ const toolReturn = [stdout?.join(`
85395
+ `), stderr?.join(`
85396
+ `)].filter((part) => typeof part === "string" && part.length > 0).join(`
85397
+ `);
85398
+ emitCanonicalMessageDelta(socket, runtime, {
85399
+ type: "message",
85400
+ message_type: "tool_return_message",
85401
+ id: outputState.messageId,
85402
+ date: new Date().toISOString(),
85403
+ run_id: params.runId ?? runtime.activeRunId ?? undefined,
85404
+ status: "success",
85405
+ tool_call_id: toolCallId,
85406
+ tool_return: toolReturn,
85407
+ tool_returns: [
85408
+ {
85409
+ tool_call_id: toolCallId,
85410
+ status: "success",
85411
+ tool_return: toolReturn,
85412
+ ...stdout ? { stdout } : {},
85413
+ ...stderr ? { stderr } : {}
85414
+ }
85415
+ ]
85416
+ }, {
85417
+ agent_id: params.agentId,
85418
+ conversation_id: params.conversationId
85419
+ });
85420
+ };
85421
+ }
84730
85422
  function getInterruptApprovalsForEmission(runtime, params) {
84731
85423
  if (params.lastExecutionResults && params.lastExecutionResults.length > 0) {
84732
85424
  return params.lastExecutionResults;
@@ -84843,7 +85535,7 @@ function stashRecoveredApprovalInterrupts(runtime, recovered) {
84843
85535
  clearRecoveredApprovalState(runtime);
84844
85536
  return true;
84845
85537
  }
84846
- var INTERRUPT_TOOL_RETURN_MAX_CHARS;
85538
+ var INTERRUPT_TOOL_RETURN_MAX_CHARS, STREAMING_TOOL_OUTPUT_MAX_CHARS;
84847
85539
  var init_interrupts = __esm(async () => {
84848
85540
  init_approval_result_normalization();
84849
85541
  init_constants();
@@ -84855,6 +85547,7 @@ var init_interrupts = __esm(async () => {
84855
85547
  init_protocol_outbound()
84856
85548
  ]);
84857
85549
  INTERRUPT_TOOL_RETURN_MAX_CHARS = LIMITS.BASH_OUTPUT_CHARS;
85550
+ STREAMING_TOOL_OUTPUT_MAX_CHARS = LIMITS.BASH_OUTPUT_CHARS;
84858
85551
  });
84859
85552
 
84860
85553
  // src/websocket/listener/permissionMode.ts
@@ -87695,6 +88388,11 @@ async function resolveRecoveredApprovalResponse(runtime, socket, response, proce
87695
88388
  agentId: recovered.agentId,
87696
88389
  conversationId: recovered.conversationId
87697
88390
  });
88391
+ const emitToolExecutionOutput = createToolExecutionOutputEmitter(socket, runtime, {
88392
+ runId: runtime.activeRunId ?? undefined,
88393
+ agentId: recovered.agentId,
88394
+ conversationId: recovered.conversationId
88395
+ });
87698
88396
  const recoveryAbortController = new AbortController;
87699
88397
  runtime.activeAbortController = recoveryAbortController;
87700
88398
  const preparedToolContext = await prepareToolExecutionContextForScope({
@@ -87709,6 +88407,7 @@ async function resolveRecoveredApprovalResponse(runtime, socket, response, proce
87709
88407
  try {
87710
88408
  const approvalResults = await executeApprovalBatch(decisions, undefined, {
87711
88409
  abortSignal: recoveryAbortController.signal,
88410
+ onStreamingOutput: emitToolExecutionOutput,
87712
88411
  toolContextId: preparedToolContext.preparedToolContext.contextId,
87713
88412
  workingDirectory,
87714
88413
  parentScope: recovered.agentId && recovered.conversationId ? {
@@ -87979,9 +88678,15 @@ async function resolveStaleApprovals(runtime, socket, abortSignal, deps = {}) {
87979
88678
  agentId: runtime.agentId ?? undefined,
87980
88679
  conversationId: recoveryConversationId
87981
88680
  });
88681
+ const emitToolExecutionOutput = createToolExecutionOutputEmitter(socket, runtime, {
88682
+ runId: runtime.activeRunId ?? undefined,
88683
+ agentId: runtime.agentId ?? undefined,
88684
+ conversationId: recoveryConversationId
88685
+ });
87982
88686
  try {
87983
88687
  const approvalResults = await executeApprovalBatch(decisions, undefined, {
87984
88688
  abortSignal,
88689
+ onStreamingOutput: emitToolExecutionOutput,
87985
88690
  toolContextId: preparedToolContext.preparedToolContext.contextId,
87986
88691
  workingDirectory: recoveryWorkingDirectory,
87987
88692
  parentScope: runtime.agentId && runtime.conversationId ? {
@@ -88327,7 +89032,46 @@ var init_send = __esm(async () => {
88327
89032
  });
88328
89033
 
88329
89034
  // src/websocket/listener/turn-approval.ts
89035
+ import { readFile as readFile11 } from "node:fs/promises";
88330
89036
  import WebSocket2 from "ws";
89037
+ function getChannelApprovalSourceScopeKey(source) {
89038
+ return [
89039
+ source.channel,
89040
+ source.accountId ?? "",
89041
+ source.chatId,
89042
+ source.threadId ?? ""
89043
+ ].join(":");
89044
+ }
89045
+ function resolveChannelApprovalSource(runtime) {
89046
+ const sources = runtime.activeChannelTurnSources ?? [];
89047
+ if (sources.length === 0) {
89048
+ return null;
89049
+ }
89050
+ const sourcesByScope = new Map;
89051
+ for (const source of sources) {
89052
+ sourcesByScope.set(getChannelApprovalSourceScopeKey(source), source);
89053
+ }
89054
+ if (sourcesByScope.size !== 1) {
89055
+ return null;
89056
+ }
89057
+ return [...sourcesByScope.values()].at(-1) ?? null;
89058
+ }
89059
+ async function maybeReadPlanPreview(toolName, turnPermissionModeState) {
89060
+ if (toolName !== "ExitPlanMode" || !turnPermissionModeState.planFilePath) {
89061
+ return {};
89062
+ }
89063
+ try {
89064
+ const planContent = await readFile11(turnPermissionModeState.planFilePath, "utf8");
89065
+ return {
89066
+ planFilePath: turnPermissionModeState.planFilePath,
89067
+ planContent
89068
+ };
89069
+ } catch {
89070
+ return {
89071
+ planFilePath: turnPermissionModeState.planFilePath
89072
+ };
89073
+ }
89074
+ }
88331
89075
  async function handleApprovalStop(params) {
88332
89076
  const {
88333
89077
  approvals,
@@ -88465,6 +89209,18 @@ async function handleApprovalStop(params) {
88465
89209
  agent_id: agentId,
88466
89210
  conversation_id: conversationId
88467
89211
  };
89212
+ const registry = getChannelRegistry();
89213
+ const channelSource = resolveChannelApprovalSource(runtime);
89214
+ if (registry && channelSource) {
89215
+ await registry.registerPendingControlRequest({
89216
+ requestId,
89217
+ kind: getInteractiveApprovalKind(ac.approval.toolName) ?? "generic_tool_approval",
89218
+ source: channelSource,
89219
+ toolName: ac.approval.toolName,
89220
+ input: ac.parsedArgs,
89221
+ ...await maybeReadPlanPreview(ac.approval.toolName, turnPermissionModeState)
89222
+ });
89223
+ }
88468
89224
  let responseBody;
88469
89225
  try {
88470
89226
  responseBody = await requestApprovalOverWS(runtime, socket, requestId, controlRequest);
@@ -88473,6 +89229,8 @@ async function handleApprovalStop(params) {
88473
89229
  return interruptTermination();
88474
89230
  }
88475
89231
  throw error;
89232
+ } finally {
89233
+ registry?.clearPendingControlRequest(requestId);
88476
89234
  }
88477
89235
  if (shouldInterrupt()) {
88478
89236
  return interruptTermination();
@@ -88550,6 +89308,11 @@ async function handleApprovalStop(params) {
88550
89308
  agentId,
88551
89309
  conversationId
88552
89310
  });
89311
+ const emitToolExecutionOutput = createToolExecutionOutputEmitter(socket, runtime, {
89312
+ runId: executionRunId,
89313
+ agentId,
89314
+ conversationId
89315
+ });
88553
89316
  if (shouldInterrupt()) {
88554
89317
  return interruptTermination();
88555
89318
  }
@@ -88568,6 +89331,7 @@ async function handleApprovalStop(params) {
88568
89331
  const executionResults = await executeApprovalBatch(decisions, undefined, {
88569
89332
  toolContextId: turnToolContextId ?? undefined,
88570
89333
  abortSignal: abortController.signal,
89334
+ onStreamingOutput: emitToolExecutionOutput,
88571
89335
  workingDirectory: turnWorkingDirectory,
88572
89336
  parentScope: agentId && conversationId ? { agentId, conversationId } : undefined,
88573
89337
  onFileWrite
@@ -88669,6 +89433,7 @@ async function handleApprovalStop(params) {
88669
89433
  };
88670
89434
  }
88671
89435
  var init_turn_approval = __esm(async () => {
89436
+ init_registry();
88672
89437
  init_diffPreview();
88673
89438
  init_interactivePolicy();
88674
89439
  init_skill_injection();
@@ -90512,6 +91277,7 @@ async function drainQueuedMessages(runtime, socket, opts, processQueuedTurn) {
90512
91277
  }
90513
91278
  let turnError;
90514
91279
  let didThrow = false;
91280
+ runtime.activeChannelTurnSources = channelTurnSources;
90515
91281
  try {
90516
91282
  await processQueuedTurn(queuedTurn, dequeuedBatch);
90517
91283
  } catch (error) {
@@ -90519,6 +91285,7 @@ async function drainQueuedMessages(runtime, socket, opts, processQueuedTurn) {
90519
91285
  turnError = error instanceof Error ? error.message : String(error);
90520
91286
  throw error;
90521
91287
  } finally {
91288
+ runtime.activeChannelTurnSources = null;
90522
91289
  if (channelTurnSources.length > 0) {
90523
91290
  await dispatchChannelTurnLifecycleEvent({
90524
91291
  type: "finished",
@@ -90596,11 +91363,6 @@ function refreshTaskCache(state) {
90596
91363
  }
90597
91364
  }
90598
91365
  function shouldFireTask(task2, now) {
90599
- if (task2.recurring && task2.expires_at) {
90600
- if (new Date(task2.expires_at).getTime() <= now.getTime()) {
90601
- return false;
90602
- }
90603
- }
90604
91366
  if (!task2.recurring && task2.scheduled_for) {
90605
91367
  const scheduledMs = new Date(task2.scheduled_for).getTime() + task2.jitter_offset_ms;
90606
91368
  return scheduledMs <= now.getTime();
@@ -90640,16 +91402,6 @@ function fireCronTask(task2, now, socket, opts, processQueuedTurn) {
90640
91402
  });
90641
91403
  }
90642
91404
  }
90643
- function handleExpiredRecurring(task2, now) {
90644
- if (!task2.recurring || !task2.expires_at)
90645
- return;
90646
- if (new Date(task2.expires_at).getTime() <= now.getTime()) {
90647
- updateTask(task2.id, (t) => {
90648
- t.status = "cancelled";
90649
- t.cancel_reason = "expired";
90650
- });
90651
- }
90652
- }
90653
91405
  function handleMissedOneShot(task2, now) {
90654
91406
  if (task2.recurring || !task2.scheduled_for)
90655
91407
  return false;
@@ -90678,9 +91430,6 @@ function tick2(state, socket, opts, processQueuedTurn) {
90678
91430
  }
90679
91431
  refreshTaskCache(state);
90680
91432
  for (const task2 of state.cachedTasks) {
90681
- if (task2.status !== "active")
90682
- continue;
90683
- handleExpiredRecurring(task2, now);
90684
91433
  if (task2.status !== "active")
90685
91434
  continue;
90686
91435
  if (handleMissedOneShot(task2, now))
@@ -91144,6 +91893,12 @@ function isListInDirectoryCommand(value) {
91144
91893
  const c = value;
91145
91894
  return c.type === "list_in_directory" && typeof c.path === "string";
91146
91895
  }
91896
+ function isGetTreeCommand(value) {
91897
+ if (!value || typeof value !== "object")
91898
+ return false;
91899
+ const c = value;
91900
+ return c.type === "get_tree" && typeof c.path === "string" && typeof c.depth === "number" && typeof c.request_id === "string";
91901
+ }
91147
91902
  function isReadFileCommand(value) {
91148
91903
  if (!value || typeof value !== "object")
91149
91904
  return false;
@@ -91470,7 +92225,7 @@ function parseServerMessage(data) {
91470
92225
  try {
91471
92226
  const raw = typeof data === "string" ? data : data.toString();
91472
92227
  const parsed = JSON.parse(raw);
91473
- if (isInputCommand(parsed) || isChangeDeviceStateCommand(parsed) || isAbortMessageCommand(parsed) || isSyncCommand(parsed) || isTerminalSpawnCommand(parsed) || isTerminalInputCommand(parsed) || isTerminalResizeCommand(parsed) || isTerminalKillCommand(parsed) || isSearchFilesCommand(parsed) || isListInDirectoryCommand(parsed) || isReadFileCommand(parsed) || isWriteFileCommand(parsed) || isWatchFileCommand(parsed) || isUnwatchFileCommand(parsed) || isEditFileCommand(parsed) || isFileOpsCommand(parsed) || isListMemoryCommand(parsed) || isMemoryHistoryCommand(parsed) || isMemoryFileAtRefCommand(parsed) || isEnableMemfsCommand(parsed) || isListModelsCommand(parsed) || isUpdateModelCommand(parsed) || isCronListCommand(parsed) || isCronAddCommand(parsed) || isCronGetCommand(parsed) || isCronDeleteCommand(parsed) || isCronDeleteAllCommand(parsed) || isSkillEnableCommand(parsed) || isSkillDisableCommand(parsed) || isCreateAgentCommand(parsed) || isGetReflectionSettingsCommand(parsed) || isSetReflectionSettingsCommand(parsed) || isChannelsListCommand(parsed) || isChannelAccountsListCommand(parsed) || isChannelAccountCreateCommand(parsed) || isChannelAccountUpdateCommand(parsed) || isChannelAccountBindCommand(parsed) || isChannelAccountUnbindCommand(parsed) || isChannelAccountDeleteCommand(parsed) || isChannelAccountStartCommand(parsed) || isChannelAccountStopCommand(parsed) || isChannelGetConfigCommand(parsed) || isChannelSetConfigCommand(parsed) || isChannelStartCommand(parsed) || isChannelStopCommand(parsed) || isChannelPairingsListCommand(parsed) || isChannelPairingBindCommand(parsed) || isChannelRoutesListCommand(parsed) || isChannelTargetsListCommand(parsed) || isChannelTargetBindCommand(parsed) || isChannelRouteUpdateCommand(parsed) || isChannelRouteRemoveCommand(parsed) || isExecuteCommandCommand(parsed) || isSearchBranchesCommand(parsed) || isCheckoutBranchCommand(parsed)) {
92228
+ if (isInputCommand(parsed) || isChangeDeviceStateCommand(parsed) || isAbortMessageCommand(parsed) || isSyncCommand(parsed) || isTerminalSpawnCommand(parsed) || isTerminalInputCommand(parsed) || isTerminalResizeCommand(parsed) || isTerminalKillCommand(parsed) || isSearchFilesCommand(parsed) || isListInDirectoryCommand(parsed) || isGetTreeCommand(parsed) || isReadFileCommand(parsed) || isWriteFileCommand(parsed) || isWatchFileCommand(parsed) || isUnwatchFileCommand(parsed) || isEditFileCommand(parsed) || isFileOpsCommand(parsed) || isListMemoryCommand(parsed) || isMemoryHistoryCommand(parsed) || isMemoryFileAtRefCommand(parsed) || isEnableMemfsCommand(parsed) || isListModelsCommand(parsed) || isUpdateModelCommand(parsed) || isCronListCommand(parsed) || isCronAddCommand(parsed) || isCronGetCommand(parsed) || isCronDeleteCommand(parsed) || isCronDeleteAllCommand(parsed) || isSkillEnableCommand(parsed) || isSkillDisableCommand(parsed) || isCreateAgentCommand(parsed) || isGetReflectionSettingsCommand(parsed) || isSetReflectionSettingsCommand(parsed) || isChannelsListCommand(parsed) || isChannelAccountsListCommand(parsed) || isChannelAccountCreateCommand(parsed) || isChannelAccountUpdateCommand(parsed) || isChannelAccountBindCommand(parsed) || isChannelAccountUnbindCommand(parsed) || isChannelAccountDeleteCommand(parsed) || isChannelAccountStartCommand(parsed) || isChannelAccountStopCommand(parsed) || isChannelGetConfigCommand(parsed) || isChannelSetConfigCommand(parsed) || isChannelStartCommand(parsed) || isChannelStopCommand(parsed) || isChannelPairingsListCommand(parsed) || isChannelPairingBindCommand(parsed) || isChannelRoutesListCommand(parsed) || isChannelTargetsListCommand(parsed) || isChannelTargetBindCommand(parsed) || isChannelRouteUpdateCommand(parsed) || isChannelRouteRemoveCommand(parsed) || isExecuteCommandCommand(parsed) || isSearchBranchesCommand(parsed) || isCheckoutBranchCommand(parsed)) {
91474
92229
  return parsed;
91475
92230
  }
91476
92231
  const invalidInput = getInvalidInputReason(parsed);
@@ -91843,18 +92598,20 @@ function formatToolsetStatusMessageForModelUpdate(params) {
91843
92598
  }
91844
92599
  return "Manual toolset override remains active: " + formatToolsetName(toolsetPreference) + ".";
91845
92600
  }
91846
- function formatEffortSuffix(updateArgs) {
92601
+ function formatEffortSuffix(modelLabel, updateArgs) {
91847
92602
  if (!updateArgs)
91848
92603
  return "";
91849
92604
  const effort = updateArgs.reasoning_effort;
91850
92605
  if (typeof effort !== "string" || effort.length === 0)
91851
92606
  return "";
92607
+ const xhighLabel = modelLabel.includes("Opus 4.7") ? "Extra-High" : "Max";
91852
92608
  const labels = {
91853
92609
  none: "No Reasoning",
91854
92610
  low: "Low",
91855
92611
  medium: "Medium",
91856
92612
  high: "High",
91857
- xhigh: "Max"
92613
+ xhigh: xhighLabel,
92614
+ max: "Max"
91858
92615
  };
91859
92616
  return ` (${labels[effort] ?? effort})`;
91860
92617
  }
@@ -91867,7 +92624,7 @@ function buildModelUpdateStatusMessage(params) {
91867
92624
  toolsetPreference,
91868
92625
  updateArgs
91869
92626
  } = params;
91870
- let message = `Model updated to ${modelLabel}${formatEffortSuffix(updateArgs)}.`;
92627
+ let message = `Model updated to ${modelLabel}${formatEffortSuffix(modelLabel, updateArgs)}.`;
91871
92628
  if (toolsetError) {
91872
92629
  message += ` Warning: toolset switch failed (${toolsetError}).`;
91873
92630
  return { message, level: "warning" };
@@ -92432,6 +93189,7 @@ async function handleChannelsProtocolCommand(parsed, socket, runtime, opts, proc
92432
93189
  has_bot_token: snapshot.hasBotToken,
92433
93190
  has_app_token: snapshot.hasAppToken,
92434
93191
  agent_id: snapshot.agentId,
93192
+ default_permission_mode: snapshot.defaultPermissionMode,
92435
93193
  created_at: snapshot.createdAt,
92436
93194
  updated_at: snapshot.updatedAt
92437
93195
  };
@@ -92525,6 +93283,7 @@ async function handleChannelsProtocolCommand(parsed, socket, runtime, opts, proc
92525
93283
  appToken: "app_token" in parsed.account ? parsed.account.app_token : undefined,
92526
93284
  mode: "mode" in parsed.account ? parsed.account.mode : undefined,
92527
93285
  agentId: "agent_id" in parsed.account ? parsed.account.agent_id : undefined,
93286
+ defaultPermissionMode: "default_permission_mode" in parsed.account ? parsed.account.default_permission_mode : undefined,
92528
93287
  dmPolicy: parsed.account.dm_policy,
92529
93288
  allowedUsers: parsed.account.allowed_users
92530
93289
  }, {
@@ -92565,6 +93324,7 @@ async function handleChannelsProtocolCommand(parsed, socket, runtime, opts, proc
92565
93324
  appToken: "app_token" in parsed.patch ? parsed.patch.app_token : undefined,
92566
93325
  mode: "mode" in parsed.patch ? parsed.patch.mode : undefined,
92567
93326
  agentId: "agent_id" in parsed.patch ? parsed.patch.agent_id : undefined,
93327
+ defaultPermissionMode: "default_permission_mode" in parsed.patch ? parsed.patch.default_permission_mode : undefined,
92568
93328
  dmPolicy: parsed.patch.dm_policy,
92569
93329
  allowedUsers: parsed.patch.allowed_users
92570
93330
  });
@@ -93306,17 +94066,35 @@ function wireChannelIngress(listener, socket, opts, processQueuedTurn) {
93306
94066
  }
93307
94067
  scheduleQueuePump(conversationRuntime, socket, opts, processQueuedTurn);
93308
94068
  });
94069
+ registry.setApprovalResponseHandler(async ({ runtime, response }) => handleApprovalResponseInput(listener, {
94070
+ runtime,
94071
+ response,
94072
+ socket,
94073
+ opts,
94074
+ processQueuedTurn
94075
+ }));
93309
94076
  registry.setEventHandler((event) => {
93310
- if (event.type === "pairings_updated") {
93311
- emitChannelPairingsUpdated(socket, event.channelId);
93312
- emitChannelsUpdated(socket, event.channelId);
93313
- return;
93314
- }
93315
- emitChannelTargetsUpdated(socket, event.channelId);
93316
- emitChannelsUpdated(socket, event.channelId);
94077
+ handleChannelRegistryEvent(event, socket, listener);
93317
94078
  });
93318
94079
  registry.setReady();
93319
94080
  }
94081
+ function handleChannelRegistryEvent(event, socket, runtime) {
94082
+ if (event.type === "pairings_updated") {
94083
+ emitChannelPairingsUpdated(socket, event.channelId);
94084
+ emitChannelsUpdated(socket, event.channelId);
94085
+ return;
94086
+ }
94087
+ if (event.type === "targets_updated") {
94088
+ emitChannelTargetsUpdated(socket, event.channelId);
94089
+ emitChannelsUpdated(socket, event.channelId);
94090
+ return;
94091
+ }
94092
+ const permissionModeState = getOrCreateConversationPermissionModeStateRef(runtime, event.agentId, event.conversationId);
94093
+ permissionModeState.mode = event.defaultPermissionMode;
94094
+ permissionModeState.planFilePath = null;
94095
+ permissionModeState.modeBeforePlan = null;
94096
+ persistPermissionModeMapForRuntime(runtime);
94097
+ }
93320
94098
  function stampInboundUserMessageOtids(incoming) {
93321
94099
  let didChange = false;
93322
94100
  const messages = incoming.messages.map((payload) => {
@@ -93725,6 +94503,51 @@ async function startListenerClient(opts) {
93725
94503
  telemetry.init();
93726
94504
  await connectWithRetry(runtime, opts);
93727
94505
  }
94506
+ async function listDirectoryHybrid(absDir, indexRoot3, includeFiles) {
94507
+ let indexedNames;
94508
+ const indexedFolders = [];
94509
+ const indexedFiles = [];
94510
+ if (indexRoot3 !== undefined) {
94511
+ const relPath = path23.relative(indexRoot3, absDir);
94512
+ if (!relPath.startsWith("..")) {
94513
+ const indexed = searchFileIndex({
94514
+ searchDir: relPath || ".",
94515
+ pattern: "",
94516
+ deep: false,
94517
+ maxResults: 1e4
94518
+ });
94519
+ indexedNames = new Set;
94520
+ for (const entry of indexed) {
94521
+ const name = entry.path.split(path23.sep).pop() ?? entry.path;
94522
+ indexedNames.add(name);
94523
+ if (entry.type === "dir") {
94524
+ indexedFolders.push(name);
94525
+ } else {
94526
+ indexedFiles.push(name);
94527
+ }
94528
+ }
94529
+ }
94530
+ }
94531
+ const { readdir: readdir7 } = await import("node:fs/promises");
94532
+ const entries = await readdir7(absDir, { withFileTypes: true });
94533
+ const extraFolders = [];
94534
+ const extraFiles = [];
94535
+ for (const e of entries) {
94536
+ if (DIR_LISTING_IGNORED_NAMES.has(e.name))
94537
+ continue;
94538
+ if (indexedNames?.has(e.name))
94539
+ continue;
94540
+ if (e.isDirectory()) {
94541
+ extraFolders.push(e.name);
94542
+ } else if (includeFiles) {
94543
+ extraFiles.push(e.name);
94544
+ }
94545
+ }
94546
+ return {
94547
+ folders: [...indexedFolders, ...extraFolders].sort((a, b) => a.localeCompare(b)),
94548
+ files: includeFiles ? [...indexedFiles, ...extraFiles].sort((a, b) => a.localeCompare(b)) : []
94549
+ };
94550
+ }
93728
94551
  async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now()) {
93729
94552
  if (runtime !== getActiveRuntime() || runtime.intentionallyClosed) {
93730
94553
  return;
@@ -94045,33 +94868,21 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
94045
94868
  console.log(`[Listen] Received list_in_directory command: path=${parsed.path}`);
94046
94869
  runDetachedListenerTask("list_in_directory", async () => {
94047
94870
  try {
94048
- const { readdir: readdir7 } = await import("node:fs/promises");
94871
+ let indexRoot3;
94872
+ try {
94873
+ await ensureFileIndex2();
94874
+ indexRoot3 = getIndexRoot();
94875
+ } catch {}
94049
94876
  console.log(`[Listen] Reading directory: ${parsed.path}`);
94050
- const entries = await readdir7(parsed.path, { withFileTypes: true });
94051
- console.log(`[Listen] Directory read success, ${entries.length} entries`);
94052
- const IGNORED_NAMES = new Set([
94053
- ".DS_Store",
94054
- ".git",
94055
- ".gitignore",
94056
- "Thumbs.db"
94057
- ]);
94058
- const sortedEntries = entries.filter((e) => !IGNORED_NAMES.has(e.name)).sort((a, b) => a.name.localeCompare(b.name));
94059
- const allFolders = [];
94060
- const allFiles = [];
94061
- for (const e of sortedEntries) {
94062
- if (e.isDirectory()) {
94063
- allFolders.push(e.name);
94064
- } else if (parsed.include_files) {
94065
- allFiles.push(e.name);
94066
- }
94067
- }
94877
+ const { folders: allFolders, files: allFiles } = await listDirectoryHybrid(parsed.path, indexRoot3, !!parsed.include_files);
94068
94878
  const total = allFolders.length + allFiles.length;
94069
94879
  const offset = parsed.offset ?? 0;
94070
94880
  const limit2 = parsed.limit ?? total;
94071
94881
  const combined = [...allFolders, ...allFiles];
94072
94882
  const page = combined.slice(offset, offset + limit2);
94073
- const folders = page.filter((name) => allFolders.includes(name));
94074
- const files = page.filter((name) => allFiles.includes(name));
94883
+ const folderSet = new Set(allFolders);
94884
+ const folders = page.filter((name) => folderSet.has(name));
94885
+ const files = page.filter((name) => !folderSet.has(name));
94075
94886
  const response = {
94076
94887
  type: "list_in_directory_response",
94077
94888
  path: parsed.path,
@@ -94102,12 +94913,77 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
94102
94913
  });
94103
94914
  return;
94104
94915
  }
94916
+ if (isGetTreeCommand(parsed)) {
94917
+ console.log(`[Listen] Received get_tree command: path=${parsed.path}, depth=${parsed.depth}`);
94918
+ runDetachedListenerTask("get_tree", async () => {
94919
+ try {
94920
+ const results = [];
94921
+ let hasMoreDepth = false;
94922
+ let indexRoot3;
94923
+ try {
94924
+ await ensureFileIndex2();
94925
+ indexRoot3 = getIndexRoot();
94926
+ } catch {}
94927
+ const queue = [[parsed.path, "", 0]];
94928
+ let qi = 0;
94929
+ while (qi < queue.length) {
94930
+ const item = queue[qi++];
94931
+ if (!item)
94932
+ break;
94933
+ const [absDir, relDir, depth] = item;
94934
+ if (depth >= parsed.depth) {
94935
+ if (depth === parsed.depth && relDir !== "") {
94936
+ hasMoreDepth = true;
94937
+ }
94938
+ continue;
94939
+ }
94940
+ let listing;
94941
+ try {
94942
+ listing = await listDirectoryHybrid(absDir, indexRoot3, true);
94943
+ } catch {
94944
+ continue;
94945
+ }
94946
+ for (const name of listing.folders) {
94947
+ const entryRel = relDir === "" ? name : `${relDir}/${name}`;
94948
+ results.push({ path: entryRel, type: "dir" });
94949
+ queue.push([path23.join(absDir, name), entryRel, depth + 1]);
94950
+ }
94951
+ for (const name of listing.files) {
94952
+ const entryRel = relDir === "" ? name : `${relDir}/${name}`;
94953
+ results.push({ path: entryRel, type: "file" });
94954
+ }
94955
+ }
94956
+ console.log(`[Listen] Sending get_tree_response: ${results.length} entries, has_more_depth=${hasMoreDepth}`);
94957
+ safeSocketSend(socket, {
94958
+ type: "get_tree_response",
94959
+ path: parsed.path,
94960
+ request_id: parsed.request_id,
94961
+ entries: results,
94962
+ has_more_depth: hasMoreDepth,
94963
+ success: true
94964
+ }, "listener_get_tree_send_failed", "listener_get_tree");
94965
+ } catch (err) {
94966
+ trackListenerError("listener_get_tree_failed", err, "listener_file_browser");
94967
+ console.error(`[Listen] get_tree error: ${err instanceof Error ? err.message : "Unknown error"}`);
94968
+ safeSocketSend(socket, {
94969
+ type: "get_tree_response",
94970
+ path: parsed.path,
94971
+ request_id: parsed.request_id,
94972
+ entries: [],
94973
+ has_more_depth: false,
94974
+ success: false,
94975
+ error: err instanceof Error ? err.message : "Failed to get tree"
94976
+ }, "listener_get_tree_send_failed", "listener_get_tree");
94977
+ }
94978
+ });
94979
+ return;
94980
+ }
94105
94981
  if (isReadFileCommand(parsed)) {
94106
94982
  console.log(`[Listen] Received read_file command: path=${parsed.path}, request_id=${parsed.request_id}`);
94107
94983
  runDetachedListenerTask("read_file", async () => {
94108
94984
  try {
94109
- const { readFile: readFile11 } = await import("node:fs/promises");
94110
- const content = await readFile11(parsed.path, "utf-8");
94985
+ const { readFile: readFile12 } = await import("node:fs/promises");
94986
+ const content = await readFile12(parsed.path, "utf-8");
94111
94987
  console.log(`[Listen] read_file success: ${parsed.path} (${content.length} bytes)`);
94112
94988
  safeSocketSend(socket, {
94113
94989
  type: "read_file_response",
@@ -94137,10 +95013,10 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
94137
95013
  try {
94138
95014
  const { edit: edit2 } = await Promise.resolve().then(() => (init_Edit2(), exports_Edit));
94139
95015
  const { write: write2 } = await Promise.resolve().then(() => (init_Write2(), exports_Write));
94140
- const { readFile: readFile11 } = await import("node:fs/promises");
95016
+ const { readFile: readFile12 } = await import("node:fs/promises");
94141
95017
  let currentContent = null;
94142
95018
  try {
94143
- currentContent = await readFile11(parsed.path, "utf-8");
95019
+ currentContent = await readFile12(parsed.path, "utf-8");
94144
95020
  } catch (readErr) {
94145
95021
  const e = readErr;
94146
95022
  if (e.code !== "ENOENT")
@@ -94162,6 +95038,7 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
94162
95038
  }
94163
95039
  }
94164
95040
  console.log(`[Listen] write_file success: ${parsed.path} (${parsed.content.length} bytes)`);
95041
+ refreshFileIndex();
94165
95042
  safeSocketSend(socket, {
94166
95043
  type: "write_file_response",
94167
95044
  request_id: parsed.request_id,
@@ -94247,7 +95124,7 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
94247
95124
  console.log(`[Listen] Received edit_file command: file_path=${parsed.file_path}, request_id=${parsed.request_id}`);
94248
95125
  runDetachedListenerTask("edit_file", async () => {
94249
95126
  try {
94250
- const { readFile: readFile11 } = await import("node:fs/promises");
95127
+ const { readFile: readFile12 } = await import("node:fs/promises");
94251
95128
  const { edit: edit2 } = await Promise.resolve().then(() => (init_Edit2(), exports_Edit));
94252
95129
  console.log(`[Listen] Executing edit: old_string="${parsed.old_string.slice(0, 50)}${parsed.old_string.length > 50 ? "..." : ""}"`);
94253
95130
  const result = await edit2({
@@ -94258,9 +95135,12 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
94258
95135
  expected_replacements: parsed.expected_replacements
94259
95136
  });
94260
95137
  console.log(`[Listen] edit_file success: ${result.replacements} replacement(s) at line ${result.startLine}`);
95138
+ if (result.replacements > 0) {
95139
+ refreshFileIndex();
95140
+ }
94261
95141
  if (result.replacements > 0) {
94262
95142
  try {
94263
- const contentAfter = await readFile11(parsed.file_path, "utf-8");
95143
+ const contentAfter = await readFile12(parsed.file_path, "utf-8");
94264
95144
  safeSocketSend(socket, {
94265
95145
  type: "file_ops",
94266
95146
  path: parsed.file_path,
@@ -94892,7 +95772,7 @@ function createLegacyTestRuntime() {
94892
95772
  }
94893
95773
  return bridge;
94894
95774
  }
94895
- var channelsServiceLoaderOverride = null, WIKI_LINK_REGEX, __listenClientTestUtils;
95775
+ var channelsServiceLoaderOverride = null, WIKI_LINK_REGEX, DIR_LISTING_IGNORED_NAMES, __listenClientTestUtils;
94896
95776
  var init_client4 = __esm(async () => {
94897
95777
  init_available_models();
94898
95778
  init_client2();
@@ -94939,6 +95819,7 @@ var init_client4 = __esm(async () => {
94939
95819
  init_protocol_outbound()
94940
95820
  ]);
94941
95821
  WIKI_LINK_REGEX = /\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g;
95822
+ DIR_LISTING_IGNORED_NAMES = new Set([".DS_Store", ".git", "Thumbs.db"]);
94942
95823
  __listenClientTestUtils = {
94943
95824
  setChannelsServiceLoaderForTests: (loader) => {
94944
95825
  channelsServiceLoaderOverride = loader;
@@ -94994,6 +95875,7 @@ var init_client4 = __esm(async () => {
94994
95875
  handleListMemoryCommand,
94995
95876
  isDetachedChannelsCommand,
94996
95877
  handleChannelsProtocolCommand,
95878
+ handleChannelRegistryEvent,
94997
95879
  handleSkillCommand,
94998
95880
  handleCreateAgentCommand,
94999
95881
  handleReflectionSettingsCommand,
@@ -95173,7 +96055,7 @@ __export(exports_skills2, {
95173
96055
  GLOBAL_SKILLS_DIR: () => GLOBAL_SKILLS_DIR2
95174
96056
  });
95175
96057
  import { existsSync as existsSync28 } from "node:fs";
95176
- import { readdir as readdir8, readFile as readFile11, realpath as realpath4, stat as stat7 } from "node:fs/promises";
96058
+ import { readdir as readdir8, readFile as readFile12, realpath as realpath4, stat as stat7 } from "node:fs/promises";
95177
96059
  import { dirname as dirname13, join as join35 } from "node:path";
95178
96060
  import { fileURLToPath as fileURLToPath8 } from "node:url";
95179
96061
  function getBundledSkillsPath2() {
@@ -95304,7 +96186,7 @@ async function findSkillFiles2(currentPath, rootPath, skills, errors, source, vi
95304
96186
  }
95305
96187
  }
95306
96188
  async function parseSkillFile2(filePath, rootPath, source) {
95307
- const content = await readFile11(filePath, "utf-8");
96189
+ const content = await readFile12(filePath, "utf-8");
95308
96190
  const { frontmatter, body } = parseFrontmatter(content);
95309
96191
  const normalizedRoot = rootPath.endsWith("/") ? rootPath.slice(0, -1) : rootPath;
95310
96192
  const relativePath = filePath.slice(normalizedRoot.length + 1);
@@ -95363,7 +96245,7 @@ __export(exports_fs, {
95363
96245
  writeJsonFile: () => writeJsonFile,
95364
96246
  writeFile: () => writeFile10,
95365
96247
  readJsonFile: () => readJsonFile,
95366
- readFile: () => readFile12,
96248
+ readFile: () => readFile13,
95367
96249
  mkdir: () => mkdir9,
95368
96250
  exists: () => exists2
95369
96251
  });
@@ -95374,7 +96256,7 @@ import {
95374
96256
  mkdirSync as mkdirSync21
95375
96257
  } from "node:fs";
95376
96258
  import { dirname as dirname14 } from "node:path";
95377
- async function readFile12(path24) {
96259
+ async function readFile13(path24) {
95378
96260
  return fsReadFileSync2(path24, { encoding: "utf-8" });
95379
96261
  }
95380
96262
  async function writeFile10(path24, content) {
@@ -95391,7 +96273,7 @@ async function mkdir9(path24, options) {
95391
96273
  mkdirSync21(path24, options);
95392
96274
  }
95393
96275
  async function readJsonFile(path24) {
95394
- const text = await readFile12(path24);
96276
+ const text = await readFile13(path24);
95395
96277
  return JSON.parse(text);
95396
96278
  }
95397
96279
  async function writeJsonFile(path24, data, options) {
@@ -97230,7 +98112,7 @@ __export(exports_import, {
97230
98112
  extractSkillsFromAf: () => extractSkillsFromAf
97231
98113
  });
97232
98114
  import { createReadStream } from "node:fs";
97233
- import { chmod, mkdir as mkdir10, readFile as readFile13, writeFile as writeFile11 } from "node:fs/promises";
98115
+ import { chmod, mkdir as mkdir10, readFile as readFile14, writeFile as writeFile11 } from "node:fs/promises";
97234
98116
  import { dirname as dirname15, resolve as resolve28 } from "node:path";
97235
98117
  async function importAgentFromFile(options) {
97236
98118
  const client = await getClient();
@@ -97263,7 +98145,7 @@ async function importAgentFromFile(options) {
97263
98145
  }
97264
98146
  async function extractSkillsFromAf(afPath, destDir) {
97265
98147
  const extracted = [];
97266
- const content = await readFile13(afPath, "utf-8");
98148
+ const content = await readFile14(afPath, "utf-8");
97267
98149
  const afData = JSON.parse(content);
97268
98150
  if (!afData.skills || !Array.isArray(afData.skills)) {
97269
98151
  return [];
@@ -100147,10 +101029,10 @@ var init_headless = __esm(async () => {
100147
101029
  init_toolset()
100148
101030
  ]);
100149
101031
  PROVIDER_FALLBACK_MAP = {
100150
- opus: "bedrock-opus-4.6",
100151
101032
  "opus-4.6-no-reasoning": "bedrock-opus-4.6",
100152
101033
  "opus-4.6-low": "bedrock-opus-4.6",
100153
101034
  "opus-4.6-medium": "bedrock-opus-4.6",
101035
+ "opus-4.6-high": "bedrock-opus-4.6",
100154
101036
  "opus-4.6-xhigh": "bedrock-opus-4.6",
100155
101037
  sonnet: "bedrock-sonnet-4.6",
100156
101038
  "sonnet-1m": "bedrock-sonnet-4.6",
@@ -124160,7 +125042,7 @@ __export(exports_custom, {
124160
125042
  COMMANDS_DIR: () => COMMANDS_DIR
124161
125043
  });
124162
125044
  import { existsSync as existsSync36 } from "node:fs";
124163
- import { readdir as readdir10, readFile as readFile14 } from "node:fs/promises";
125045
+ import { readdir as readdir10, readFile as readFile15 } from "node:fs/promises";
124164
125046
  import { basename as basename11, dirname as dirname17, join as join44 } from "node:path";
124165
125047
  async function getCustomCommands() {
124166
125048
  if (cachedCommands !== null) {
@@ -124219,7 +125101,7 @@ async function findCommandFiles(currentPath, rootPath, commands2, source2) {
124219
125101
  } catch (_error) {}
124220
125102
  }
124221
125103
  async function parseCommandFile(filePath, rootPath, source2) {
124222
- const content = await readFile14(filePath, "utf-8");
125104
+ const content = await readFile15(filePath, "utf-8");
124223
125105
  const { frontmatter, body } = parseFrontmatter(content);
124224
125106
  const id = basename11(filePath, ".md");
124225
125107
  const relativePath = dirname17(filePath).slice(rootPath.length);
@@ -127008,6 +127890,8 @@ function formatReasoningLabel(effort) {
127008
127890
  if (effort === "none")
127009
127891
  return null;
127010
127892
  if (effort === "xhigh")
127893
+ return "xhigh";
127894
+ if (effort === "max")
127011
127895
  return "max";
127012
127896
  if (effort === "minimal")
127013
127897
  return "minimal";
@@ -128046,6 +128930,8 @@ function getReasoningEffortTag(effort) {
128046
128930
  if (effort === "none")
128047
128931
  return null;
128048
128932
  if (effort === "xhigh")
128933
+ return "xhigh";
128934
+ if (effort === "max")
128049
128935
  return "max";
128050
128936
  if (effort === "minimal")
128051
128937
  return "minimal";
@@ -135673,10 +136559,12 @@ var init_MessageSearch = __esm(async () => {
135673
136559
  });
135674
136560
 
135675
136561
  // src/cli/components/ModelReasoningSelector.tsx
135676
- function formatEffortLabel(effort) {
136562
+ function formatEffortLabel(effort, hasDistinctMaxTier) {
135677
136563
  if (effort === "none")
135678
136564
  return "Off";
135679
136565
  if (effort === "xhigh")
136566
+ return hasDistinctMaxTier ? "Extra-High" : "Max";
136567
+ if (effort === "max")
135680
136568
  return "Max";
135681
136569
  if (effort === "minimal")
135682
136570
  return "Minimal";
@@ -135703,6 +136591,7 @@ function ModelReasoningSelector({
135703
136591
  }, [options, initialModelId]);
135704
136592
  const selectedOption = options[selectedIndex] ?? options[0];
135705
136593
  const effortOptions = import_react82.useMemo(() => options.filter((option) => option.effort !== "none"), [options]);
136594
+ const hasDistinctMaxTier = import_react82.useMemo(() => options.some((option) => option.effort === "max"), [options]);
135706
136595
  const totalBars = Math.max(effortOptions.length, 1);
135707
136596
  const selectedBars = import_react82.useMemo(() => {
135708
136597
  if (!selectedOption)
@@ -135741,7 +136630,7 @@ function ModelReasoningSelector({
135741
136630
  setSelectedIndex((prev) => (prev + 1) % options.length);
135742
136631
  }
135743
136632
  });
135744
- const effortLabel = selectedOption ? formatEffortLabel(selectedOption.effort) : "Medium";
136633
+ const effortLabel = selectedOption ? formatEffortLabel(selectedOption.effort, hasDistinctMaxTier) : "Medium";
135745
136634
  const selectedText = selectedBars > 0 ? EFFORT_BLOCK.repeat(selectedBars) : "";
135746
136635
  const remainingBars = totalBars > selectedBars ? EFFORT_BLOCK.repeat(totalBars - selectedBars) : "";
135747
136636
  return /* @__PURE__ */ jsx_dev_runtime59.jsxDEV(Box_default, {
@@ -136944,7 +137833,7 @@ var init_PersonalitySelector = __esm(async () => {
136944
137833
  });
136945
137834
 
136946
137835
  // src/utils/aws-credentials.ts
136947
- import { readFile as readFile15 } from "node:fs/promises";
137836
+ import { readFile as readFile16 } from "node:fs/promises";
136948
137837
  import { homedir as homedir36 } from "node:os";
136949
137838
  import { join as join48 } from "node:path";
136950
137839
  async function parseAwsCredentials() {
@@ -136952,11 +137841,11 @@ async function parseAwsCredentials() {
136952
137841
  const configPath = join48(homedir36(), ".aws", "config");
136953
137842
  const profiles = new Map;
136954
137843
  try {
136955
- const content = await readFile15(credentialsPath, "utf-8");
137844
+ const content = await readFile16(credentialsPath, "utf-8");
136956
137845
  parseIniFile(content, profiles, false);
136957
137846
  } catch {}
136958
137847
  try {
136959
- const content = await readFile15(configPath, "utf-8");
137848
+ const content = await readFile16(configPath, "utf-8");
136960
137849
  parseIniFile(content, profiles, true);
136961
137850
  } catch {}
136962
137851
  return Array.from(profiles.values());
@@ -143621,7 +144510,7 @@ var exports_export = {};
143621
144510
  __export(exports_export, {
143622
144511
  packageSkills: () => packageSkills
143623
144512
  });
143624
- import { readdir as readdir11, readFile as readFile16 } from "node:fs/promises";
144513
+ import { readdir as readdir11, readFile as readFile17 } from "node:fs/promises";
143625
144514
  import { relative as relative17, resolve as resolve32 } from "node:path";
143626
144515
  async function packageSkills(agentId, skillsDir) {
143627
144516
  const skills = [];
@@ -143642,7 +144531,7 @@ async function packageSkills(agentId, skillsDir) {
143642
144531
  const skillDir = resolve32(baseDir, entry.name);
143643
144532
  const skillMdPath = resolve32(skillDir, "SKILL.md");
143644
144533
  try {
143645
- await readFile16(skillMdPath, "utf-8");
144534
+ await readFile17(skillMdPath, "utf-8");
143646
144535
  } catch {
143647
144536
  console.warn(`Skipping invalid skill ${entry.name}: missing SKILL.md`);
143648
144537
  continue;
@@ -143674,7 +144563,7 @@ async function readSkillFiles(skillDir) {
143674
144563
  if (entry.isDirectory()) {
143675
144564
  await walk(fullPath);
143676
144565
  } else {
143677
- const content = await readFile16(fullPath, "utf-8");
144566
+ const content = await readFile17(fullPath, "utf-8");
143678
144567
  const relativePath = relative17(skillDir, fullPath).replace(/\\/g, "/");
143679
144568
  files[relativePath] = content;
143680
144569
  }
@@ -143828,12 +144717,12 @@ function deriveReasoningEffort(modelSettings, llmConfig) {
143828
144717
  const effort = modelSettings.effort;
143829
144718
  if (effort === "low" || effort === "medium" || effort === "high")
143830
144719
  return effort;
143831
- if (effort === "max")
143832
- return "xhigh";
144720
+ if (effort === "xhigh" || effort === "max")
144721
+ return effort;
143833
144722
  }
143834
144723
  }
143835
144724
  const re = llmConfig?.reasoning_effort;
143836
- if (re === "none" || re === "minimal" || re === "low" || re === "medium" || re === "high" || re === "xhigh")
144725
+ if (re === "none" || re === "minimal" || re === "low" || re === "medium" || re === "high" || re === "xhigh" || re === "max")
143837
144726
  return re;
143838
144727
  if (llmConfig?.enable_reasoner === false)
143839
144728
  return "none";
@@ -143842,7 +144731,7 @@ function deriveReasoningEffort(modelSettings, llmConfig) {
143842
144731
  function inferReasoningEffortFromModelPreset(modelId, modelHandle) {
143843
144732
  const modelInfo = (modelId ? getModelInfo2(modelId) : null) ?? (modelHandle ? getModelInfo2(modelHandle) : null);
143844
144733
  const presetEffort = modelInfo?.updateArgs?.reasoning_effort;
143845
- if (presetEffort === "none" || presetEffort === "minimal" || presetEffort === "low" || presetEffort === "medium" || presetEffort === "high" || presetEffort === "xhigh") {
144734
+ if (presetEffort === "none" || presetEffort === "minimal" || presetEffort === "low" || presetEffort === "medium" || presetEffort === "high" || presetEffort === "xhigh" || presetEffort === "max") {
143846
144735
  return presetEffort;
143847
144736
  }
143848
144737
  return null;
@@ -143883,7 +144772,8 @@ function getErrorHintForStopReason(stopReason, currentModelId, modelEndpointType
143883
144772
  }
143884
144773
  const isAutoModel = currentModelId?.startsWith("auto") ?? false;
143885
144774
  const statusInfo = modelEndpointType && !isAutoModel ? PROVIDER_STATUS_PAGES[modelEndpointType] : undefined;
143886
- const hasBedrockOpus = currentModelId === "opus" && modelEndpointType === "anthropic" && getModelInfo2("bedrock-opus-4.6");
144775
+ const isOpus46 = currentModelId?.startsWith("opus-4.6") ?? false;
144776
+ const hasBedrockOpus = isOpus46 && modelEndpointType === "anthropic" && getModelInfo2("bedrock-opus-4.6");
143887
144777
  const modelSwapSuffix = hasBedrockOpus ? " (e.g. Opus 4.6 via Amazon Bedrock)" : "";
143888
144778
  if (statusInfo) {
143889
144779
  return [
@@ -151231,7 +152121,7 @@ ${SYSTEM_REMINDER_CLOSE}
151231
152121
  const modelHandle = model.handle ?? model.id;
151232
152122
  const modelUpdateArgs = model.updateArgs;
151233
152123
  const rawReasoningEffort = modelUpdateArgs?.reasoning_effort;
151234
- const reasoningLevel = typeof rawReasoningEffort === "string" ? rawReasoningEffort === "none" ? "no" : rawReasoningEffort === "xhigh" ? "max" : rawReasoningEffort : modelUpdateArgs?.enable_reasoner === false ? "no" : null;
152124
+ const reasoningLevel = typeof rawReasoningEffort === "string" ? rawReasoningEffort === "none" ? "no" : rawReasoningEffort === "xhigh" ? model.label.includes("Opus 4.7") ? "extra-high" : "max" : rawReasoningEffort : modelUpdateArgs?.enable_reasoner === false ? "no" : null;
151235
152125
  const selectedContextWindow = model.updateArgs?.context_window;
151236
152126
  const reasoningTierOptions = getReasoningTierOptionsForHandle3(modelHandle, selectedContextWindow);
151237
152127
  if (!opts?.skipReasoningPrompt && activeOverlay === "model" && reasoningTierOptions.length > 1) {
@@ -152007,7 +152897,16 @@ ${guidance}`);
152007
152897
  }).filter((m) => Boolean(m.effort));
152008
152898
  if (tiers.length < 2)
152009
152899
  return;
152010
- const order = ["none", "minimal", "low", "medium", "high", "xhigh"];
152900
+ const anthropicXHighEffort = modelHandle.includes("claude-opus-4-7") ? "xhigh" : "max";
152901
+ const order = [
152902
+ "none",
152903
+ "minimal",
152904
+ "low",
152905
+ "medium",
152906
+ "high",
152907
+ "xhigh",
152908
+ "max"
152909
+ ];
152011
152910
  const rank = (effort) => {
152012
152911
  const idx = order.indexOf(effort);
152013
152912
  return idx >= 0 ? idx : 999;
@@ -152044,12 +152943,11 @@ ${guidance}`);
152044
152943
  };
152045
152944
  }
152046
152945
  if (ms.provider_type === "anthropic" || ms.provider_type === "bedrock") {
152047
- const anthropicEffort = next.effort === "xhigh" ? "max" : next.effort;
152048
152946
  return {
152049
152947
  ...prev,
152050
152948
  model_settings: {
152051
152949
  ...ms,
152052
- effort: anthropicEffort
152950
+ effort: next.effort === "xhigh" ? anthropicXHighEffort : next.effort
152053
152951
  }
152054
152952
  };
152055
152953
  }
@@ -154443,7 +155341,7 @@ __export(exports_import2, {
154443
155341
  extractSkillsFromAf: () => extractSkillsFromAf2
154444
155342
  });
154445
155343
  import { createReadStream as createReadStream2 } from "node:fs";
154446
- import { chmod as chmod2, mkdir as mkdir11, readFile as readFile17, writeFile as writeFile12 } from "node:fs/promises";
155344
+ import { chmod as chmod2, mkdir as mkdir11, readFile as readFile18, writeFile as writeFile12 } from "node:fs/promises";
154447
155345
  import { dirname as dirname20, resolve as resolve33 } from "node:path";
154448
155346
  async function importAgentFromFile2(options) {
154449
155347
  const client = await getClient();
@@ -154476,7 +155374,7 @@ async function importAgentFromFile2(options) {
154476
155374
  }
154477
155375
  async function extractSkillsFromAf2(afPath, destDir) {
154478
155376
  const extracted = [];
154479
- const content = await readFile17(afPath, "utf-8");
155377
+ const content = await readFile18(afPath, "utf-8");
154480
155378
  const afData = JSON.parse(content);
154481
155379
  if (!afData.skills || !Array.isArray(afData.skills)) {
154482
155380
  return [];
@@ -155490,6 +156388,9 @@ init_openai_codex_provider();
155490
156388
  init_debug();
155491
156389
  init_available_models();
155492
156390
  init_client2();
156391
+ function supportsDistinctAnthropicXHighEffort(modelHandle) {
156392
+ return modelHandle.includes("claude-opus-4-7");
156393
+ }
155493
156394
  function buildModelSettings(modelHandle, updateArgs) {
155494
156395
  const isOpenAI = modelHandle.startsWith("openai/") || modelHandle.startsWith(`${OPENAI_CODEX_PROVIDER_NAME}/`);
155495
156396
  const isAnthropic = modelHandle.startsWith("anthropic/") || modelHandle.startsWith("claude-pro-max/") || modelHandle.startsWith("minimax/");
@@ -155523,10 +156424,13 @@ function buildModelSettings(modelHandle, updateArgs) {
155523
156424
  parallel_tool_calls: true
155524
156425
  };
155525
156426
  const effort = updateArgs?.reasoning_effort;
156427
+ const hasDistinctXHigh = supportsDistinctAnthropicXHighEffort(modelHandle);
155526
156428
  if (effort === "low" || effort === "medium" || effort === "high") {
155527
156429
  anthropicSettings.effort = effort;
155528
156430
  } else if (effort === "xhigh") {
155529
- anthropicSettings.effort = "max";
156431
+ anthropicSettings.effort = hasDistinctXHigh ? "xhigh" : "max";
156432
+ } else if (effort === "max") {
156433
+ anthropicSettings.effort = effort;
155530
156434
  }
155531
156435
  if (updateArgs?.enable_reasoner !== undefined || typeof updateArgs?.max_reasoning_tokens === "number") {
155532
156436
  anthropicSettings.thinking = {
@@ -155579,10 +156483,13 @@ function buildModelSettings(modelHandle, updateArgs) {
155579
156483
  parallel_tool_calls: true
155580
156484
  };
155581
156485
  const effort = updateArgs?.reasoning_effort;
156486
+ const hasDistinctXHigh = supportsDistinctAnthropicXHighEffort(modelHandle);
155582
156487
  if (effort === "low" || effort === "medium" || effort === "high") {
155583
156488
  bedrockSettings.effort = effort;
155584
156489
  } else if (effort === "xhigh") {
155585
- bedrockSettings.effort = "max";
156490
+ bedrockSettings.effort = hasDistinctXHigh ? "xhigh" : "max";
156491
+ } else if (effort === "max") {
156492
+ bedrockSettings.effort = effort;
155586
156493
  }
155587
156494
  if (updateArgs?.enable_reasoner !== undefined || typeof updateArgs?.max_reasoning_tokens === "number") {
155588
156495
  bedrockSettings.thinking = {
@@ -158410,8 +159317,14 @@ Usage:
158410
159317
  letta channels route list [--channel <ch>] Show routing table
158411
159318
  letta channels route add [options] Add a route
158412
159319
  letta channels route remove [options] Remove a route
159320
+ letta channels bind [options] Bind a Slack app to an agent
158413
159321
  letta channels pair [options] Approve pairing + bind to agent
158414
159322
 
159323
+ Bind options (Slack only):
159324
+ --channel slack Required
159325
+ --account-id <id> Channel account ID (optional; inferred when only one account exists)
159326
+ --agent <id> Agent ID (defaults to LETTA_AGENT_ID)
159327
+
158415
159328
  Route add options:
158416
159329
  --channel <name> Channel name (e.g. "telegram")
158417
159330
  --account-id <id> Channel account ID (required when multiple accounts exist)
@@ -158705,6 +159618,46 @@ async function handlePair(values) {
158705
159618
  }
158706
159619
  return result.success ? 0 : 1;
158707
159620
  }
159621
+ function handleBind(values) {
159622
+ const channelId = values.channel;
159623
+ const accountId = values["account-id"];
159624
+ const agentId = getAgentId3(values.agent);
159625
+ if (!channelId) {
159626
+ console.error("Error: --channel is required.");
159627
+ return 1;
159628
+ }
159629
+ if (channelId !== "slack") {
159630
+ console.error(`"bind" is only supported for Slack. Telegram binding is route-scoped — use "pair" or "route add" instead.`);
159631
+ return 1;
159632
+ }
159633
+ if (!agentId) {
159634
+ console.error("Error: --agent is required (or set LETTA_AGENT_ID env var).");
159635
+ return 1;
159636
+ }
159637
+ let resolvedAccountId;
159638
+ try {
159639
+ resolvedAccountId = resolveSelectedAccountId(channelId, accountId);
159640
+ } catch (error) {
159641
+ console.error(error instanceof Error ? error.message : String(error));
159642
+ return 1;
159643
+ }
159644
+ const account = getChannelAccount(channelId, resolvedAccountId);
159645
+ if (!account) {
159646
+ console.error(`Account "${resolvedAccountId}" not found for channel "${channelId}".`);
159647
+ return 1;
159648
+ }
159649
+ account.agentId = agentId;
159650
+ account.updatedAt = new Date().toISOString();
159651
+ upsertChannelAccount(channelId, account);
159652
+ console.log(JSON.stringify({
159653
+ success: true,
159654
+ channel: channelId,
159655
+ accountId: resolvedAccountId,
159656
+ agentId
159657
+ }, null, 2));
159658
+ console.warn("Note: If a listener is running, restart it for the binding to take effect.");
159659
+ return 0;
159660
+ }
158708
159661
  async function runChannelsSubcommand(argv) {
158709
159662
  const { values, positionals } = parseChannelsArgs(argv);
158710
159663
  if (values.help) {
@@ -158745,6 +159698,8 @@ async function runChannelsSubcommand(argv) {
158745
159698
  return 1;
158746
159699
  }
158747
159700
  }
159701
+ case "bind":
159702
+ return handleBind(values);
158748
159703
  case "pair":
158749
159704
  return await handlePair(values);
158750
159705
  default:
@@ -158752,7 +159707,7 @@ async function runChannelsSubcommand(argv) {
158752
159707
  printUsage3();
158753
159708
  return 0;
158754
159709
  }
158755
- console.error(`Unknown channels action: "${action}". Use: install, configure, status, route, pair`);
159710
+ console.error(`Unknown channels action: "${action}". Use: install, configure, status, route, bind, pair`);
158756
159711
  return 1;
158757
159712
  }
158758
159713
  }
@@ -163877,4 +164832,4 @@ Error during initialization: ${message}`);
163877
164832
  }
163878
164833
  main();
163879
164834
 
163880
- //# debugId=E826850D234138B664756E2164756E21
164835
+ //# debugId=13707EE7EA6B933364756E2164756E21