@openacp/cli 2026.401.2 → 2026.401.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -291,6 +291,7 @@ interface SessionRecord<P = Record<string, unknown>> {
291
291
  interface TelegramPlatformData {
292
292
  topicId: number;
293
293
  skillMsgId?: number;
294
+ controlMsgId?: number;
294
295
  }
295
296
  interface UsageRecord {
296
297
  id: string;
@@ -2016,6 +2017,9 @@ interface EventBusEvents {
2016
2017
  attachments?: unknown[];
2017
2018
  }) => void;
2018
2019
  "usage:recorded": (data: UsageRecordEvent) => void;
2020
+ "session:configChanged": (data: {
2021
+ sessionId: string;
2022
+ }) => void;
2019
2023
  "session:agentSwitch": (data: {
2020
2024
  sessionId: string;
2021
2025
  fromAgent: string;
@@ -3243,6 +3247,12 @@ declare class TelegramAdapter extends MessagingAdapter {
3243
3247
  private callbackCounter;
3244
3248
  /** Pending skill commands queued when session.threadId was not yet set */
3245
3249
  private _pendingSkillCommands;
3250
+ /** Control message IDs per session (for updating status text/buttons) */
3251
+ private controlMsgIds;
3252
+ /** Store control message ID in memory + persist to session record */
3253
+ private storeControlMsgId;
3254
+ /** Get control message ID (from Map, with fallback to session record) */
3255
+ private getControlMsgId;
3246
3256
  private getThreadId;
3247
3257
  private getOrCreateTracker;
3248
3258
  constructor(core: OpenACPCore, config: TelegramChannelConfig, saveTopicIds?: (updates: {
@@ -3284,6 +3294,12 @@ declare class TelegramAdapter extends MessagingAdapter {
3284
3294
  protected handleUsage(sessionId: string, content: OutgoingMessage, verbosity: DisplayVerbosity): Promise<void>;
3285
3295
  protected handleAttachment(sessionId: string, content: OutgoingMessage): Promise<void>;
3286
3296
  protected handleSessionEnd(sessionId: string, _content: OutgoingMessage): Promise<void>;
3297
+ protected handleConfigUpdate(sessionId: string, _content: OutgoingMessage): Promise<void>;
3298
+ /**
3299
+ * Edit the pinned control message to reflect current session state
3300
+ * (model, thought level, mode, bypass status).
3301
+ */
3302
+ updateControlMessage(sessionId: string): Promise<void>;
3287
3303
  protected handleError(sessionId: string, content: OutgoingMessage): Promise<void>;
3288
3304
  protected handleSystem(sessionId: string, content: OutgoingMessage): Promise<void>;
3289
3305
  sendPermissionRequest(sessionId: string, request: PermissionRequest): Promise<void>;
package/dist/index.js CHANGED
@@ -295,6 +295,7 @@ function resolveInstanceRoot(opts) {
295
295
  if (opts.global) return path2.join(os2.homedir(), ".openacp");
296
296
  const localRoot = path2.join(cwd, ".openacp");
297
297
  if (fs2.existsSync(localRoot)) return localRoot;
298
+ if (process.env.OPENACP_INSTANCE_ROOT) return process.env.OPENACP_INSTANCE_ROOT;
298
299
  return null;
299
300
  }
300
301
  function getGlobalRoot() {
@@ -12317,31 +12318,38 @@ init_log();
12317
12318
  import { InlineKeyboard } from "grammy";
12318
12319
  init_log();
12319
12320
  var log18 = createChildLogger({ module: "telegram-cmd-admin" });
12321
+ function isBypassActive(session) {
12322
+ const modeOpt = session.getConfigByCategory("mode");
12323
+ return modeOpt?.type === "select" && isPermissionBypass(String(modeOpt.currentValue)) || !!session.clientOverrides.bypassPermissions;
12324
+ }
12320
12325
  function setupDangerousModeCallbacks(bot, core) {
12321
12326
  bot.callbackQuery(/^d:/, async (ctx) => {
12322
12327
  const sessionId = ctx.callbackQuery.data.slice(2);
12323
12328
  const session = core.sessionManager.getSession(sessionId);
12324
12329
  if (session) {
12325
- const newDangerousMode2 = !session.clientOverrides.bypassPermissions;
12326
- session.clientOverrides.bypassPermissions = newDangerousMode2;
12327
- log18.info(
12328
- { sessionId, dangerousMode: newDangerousMode2 },
12329
- "Bypass permissions toggled via button"
12330
- );
12331
- core.sessionManager.patchRecord(sessionId, { clientOverrides: session.clientOverrides }).catch(() => {
12332
- });
12333
- const toastText2 = newDangerousMode2 ? "\u2620\uFE0F Bypass enabled \u2014 permissions auto-approved" : "\u{1F510} Bypass disabled \u2014 approvals required";
12330
+ const wantOn = !isBypassActive(session);
12331
+ const toastText2 = wantOn ? "\u2620\uFE0F Bypass Permissions enabled \u2014 permissions auto-approved" : "\u{1F510} Bypass Permissions disabled \u2014 approvals required";
12334
12332
  try {
12335
12333
  await ctx.answerCallbackQuery({ text: toastText2 });
12336
12334
  } catch {
12337
12335
  }
12336
+ const registry = core.lifecycleManager?.serviceRegistry?.get("command-registry");
12337
+ if (registry) {
12338
+ await registry.execute(wantOn ? "/bypass on" : "/bypass off", {
12339
+ raw: wantOn ? "on" : "off",
12340
+ sessionId,
12341
+ channelId: "telegram",
12342
+ userId: String(ctx.from?.id ?? ""),
12343
+ reply: async () => {
12344
+ }
12345
+ }).catch(() => {
12346
+ });
12347
+ }
12348
+ log18.info({ sessionId, wantOn }, "Bypass permissions toggled via button");
12338
12349
  try {
12339
- await ctx.editMessageReplyMarkup({
12340
- reply_markup: buildSessionControlKeyboard(
12341
- sessionId,
12342
- newDangerousMode2,
12343
- session.voiceMode === "on"
12344
- )
12350
+ await ctx.editMessageText(buildSessionStatusText(session), {
12351
+ parse_mode: "HTML",
12352
+ reply_markup: buildSessionControlKeyboard(sessionId, isBypassActive(session), session.voiceMode === "on")
12345
12353
  });
12346
12354
  } catch {
12347
12355
  }
@@ -12364,7 +12372,7 @@ function setupDangerousModeCallbacks(bot, core) {
12364
12372
  { sessionId, dangerousMode: newDangerousMode },
12365
12373
  "Bypass permissions toggled via button (store-only, session not in memory)"
12366
12374
  );
12367
- const toastText = newDangerousMode ? "\u2620\uFE0F Bypass enabled \u2014 permissions auto-approved" : "\u{1F510} Bypass disabled \u2014 approvals required";
12375
+ const toastText = newDangerousMode ? "\u2620\uFE0F Bypass Permissions enabled \u2014 permissions auto-approved" : "\u{1F510} Bypass Permissions disabled \u2014 approvals required";
12368
12376
  try {
12369
12377
  await ctx.answerCallbackQuery({ text: toastText });
12370
12378
  } catch {
@@ -12424,7 +12432,7 @@ async function handleEnableDangerous(ctx, core) {
12424
12432
  });
12425
12433
  }
12426
12434
  await ctx.reply(
12427
- `\u2620\uFE0F <b>Bypass enabled</b>
12435
+ `\u2620\uFE0F <b>Bypass Permissions enabled</b>
12428
12436
 
12429
12437
  All permission requests will be auto-approved \u2014 the agent can run any action without asking.
12430
12438
 
@@ -12475,19 +12483,42 @@ async function handleDisableDangerous(ctx, core) {
12475
12483
  });
12476
12484
  }
12477
12485
  await ctx.reply(
12478
- "\u{1F510} <b>Bypass disabled</b>\n\nYou will be asked to approve risky actions.",
12486
+ "\u{1F510} <b>Bypass Permissions disabled</b>\n\nYou will be asked to approve risky actions.",
12479
12487
  { parse_mode: "HTML" }
12480
12488
  );
12481
12489
  }
12482
12490
  function buildSessionControlKeyboard(sessionId, dangerousMode, voiceMode) {
12483
12491
  return new InlineKeyboard().text(
12484
- dangerousMode ? "\u{1F510} Disable Bypass" : "\u2620\uFE0F Enable Bypass",
12492
+ dangerousMode ? "\u{1F510} Disable Bypass Permissions" : "\u2620\uFE0F Enable Bypass Permissions",
12485
12493
  `d:${sessionId}`
12486
12494
  ).row().text(
12487
12495
  voiceMode ? "\u{1F50A} Text to Speech" : "\u{1F507} Text to Speech",
12488
12496
  `v:${sessionId}`
12489
12497
  );
12490
12498
  }
12499
+ function buildSessionStatusText(session, heading = "\u2705 New chat (same agent &amp; workspace)") {
12500
+ const lines = [heading];
12501
+ lines.push(`<b>Agent:</b> ${escapeHtml(session.agentName)}`);
12502
+ lines.push(`<b>Workspace:</b> <code>${escapeHtml(session.workingDirectory)}</code>`);
12503
+ const modelOpt = session.getConfigByCategory("model");
12504
+ if (modelOpt && modelOpt.type === "select") {
12505
+ const choice = modelOpt.options.flatMap((o) => "group" in o ? o.options : [o]).find((c2) => c2.value === modelOpt.currentValue);
12506
+ lines.push(`<b>Model:</b> ${escapeHtml(choice?.name ?? modelOpt.currentValue)}`);
12507
+ }
12508
+ const thoughtOpt = session.getConfigByCategory("thought_level");
12509
+ if (thoughtOpt && thoughtOpt.type === "select") {
12510
+ const choice = thoughtOpt.options.flatMap((o) => "group" in o ? o.options : [o]).find((c2) => c2.value === thoughtOpt.currentValue);
12511
+ lines.push(`<b>Thinking:</b> ${escapeHtml(choice?.name ?? thoughtOpt.currentValue)}`);
12512
+ }
12513
+ const modeOpt = session.getConfigByCategory("mode");
12514
+ if (isBypassActive(session)) {
12515
+ lines.push(`<b>Mode:</b> \u2620\uFE0F Bypass Permissions enabled`);
12516
+ } else if (modeOpt && modeOpt.type === "select") {
12517
+ const choice = modeOpt.options.flatMap((o) => "group" in o ? o.options : [o]).find((c2) => c2.value === modeOpt.currentValue);
12518
+ lines.push(`<b>Mode:</b> ${escapeHtml(choice?.name ?? modeOpt.currentValue)}`);
12519
+ }
12520
+ return lines.join("\n");
12521
+ }
12491
12522
  function setupTTSCallbacks(bot, core) {
12492
12523
  bot.callbackQuery(/^v:/, async (ctx) => {
12493
12524
  const sessionId = ctx.callbackQuery.data.slice(2);
@@ -12518,12 +12549,14 @@ function setupTTSCallbacks(bot, core) {
12518
12549
  } catch {
12519
12550
  }
12520
12551
  try {
12521
- await ctx.editMessageReplyMarkup({
12522
- reply_markup: buildSessionControlKeyboard(
12523
- sessionId,
12524
- session.clientOverrides.bypassPermissions ?? false,
12525
- newMode === "on"
12526
- )
12552
+ const keyboard = buildSessionControlKeyboard(
12553
+ sessionId,
12554
+ isBypassActive(session),
12555
+ newMode === "on"
12556
+ );
12557
+ await ctx.editMessageText(buildSessionStatusText(session), {
12558
+ parse_mode: "HTML",
12559
+ reply_markup: keyboard
12527
12560
  });
12528
12561
  } catch {
12529
12562
  }
@@ -12747,14 +12780,14 @@ function cleanupPending(userId) {
12747
12780
  function botFromCtx(ctx) {
12748
12781
  return { api: ctx.api };
12749
12782
  }
12750
- async function handleNew(ctx, core, chatId, assistant) {
12783
+ async function handleNew(ctx, core, chatId, assistant, onControlMessage) {
12751
12784
  const rawMatch = ctx.match;
12752
12785
  const matchStr = typeof rawMatch === "string" ? rawMatch : "";
12753
12786
  const args = matchStr.split(" ").filter(Boolean);
12754
12787
  const agentName = args[0];
12755
12788
  const workspace = args[1];
12756
12789
  if (agentName && workspace) {
12757
- await createSessionDirect(ctx, core, chatId, agentName, workspace);
12790
+ await createSessionDirect(ctx, core, chatId, agentName, workspace, onControlMessage);
12758
12791
  return;
12759
12792
  }
12760
12793
  const currentThreadId = ctx.message?.message_thread_id;
@@ -12832,7 +12865,7 @@ async function startConfirmStep(ctx, chatId, userId, agentName, workspace) {
12832
12865
  timer: setTimeout(() => pendingNewSessions.delete(userId), PENDING_TIMEOUT_MS)
12833
12866
  });
12834
12867
  }
12835
- async function createSessionDirect(ctx, core, chatId, agentName, workspace) {
12868
+ async function createSessionDirect(ctx, core, chatId, agentName, workspace, onControlMessage) {
12836
12869
  log19.info({ userId: ctx.from?.id, agentName, workspace }, "New session command (direct)");
12837
12870
  let threadId;
12838
12871
  try {
@@ -12850,19 +12883,19 @@ async function createSessionDirect(ctx, core, chatId, agentName, workspace) {
12850
12883
  await ctx.api.editForumTopic(chatId, threadId, { name: finalName });
12851
12884
  } catch {
12852
12885
  }
12853
- await ctx.api.sendMessage(
12886
+ const controlMsg = await ctx.api.sendMessage(
12854
12887
  chatId,
12855
- `\u2705 <b>Session started</b>
12856
- <b>Agent:</b> ${escapeHtml(session.agentName)}
12857
- <b>Workspace:</b> <code>${escapeHtml(session.workingDirectory)}</code>
12858
-
12859
- This is your coding session \u2014 chat here to work with the agent.`,
12888
+ buildSessionStatusText(session, `\u2705 <b>Session started</b>`),
12860
12889
  {
12861
12890
  message_thread_id: threadId,
12862
12891
  parse_mode: "HTML",
12863
12892
  reply_markup: buildSessionControlKeyboard(session.id, false, false)
12864
12893
  }
12865
12894
  );
12895
+ onControlMessage?.(session.id, controlMsg.message_id);
12896
+ await core.sessionManager.patchRecord(session.id, {
12897
+ platform: { topicId: threadId }
12898
+ });
12866
12899
  return threadId ?? null;
12867
12900
  } catch (err) {
12868
12901
  log19.error({ err }, "Session creation failed");
@@ -12877,7 +12910,7 @@ This is your coding session \u2014 chat here to work with the agent.`,
12877
12910
  return null;
12878
12911
  }
12879
12912
  }
12880
- async function handleNewChat(ctx, core, chatId) {
12913
+ async function handleNewChat(ctx, core, chatId, onControlMessage) {
12881
12914
  const threadId = ctx.message?.message_thread_id;
12882
12915
  if (!threadId) {
12883
12916
  await ctx.reply(
@@ -12932,17 +12965,19 @@ async function handleNewChat(ctx, core, chatId) {
12932
12965
  await core.sessionManager.patchRecord(session.id, { platform: {
12933
12966
  topicId: newThreadId
12934
12967
  } });
12935
- await ctx.api.sendMessage(
12968
+ const controlMsg = await ctx.api.sendMessage(
12936
12969
  chatId,
12937
- `\u2705 New chat (same agent &amp; workspace)
12938
- <b>Agent:</b> ${escapeHtml(session.agentName)}
12939
- <b>Workspace:</b> <code>${escapeHtml(session.workingDirectory)}</code>`,
12970
+ buildSessionStatusText(session, `\u2705 New chat (same agent &amp; workspace)`),
12940
12971
  {
12941
12972
  message_thread_id: newThreadId,
12942
12973
  parse_mode: "HTML",
12943
12974
  reply_markup: buildSessionControlKeyboard(session.id, false, false)
12944
12975
  }
12945
12976
  );
12977
+ onControlMessage?.(session.id, controlMsg.message_id);
12978
+ await core.sessionManager.patchRecord(session.id, {
12979
+ platform: { topicId: newThreadId }
12980
+ });
12946
12981
  } catch (err) {
12947
12982
  if (newThreadId) {
12948
12983
  try {
@@ -13935,7 +13970,7 @@ Choose the repo that has Entire checkpoints enabled:`;
13935
13970
  timer: setTimeout(() => pendingResumes.delete(userId), PENDING_TIMEOUT_MS2)
13936
13971
  });
13937
13972
  }
13938
- async function executeResume(ctx, core, chatId, query, repoPath) {
13973
+ async function executeResume(ctx, core, chatId, query, repoPath, onControlMessage) {
13939
13974
  const provider = await core.contextManager.getProvider(repoPath);
13940
13975
  if (!provider) {
13941
13976
  await ctx.reply(
@@ -13993,22 +14028,23 @@ Repo: <code>${escapeHtml(repoPath)}</code>`,
13993
14028
  { parse_mode: "HTML" }
13994
14029
  );
13995
14030
  }
13996
- await ctx.api.sendMessage(
13997
- chatId,
13998
- `\u2705 <b>Session resumed with context</b>
13999
- <b>Agent:</b> ${escapeHtml(session.agentName)}
14000
- <b>Workspace:</b> <code>${escapeHtml(session.workingDirectory)}</code>
14031
+ const resumeHeading = `\u2705 <b>Session resumed with context</b>
14001
14032
  <b>Sessions loaded:</b> ${sessionCount}
14002
14033
  <b>Mode:</b> ${escapeHtml(mode)}
14003
- <b>~Tokens:</b> ${tokens.toLocaleString()}
14004
-
14005
- Context is ready \u2014 chat here to continue working with the agent.`,
14034
+ <b>~Tokens:</b> ${tokens.toLocaleString()}`;
14035
+ const controlMsg = await ctx.api.sendMessage(
14036
+ chatId,
14037
+ buildSessionStatusText(session, resumeHeading),
14006
14038
  {
14007
14039
  message_thread_id: threadId,
14008
14040
  parse_mode: "HTML",
14009
14041
  reply_markup: buildSessionControlKeyboard(session.id, false, false)
14010
14042
  }
14011
14043
  );
14044
+ onControlMessage?.(session.id, controlMsg.message_id);
14045
+ await core.sessionManager.patchRecord(session.id, {
14046
+ platform: { topicId: threadId }
14047
+ });
14012
14048
  } catch (err) {
14013
14049
  log21.error({ err }, "Resume session creation failed");
14014
14050
  if (threadId) {
@@ -14021,7 +14057,7 @@ Context is ready \u2014 chat here to continue working with the agent.`,
14021
14057
  await ctx.reply(`\u274C ${escapeHtml(message)}`, { parse_mode: "HTML" });
14022
14058
  }
14023
14059
  }
14024
- async function handleResume(ctx, core, chatId, assistant) {
14060
+ async function handleResume(ctx, core, chatId, assistant, onControlMessage) {
14025
14061
  const rawMatch = ctx.match;
14026
14062
  const matchStr = typeof rawMatch === "string" ? rawMatch : "";
14027
14063
  const parsed = parseResumeArgs(matchStr);
@@ -14046,7 +14082,7 @@ Usage examples:
14046
14082
  if (!userId) return;
14047
14083
  await showWorkspacePicker(ctx, core, chatId, userId, query);
14048
14084
  }
14049
- async function handlePendingResumeInput(ctx, core, chatId, assistantTopicId) {
14085
+ async function handlePendingResumeInput(ctx, core, chatId, assistantTopicId, onControlMessage) {
14050
14086
  const userId = ctx.from?.id;
14051
14087
  if (!userId) return false;
14052
14088
  const pending = pendingResumes.get(userId);
@@ -14066,10 +14102,10 @@ async function handlePendingResumeInput(ctx, core, chatId, assistantTopicId) {
14066
14102
  }
14067
14103
  const resolved = core.configManager.resolveWorkspace(workspace);
14068
14104
  cleanupPending2(userId);
14069
- await executeResume(ctx, core, chatId, pending.query, resolved);
14105
+ await executeResume(ctx, core, chatId, pending.query, resolved, onControlMessage);
14070
14106
  return true;
14071
14107
  }
14072
- function setupResumeCallbacks(bot, core, chatId) {
14108
+ function setupResumeCallbacks(bot, core, chatId, onControlMessage) {
14073
14109
  bot.callbackQuery(/^m:resume:/, async (ctx) => {
14074
14110
  const data = ctx.callbackQuery.data;
14075
14111
  const userId = ctx.from?.id;
@@ -14088,7 +14124,7 @@ function setupResumeCallbacks(bot, core, chatId) {
14088
14124
  await ctx.api.editMessageText(chatId, pending.messageId, `\u23F3 Using <code>${escapeHtml(resolved)}</code>...`, { parse_mode: "HTML" });
14089
14125
  } catch {
14090
14126
  }
14091
- await executeResume(ctx, core, chatId, pending.query, resolved);
14127
+ await executeResume(ctx, core, chatId, pending.query, resolved, onControlMessage);
14092
14128
  return;
14093
14129
  }
14094
14130
  if (data === "m:resume:ws:custom") {
@@ -14119,7 +14155,7 @@ Or just the folder name (will use workspace baseDir)`,
14119
14155
  await ctx.api.editMessageText(chatId, pending.messageId, `\u23F3 Using <code>${escapeHtml(resolved)}</code>...`, { parse_mode: "HTML" });
14120
14156
  } catch {
14121
14157
  }
14122
- await executeResume(ctx, core, chatId, pending.query, resolved);
14158
+ await executeResume(ctx, core, chatId, pending.query, resolved, onControlMessage);
14123
14159
  return;
14124
14160
  }
14125
14161
  });
@@ -14722,8 +14758,9 @@ Switch to <b>${escapeHtml(agentName)}</b> anyway?`,
14722
14758
  init_menu();
14723
14759
  init_menu();
14724
14760
  function setupCommands(bot, core, chatId, assistant) {
14725
- bot.command("new", (ctx) => handleNew(ctx, core, chatId, assistant));
14726
- bot.command("newchat", (ctx) => handleNewChat(ctx, core, chatId));
14761
+ const onControlMessage = assistant?.setControlMessage;
14762
+ bot.command("new", (ctx) => handleNew(ctx, core, chatId, assistant, onControlMessage));
14763
+ bot.command("newchat", (ctx) => handleNewChat(ctx, core, chatId, onControlMessage));
14727
14764
  bot.command("cancel", (ctx) => handleCancel(ctx, core, assistant));
14728
14765
  bot.command("status", (ctx) => handleStatus(ctx, core));
14729
14766
  bot.command("sessions", (ctx) => handleTopics(ctx, core));
@@ -14744,12 +14781,12 @@ function setupCommands(bot, core, chatId, assistant) {
14744
14781
  bot.command("text_to_speech", (ctx) => handleTTS(ctx, core));
14745
14782
  bot.command("verbosity", (ctx) => handleVerbosity(ctx, core));
14746
14783
  bot.command("outputmode", (ctx) => handleOutputMode(ctx, core));
14747
- bot.command("resume", (ctx) => handleResume(ctx, core, chatId, assistant));
14784
+ bot.command("resume", (ctx) => handleResume(ctx, core, chatId, assistant, onControlMessage));
14748
14785
  bot.command("switch", (ctx) => handleSwitch(ctx, core));
14749
14786
  }
14750
- function setupAllCallbacks(bot, core, chatId, systemTopicIds, getAssistantSession) {
14787
+ function setupAllCallbacks(bot, core, chatId, systemTopicIds, getAssistantSession, onControlMessage) {
14751
14788
  setupNewSessionCallbacks(bot, core, chatId);
14752
- setupResumeCallbacks(bot, core, chatId);
14789
+ setupResumeCallbacks(bot, core, chatId, onControlMessage);
14753
14790
  setupSessionCallbacks(bot, core, chatId, systemTopicIds);
14754
14791
  setupSettingsCallbacks(bot, core, getAssistantSession ?? (() => void 0));
14755
14792
  setupDoctorCallbacks(bot);
@@ -16128,6 +16165,29 @@ var TelegramAdapter = class extends MessagingAdapter {
16128
16165
  callbackCounter = 0;
16129
16166
  /** Pending skill commands queued when session.threadId was not yet set */
16130
16167
  _pendingSkillCommands = /* @__PURE__ */ new Map();
16168
+ /** Control message IDs per session (for updating status text/buttons) */
16169
+ controlMsgIds = /* @__PURE__ */ new Map();
16170
+ /** Store control message ID in memory + persist to session record */
16171
+ storeControlMsgId(sessionId, msgId) {
16172
+ this.storeControlMsgId(sessionId, msgId);
16173
+ this.core.sessionManager.patchRecord(sessionId, {
16174
+ platform: { controlMsgId: msgId }
16175
+ }).catch(() => {
16176
+ });
16177
+ }
16178
+ /** Get control message ID (from Map, with fallback to session record) */
16179
+ getControlMsgId(sessionId) {
16180
+ let msgId = this.controlMsgIds.get(sessionId);
16181
+ if (!msgId) {
16182
+ const record = this.core.sessionManager.getSessionRecord(sessionId);
16183
+ const platform2 = record?.platform;
16184
+ if (platform2?.controlMsgId) {
16185
+ msgId = platform2.controlMsgId;
16186
+ this.storeControlMsgId(sessionId, msgId);
16187
+ }
16188
+ }
16189
+ return msgId;
16190
+ }
16131
16191
  getThreadId(sessionId) {
16132
16192
  const threadId = this._sessionThreadIds.get(sessionId);
16133
16193
  if (threadId === void 0) {
@@ -16334,7 +16394,26 @@ var TelegramAdapter = class extends MessagingAdapter {
16334
16394
  });
16335
16395
  await ctx.answerCallbackQuery();
16336
16396
  if (response.type !== "silent") {
16337
- await this.renderCommandResponse(response, chatId, topicId);
16397
+ if (response.type === "menu") {
16398
+ const keyboard = response.options.map((opt) => [
16399
+ {
16400
+ text: `${opt.label}${opt.hint ? ` \u2014 ${opt.hint}` : ""}`,
16401
+ callback_data: this.toCallbackData(opt.command)
16402
+ }
16403
+ ]);
16404
+ try {
16405
+ await ctx.editMessageText(response.title, {
16406
+ reply_markup: { inline_keyboard: keyboard }
16407
+ });
16408
+ } catch {
16409
+ }
16410
+ } else if (response.type === "text" || response.type === "error") {
16411
+ const text3 = response.type === "text" ? response.text : `\u274C ${response.message}`;
16412
+ try {
16413
+ await ctx.editMessageText(text3, { parse_mode: "Markdown" });
16414
+ } catch {
16415
+ }
16416
+ }
16338
16417
  }
16339
16418
  } catch {
16340
16419
  await ctx.answerCallbackQuery({ text: "Command failed" });
@@ -16364,6 +16443,9 @@ var TelegramAdapter = class extends MessagingAdapter {
16364
16443
  topicId: this.assistantTopicId,
16365
16444
  enqueuePrompt: (p) => this.assistantSession.enqueuePrompt(p)
16366
16445
  };
16446
+ },
16447
+ (sessionId, msgId) => {
16448
+ this.storeControlMsgId(sessionId, msgId);
16367
16449
  }
16368
16450
  );
16369
16451
  setupCommands(
@@ -16373,6 +16455,9 @@ var TelegramAdapter = class extends MessagingAdapter {
16373
16455
  {
16374
16456
  topicId: this.assistantTopicId,
16375
16457
  getSession: () => this.assistantSession,
16458
+ setControlMessage: (sessionId, msgId) => {
16459
+ this.storeControlMsgId(sessionId, msgId);
16460
+ },
16376
16461
  respawn: async () => {
16377
16462
  if (this.assistantSession) {
16378
16463
  await this.assistantSession.destroy();
@@ -16436,6 +16521,10 @@ var TelegramAdapter = class extends MessagingAdapter {
16436
16521
  }
16437
16522
  );
16438
16523
  });
16524
+ this.core.eventBus.on("session:configChanged", ({ sessionId }) => {
16525
+ this.updateControlMessage(sessionId).catch(() => {
16526
+ });
16527
+ });
16439
16528
  this.setupRoutes();
16440
16529
  this.bot.start({
16441
16530
  allowed_updates: ["message", "callback_query"],
@@ -16640,7 +16729,10 @@ ${lines.join("\n")}`;
16640
16729
  ctx,
16641
16730
  this.core,
16642
16731
  this.telegramConfig.chatId,
16643
- this.assistantTopicId
16732
+ this.assistantTopicId,
16733
+ (sessionId2, msgId) => {
16734
+ this.storeControlMsgId(sessionId2, msgId);
16735
+ }
16644
16736
  )) {
16645
16737
  return;
16646
16738
  }
@@ -17019,6 +17111,41 @@ Task completed.
17019
17111
  );
17020
17112
  }
17021
17113
  }
17114
+ async handleConfigUpdate(sessionId, _content) {
17115
+ await this.updateControlMessage(sessionId);
17116
+ }
17117
+ /**
17118
+ * Edit the pinned control message to reflect current session state
17119
+ * (model, thought level, mode, bypass status).
17120
+ */
17121
+ async updateControlMessage(sessionId) {
17122
+ const session = this.core.sessionManager.getSession(sessionId);
17123
+ if (!session) return;
17124
+ const controlMsgId = this.getControlMsgId(sessionId);
17125
+ if (!controlMsgId) return;
17126
+ const threadId = Number(session.threadId);
17127
+ if (!threadId || isNaN(threadId)) return;
17128
+ const text3 = buildSessionStatusText(session);
17129
+ const keyboard = buildSessionControlKeyboard(
17130
+ sessionId,
17131
+ isBypassActive(session),
17132
+ session.voiceMode === "on"
17133
+ );
17134
+ try {
17135
+ await this.bot.api.editMessageText(
17136
+ this.telegramConfig.chatId,
17137
+ controlMsgId,
17138
+ text3,
17139
+ { parse_mode: "HTML" }
17140
+ );
17141
+ await this.bot.api.editMessageReplyMarkup(
17142
+ this.telegramConfig.chatId,
17143
+ controlMsgId,
17144
+ { reply_markup: keyboard }
17145
+ );
17146
+ } catch {
17147
+ }
17148
+ }
17022
17149
  async handleError(sessionId, content) {
17023
17150
  this.getTracer(sessionId)?.log("telegram", { action: "handle:error", sessionId, text: content.text });
17024
17151
  const threadId = this.getThreadId(sessionId);