@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/cli.js CHANGED
@@ -7493,6 +7493,7 @@ function resolveInstanceRoot(opts) {
7493
7493
  if (opts.global) return path21.join(os10.homedir(), ".openacp");
7494
7494
  const localRoot = path21.join(cwd, ".openacp");
7495
7495
  if (fs17.existsSync(localRoot)) return localRoot;
7496
+ if (process.env.OPENACP_INSTANCE_ROOT) return process.env.OPENACP_INSTANCE_ROOT;
7496
7497
  return null;
7497
7498
  }
7498
7499
  function getGlobalRoot() {
@@ -10060,33 +10061,53 @@ var init_formatting = __esm({
10060
10061
  }
10061
10062
  });
10062
10063
 
10064
+ // src/core/utils/bypass-detection.ts
10065
+ function isPermissionBypass(value) {
10066
+ const lower = value.toLowerCase();
10067
+ return BYPASS_KEYWORDS.some((kw) => lower.includes(kw));
10068
+ }
10069
+ var BYPASS_KEYWORDS;
10070
+ var init_bypass_detection = __esm({
10071
+ "src/core/utils/bypass-detection.ts"() {
10072
+ "use strict";
10073
+ BYPASS_KEYWORDS = ["bypass", "dangerous", "skip", "dontask", "dont_ask", "auto_accept"];
10074
+ }
10075
+ });
10076
+
10063
10077
  // src/plugins/telegram/commands/admin.ts
10064
10078
  import { InlineKeyboard } from "grammy";
10079
+ function isBypassActive(session) {
10080
+ const modeOpt = session.getConfigByCategory("mode");
10081
+ return modeOpt?.type === "select" && isPermissionBypass(String(modeOpt.currentValue)) || !!session.clientOverrides.bypassPermissions;
10082
+ }
10065
10083
  function setupDangerousModeCallbacks(bot, core) {
10066
10084
  bot.callbackQuery(/^d:/, async (ctx) => {
10067
10085
  const sessionId = ctx.callbackQuery.data.slice(2);
10068
10086
  const session = core.sessionManager.getSession(sessionId);
10069
10087
  if (session) {
10070
- const newDangerousMode2 = !session.clientOverrides.bypassPermissions;
10071
- session.clientOverrides.bypassPermissions = newDangerousMode2;
10072
- log14.info(
10073
- { sessionId, dangerousMode: newDangerousMode2 },
10074
- "Bypass permissions toggled via button"
10075
- );
10076
- core.sessionManager.patchRecord(sessionId, { clientOverrides: session.clientOverrides }).catch(() => {
10077
- });
10078
- const toastText2 = newDangerousMode2 ? "\u2620\uFE0F Bypass enabled \u2014 permissions auto-approved" : "\u{1F510} Bypass disabled \u2014 approvals required";
10088
+ const wantOn = !isBypassActive(session);
10089
+ const toastText2 = wantOn ? "\u2620\uFE0F Bypass Permissions enabled \u2014 permissions auto-approved" : "\u{1F510} Bypass Permissions disabled \u2014 approvals required";
10079
10090
  try {
10080
10091
  await ctx.answerCallbackQuery({ text: toastText2 });
10081
10092
  } catch {
10082
10093
  }
10094
+ const registry = core.lifecycleManager?.serviceRegistry?.get("command-registry");
10095
+ if (registry) {
10096
+ await registry.execute(wantOn ? "/bypass on" : "/bypass off", {
10097
+ raw: wantOn ? "on" : "off",
10098
+ sessionId,
10099
+ channelId: "telegram",
10100
+ userId: String(ctx.from?.id ?? ""),
10101
+ reply: async () => {
10102
+ }
10103
+ }).catch(() => {
10104
+ });
10105
+ }
10106
+ log14.info({ sessionId, wantOn }, "Bypass permissions toggled via button");
10083
10107
  try {
10084
- await ctx.editMessageReplyMarkup({
10085
- reply_markup: buildSessionControlKeyboard(
10086
- sessionId,
10087
- newDangerousMode2,
10088
- session.voiceMode === "on"
10089
- )
10108
+ await ctx.editMessageText(buildSessionStatusText(session), {
10109
+ parse_mode: "HTML",
10110
+ reply_markup: buildSessionControlKeyboard(sessionId, isBypassActive(session), session.voiceMode === "on")
10090
10111
  });
10091
10112
  } catch {
10092
10113
  }
@@ -10109,7 +10130,7 @@ function setupDangerousModeCallbacks(bot, core) {
10109
10130
  { sessionId, dangerousMode: newDangerousMode },
10110
10131
  "Bypass permissions toggled via button (store-only, session not in memory)"
10111
10132
  );
10112
- const toastText = newDangerousMode ? "\u2620\uFE0F Bypass enabled \u2014 permissions auto-approved" : "\u{1F510} Bypass disabled \u2014 approvals required";
10133
+ const toastText = newDangerousMode ? "\u2620\uFE0F Bypass Permissions enabled \u2014 permissions auto-approved" : "\u{1F510} Bypass Permissions disabled \u2014 approvals required";
10113
10134
  try {
10114
10135
  await ctx.answerCallbackQuery({ text: toastText });
10115
10136
  } catch {
@@ -10169,7 +10190,7 @@ async function handleEnableDangerous(ctx, core) {
10169
10190
  });
10170
10191
  }
10171
10192
  await ctx.reply(
10172
- `\u2620\uFE0F <b>Bypass enabled</b>
10193
+ `\u2620\uFE0F <b>Bypass Permissions enabled</b>
10173
10194
 
10174
10195
  All permission requests will be auto-approved \u2014 the agent can run any action without asking.
10175
10196
 
@@ -10220,19 +10241,42 @@ async function handleDisableDangerous(ctx, core) {
10220
10241
  });
10221
10242
  }
10222
10243
  await ctx.reply(
10223
- "\u{1F510} <b>Bypass disabled</b>\n\nYou will be asked to approve risky actions.",
10244
+ "\u{1F510} <b>Bypass Permissions disabled</b>\n\nYou will be asked to approve risky actions.",
10224
10245
  { parse_mode: "HTML" }
10225
10246
  );
10226
10247
  }
10227
10248
  function buildSessionControlKeyboard(sessionId, dangerousMode, voiceMode) {
10228
10249
  return new InlineKeyboard().text(
10229
- dangerousMode ? "\u{1F510} Disable Bypass" : "\u2620\uFE0F Enable Bypass",
10250
+ dangerousMode ? "\u{1F510} Disable Bypass Permissions" : "\u2620\uFE0F Enable Bypass Permissions",
10230
10251
  `d:${sessionId}`
10231
10252
  ).row().text(
10232
10253
  voiceMode ? "\u{1F50A} Text to Speech" : "\u{1F507} Text to Speech",
10233
10254
  `v:${sessionId}`
10234
10255
  );
10235
10256
  }
10257
+ function buildSessionStatusText(session, heading = "\u2705 New chat (same agent &amp; workspace)") {
10258
+ const lines = [heading];
10259
+ lines.push(`<b>Agent:</b> ${escapeHtml4(session.agentName)}`);
10260
+ lines.push(`<b>Workspace:</b> <code>${escapeHtml4(session.workingDirectory)}</code>`);
10261
+ const modelOpt = session.getConfigByCategory("model");
10262
+ if (modelOpt && modelOpt.type === "select") {
10263
+ const choice = modelOpt.options.flatMap((o) => "group" in o ? o.options : [o]).find((c3) => c3.value === modelOpt.currentValue);
10264
+ lines.push(`<b>Model:</b> ${escapeHtml4(choice?.name ?? modelOpt.currentValue)}`);
10265
+ }
10266
+ const thoughtOpt = session.getConfigByCategory("thought_level");
10267
+ if (thoughtOpt && thoughtOpt.type === "select") {
10268
+ const choice = thoughtOpt.options.flatMap((o) => "group" in o ? o.options : [o]).find((c3) => c3.value === thoughtOpt.currentValue);
10269
+ lines.push(`<b>Thinking:</b> ${escapeHtml4(choice?.name ?? thoughtOpt.currentValue)}`);
10270
+ }
10271
+ const modeOpt = session.getConfigByCategory("mode");
10272
+ if (isBypassActive(session)) {
10273
+ lines.push(`<b>Mode:</b> \u2620\uFE0F Bypass Permissions enabled`);
10274
+ } else if (modeOpt && modeOpt.type === "select") {
10275
+ const choice = modeOpt.options.flatMap((o) => "group" in o ? o.options : [o]).find((c3) => c3.value === modeOpt.currentValue);
10276
+ lines.push(`<b>Mode:</b> ${escapeHtml4(choice?.name ?? modeOpt.currentValue)}`);
10277
+ }
10278
+ return lines.join("\n");
10279
+ }
10236
10280
  function setupTTSCallbacks(bot, core) {
10237
10281
  bot.callbackQuery(/^v:/, async (ctx) => {
10238
10282
  const sessionId = ctx.callbackQuery.data.slice(2);
@@ -10263,12 +10307,14 @@ function setupTTSCallbacks(bot, core) {
10263
10307
  } catch {
10264
10308
  }
10265
10309
  try {
10266
- await ctx.editMessageReplyMarkup({
10267
- reply_markup: buildSessionControlKeyboard(
10268
- sessionId,
10269
- session.clientOverrides.bypassPermissions ?? false,
10270
- newMode === "on"
10271
- )
10310
+ const keyboard = buildSessionControlKeyboard(
10311
+ sessionId,
10312
+ isBypassActive(session),
10313
+ newMode === "on"
10314
+ );
10315
+ await ctx.editMessageText(buildSessionStatusText(session), {
10316
+ parse_mode: "HTML",
10317
+ reply_markup: keyboard
10272
10318
  });
10273
10319
  } catch {
10274
10320
  }
@@ -10476,6 +10522,7 @@ var log14, OUTPUT_MODE_LABELS;
10476
10522
  var init_admin = __esm({
10477
10523
  "src/plugins/telegram/commands/admin.ts"() {
10478
10524
  "use strict";
10525
+ init_bypass_detection();
10479
10526
  init_formatting();
10480
10527
  init_log();
10481
10528
  log14 = createChildLogger({ module: "telegram-cmd-admin" });
@@ -10499,14 +10546,14 @@ function cleanupPending(userId) {
10499
10546
  function botFromCtx(ctx) {
10500
10547
  return { api: ctx.api };
10501
10548
  }
10502
- async function handleNew(ctx, core, chatId, assistant) {
10549
+ async function handleNew(ctx, core, chatId, assistant, onControlMessage) {
10503
10550
  const rawMatch = ctx.match;
10504
10551
  const matchStr = typeof rawMatch === "string" ? rawMatch : "";
10505
10552
  const args2 = matchStr.split(" ").filter(Boolean);
10506
10553
  const agentName = args2[0];
10507
10554
  const workspace = args2[1];
10508
10555
  if (agentName && workspace) {
10509
- await createSessionDirect(ctx, core, chatId, agentName, workspace);
10556
+ await createSessionDirect(ctx, core, chatId, agentName, workspace, onControlMessage);
10510
10557
  return;
10511
10558
  }
10512
10559
  const currentThreadId = ctx.message?.message_thread_id;
@@ -10584,7 +10631,7 @@ async function startConfirmStep(ctx, chatId, userId, agentName, workspace) {
10584
10631
  timer: setTimeout(() => pendingNewSessions.delete(userId), PENDING_TIMEOUT_MS)
10585
10632
  });
10586
10633
  }
10587
- async function createSessionDirect(ctx, core, chatId, agentName, workspace) {
10634
+ async function createSessionDirect(ctx, core, chatId, agentName, workspace, onControlMessage) {
10588
10635
  log15.info({ userId: ctx.from?.id, agentName, workspace }, "New session command (direct)");
10589
10636
  let threadId;
10590
10637
  try {
@@ -10602,19 +10649,19 @@ async function createSessionDirect(ctx, core, chatId, agentName, workspace) {
10602
10649
  await ctx.api.editForumTopic(chatId, threadId, { name: finalName });
10603
10650
  } catch {
10604
10651
  }
10605
- await ctx.api.sendMessage(
10652
+ const controlMsg = await ctx.api.sendMessage(
10606
10653
  chatId,
10607
- `\u2705 <b>Session started</b>
10608
- <b>Agent:</b> ${escapeHtml4(session.agentName)}
10609
- <b>Workspace:</b> <code>${escapeHtml4(session.workingDirectory)}</code>
10610
-
10611
- This is your coding session \u2014 chat here to work with the agent.`,
10654
+ buildSessionStatusText(session, `\u2705 <b>Session started</b>`),
10612
10655
  {
10613
10656
  message_thread_id: threadId,
10614
10657
  parse_mode: "HTML",
10615
10658
  reply_markup: buildSessionControlKeyboard(session.id, false, false)
10616
10659
  }
10617
10660
  );
10661
+ onControlMessage?.(session.id, controlMsg.message_id);
10662
+ await core.sessionManager.patchRecord(session.id, {
10663
+ platform: { topicId: threadId }
10664
+ });
10618
10665
  return threadId ?? null;
10619
10666
  } catch (err) {
10620
10667
  log15.error({ err }, "Session creation failed");
@@ -10629,7 +10676,7 @@ This is your coding session \u2014 chat here to work with the agent.`,
10629
10676
  return null;
10630
10677
  }
10631
10678
  }
10632
- async function handleNewChat(ctx, core, chatId) {
10679
+ async function handleNewChat(ctx, core, chatId, onControlMessage) {
10633
10680
  const threadId = ctx.message?.message_thread_id;
10634
10681
  if (!threadId) {
10635
10682
  await ctx.reply(
@@ -10684,17 +10731,19 @@ async function handleNewChat(ctx, core, chatId) {
10684
10731
  await core.sessionManager.patchRecord(session.id, { platform: {
10685
10732
  topicId: newThreadId
10686
10733
  } });
10687
- await ctx.api.sendMessage(
10734
+ const controlMsg = await ctx.api.sendMessage(
10688
10735
  chatId,
10689
- `\u2705 New chat (same agent &amp; workspace)
10690
- <b>Agent:</b> ${escapeHtml4(session.agentName)}
10691
- <b>Workspace:</b> <code>${escapeHtml4(session.workingDirectory)}</code>`,
10736
+ buildSessionStatusText(session, `\u2705 New chat (same agent &amp; workspace)`),
10692
10737
  {
10693
10738
  message_thread_id: newThreadId,
10694
10739
  parse_mode: "HTML",
10695
10740
  reply_markup: buildSessionControlKeyboard(session.id, false, false)
10696
10741
  }
10697
10742
  );
10743
+ onControlMessage?.(session.id, controlMsg.message_id);
10744
+ await core.sessionManager.patchRecord(session.id, {
10745
+ platform: { topicId: newThreadId }
10746
+ });
10698
10747
  } catch (err) {
10699
10748
  if (newThreadId) {
10700
10749
  try {
@@ -12184,7 +12233,7 @@ Choose the repo that has Entire checkpoints enabled:`;
12184
12233
  timer: setTimeout(() => pendingResumes.delete(userId), PENDING_TIMEOUT_MS2)
12185
12234
  });
12186
12235
  }
12187
- async function executeResume(ctx, core, chatId, query, repoPath) {
12236
+ async function executeResume(ctx, core, chatId, query, repoPath, onControlMessage) {
12188
12237
  const provider = await core.contextManager.getProvider(repoPath);
12189
12238
  if (!provider) {
12190
12239
  await ctx.reply(
@@ -12242,22 +12291,23 @@ Repo: <code>${escapeHtml4(repoPath)}</code>`,
12242
12291
  { parse_mode: "HTML" }
12243
12292
  );
12244
12293
  }
12245
- await ctx.api.sendMessage(
12246
- chatId,
12247
- `\u2705 <b>Session resumed with context</b>
12248
- <b>Agent:</b> ${escapeHtml4(session.agentName)}
12249
- <b>Workspace:</b> <code>${escapeHtml4(session.workingDirectory)}</code>
12294
+ const resumeHeading = `\u2705 <b>Session resumed with context</b>
12250
12295
  <b>Sessions loaded:</b> ${sessionCount}
12251
12296
  <b>Mode:</b> ${escapeHtml4(mode)}
12252
- <b>~Tokens:</b> ${tokens.toLocaleString()}
12253
-
12254
- Context is ready \u2014 chat here to continue working with the agent.`,
12297
+ <b>~Tokens:</b> ${tokens.toLocaleString()}`;
12298
+ const controlMsg = await ctx.api.sendMessage(
12299
+ chatId,
12300
+ buildSessionStatusText(session, resumeHeading),
12255
12301
  {
12256
12302
  message_thread_id: threadId,
12257
12303
  parse_mode: "HTML",
12258
12304
  reply_markup: buildSessionControlKeyboard(session.id, false, false)
12259
12305
  }
12260
12306
  );
12307
+ onControlMessage?.(session.id, controlMsg.message_id);
12308
+ await core.sessionManager.patchRecord(session.id, {
12309
+ platform: { topicId: threadId }
12310
+ });
12261
12311
  } catch (err) {
12262
12312
  log17.error({ err }, "Resume session creation failed");
12263
12313
  if (threadId) {
@@ -12270,7 +12320,7 @@ Context is ready \u2014 chat here to continue working with the agent.`,
12270
12320
  await ctx.reply(`\u274C ${escapeHtml4(message)}`, { parse_mode: "HTML" });
12271
12321
  }
12272
12322
  }
12273
- async function handleResume(ctx, core, chatId, assistant) {
12323
+ async function handleResume(ctx, core, chatId, assistant, onControlMessage) {
12274
12324
  const rawMatch = ctx.match;
12275
12325
  const matchStr = typeof rawMatch === "string" ? rawMatch : "";
12276
12326
  const parsed = parseResumeArgs(matchStr);
@@ -12295,7 +12345,7 @@ Usage examples:
12295
12345
  if (!userId) return;
12296
12346
  await showWorkspacePicker(ctx, core, chatId, userId, query);
12297
12347
  }
12298
- async function handlePendingResumeInput(ctx, core, chatId, assistantTopicId) {
12348
+ async function handlePendingResumeInput(ctx, core, chatId, assistantTopicId, onControlMessage) {
12299
12349
  const userId = ctx.from?.id;
12300
12350
  if (!userId) return false;
12301
12351
  const pending = pendingResumes.get(userId);
@@ -12315,10 +12365,10 @@ async function handlePendingResumeInput(ctx, core, chatId, assistantTopicId) {
12315
12365
  }
12316
12366
  const resolved = core.configManager.resolveWorkspace(workspace);
12317
12367
  cleanupPending2(userId);
12318
- await executeResume(ctx, core, chatId, pending.query, resolved);
12368
+ await executeResume(ctx, core, chatId, pending.query, resolved, onControlMessage);
12319
12369
  return true;
12320
12370
  }
12321
- function setupResumeCallbacks(bot, core, chatId) {
12371
+ function setupResumeCallbacks(bot, core, chatId, onControlMessage) {
12322
12372
  bot.callbackQuery(/^m:resume:/, async (ctx) => {
12323
12373
  const data = ctx.callbackQuery.data;
12324
12374
  const userId = ctx.from?.id;
@@ -12337,7 +12387,7 @@ function setupResumeCallbacks(bot, core, chatId) {
12337
12387
  await ctx.api.editMessageText(chatId, pending.messageId, `\u23F3 Using <code>${escapeHtml4(resolved)}</code>...`, { parse_mode: "HTML" });
12338
12388
  } catch {
12339
12389
  }
12340
- await executeResume(ctx, core, chatId, pending.query, resolved);
12390
+ await executeResume(ctx, core, chatId, pending.query, resolved, onControlMessage);
12341
12391
  return;
12342
12392
  }
12343
12393
  if (data === "m:resume:ws:custom") {
@@ -12368,7 +12418,7 @@ Or just the folder name (will use workspace baseDir)`,
12368
12418
  await ctx.api.editMessageText(chatId, pending.messageId, `\u23F3 Using <code>${escapeHtml4(resolved)}</code>...`, { parse_mode: "HTML" });
12369
12419
  } catch {
12370
12420
  }
12371
- await executeResume(ctx, core, chatId, pending.query, resolved);
12421
+ await executeResume(ctx, core, chatId, pending.query, resolved, onControlMessage);
12372
12422
  return;
12373
12423
  }
12374
12424
  });
@@ -13719,8 +13769,9 @@ var init_switch = __esm({
13719
13769
 
13720
13770
  // src/plugins/telegram/commands/index.ts
13721
13771
  function setupCommands(bot, core, chatId, assistant) {
13722
- bot.command("new", (ctx) => handleNew(ctx, core, chatId, assistant));
13723
- bot.command("newchat", (ctx) => handleNewChat(ctx, core, chatId));
13772
+ const onControlMessage = assistant?.setControlMessage;
13773
+ bot.command("new", (ctx) => handleNew(ctx, core, chatId, assistant, onControlMessage));
13774
+ bot.command("newchat", (ctx) => handleNewChat(ctx, core, chatId, onControlMessage));
13724
13775
  bot.command("cancel", (ctx) => handleCancel(ctx, core, assistant));
13725
13776
  bot.command("status", (ctx) => handleStatus(ctx, core));
13726
13777
  bot.command("sessions", (ctx) => handleTopics(ctx, core));
@@ -13741,12 +13792,12 @@ function setupCommands(bot, core, chatId, assistant) {
13741
13792
  bot.command("text_to_speech", (ctx) => handleTTS(ctx, core));
13742
13793
  bot.command("verbosity", (ctx) => handleVerbosity(ctx, core));
13743
13794
  bot.command("outputmode", (ctx) => handleOutputMode(ctx, core));
13744
- bot.command("resume", (ctx) => handleResume(ctx, core, chatId, assistant));
13795
+ bot.command("resume", (ctx) => handleResume(ctx, core, chatId, assistant, onControlMessage));
13745
13796
  bot.command("switch", (ctx) => handleSwitch(ctx, core));
13746
13797
  }
13747
- function setupAllCallbacks(bot, core, chatId, systemTopicIds, getAssistantSession) {
13798
+ function setupAllCallbacks(bot, core, chatId, systemTopicIds, getAssistantSession, onControlMessage) {
13748
13799
  setupNewSessionCallbacks(bot, core, chatId);
13749
- setupResumeCallbacks(bot, core, chatId);
13800
+ setupResumeCallbacks(bot, core, chatId, onControlMessage);
13750
13801
  setupSessionCallbacks(bot, core, chatId, systemTopicIds);
13751
13802
  setupSettingsCallbacks(bot, core, getAssistantSession ?? (() => void 0));
13752
13803
  setupDoctorCallbacks(bot);
@@ -16344,6 +16395,7 @@ var init_adapter2 = __esm({
16344
16395
  init_log();
16345
16396
  init_topics2();
16346
16397
  init_commands3();
16398
+ init_admin();
16347
16399
  init_permissions();
16348
16400
  init_assistant();
16349
16401
  init_formatting();
@@ -16388,6 +16440,29 @@ var init_adapter2 = __esm({
16388
16440
  callbackCounter = 0;
16389
16441
  /** Pending skill commands queued when session.threadId was not yet set */
16390
16442
  _pendingSkillCommands = /* @__PURE__ */ new Map();
16443
+ /** Control message IDs per session (for updating status text/buttons) */
16444
+ controlMsgIds = /* @__PURE__ */ new Map();
16445
+ /** Store control message ID in memory + persist to session record */
16446
+ storeControlMsgId(sessionId, msgId) {
16447
+ this.storeControlMsgId(sessionId, msgId);
16448
+ this.core.sessionManager.patchRecord(sessionId, {
16449
+ platform: { controlMsgId: msgId }
16450
+ }).catch(() => {
16451
+ });
16452
+ }
16453
+ /** Get control message ID (from Map, with fallback to session record) */
16454
+ getControlMsgId(sessionId) {
16455
+ let msgId = this.controlMsgIds.get(sessionId);
16456
+ if (!msgId) {
16457
+ const record = this.core.sessionManager.getSessionRecord(sessionId);
16458
+ const platform2 = record?.platform;
16459
+ if (platform2?.controlMsgId) {
16460
+ msgId = platform2.controlMsgId;
16461
+ this.storeControlMsgId(sessionId, msgId);
16462
+ }
16463
+ }
16464
+ return msgId;
16465
+ }
16391
16466
  getThreadId(sessionId) {
16392
16467
  const threadId = this._sessionThreadIds.get(sessionId);
16393
16468
  if (threadId === void 0) {
@@ -16594,7 +16669,26 @@ var init_adapter2 = __esm({
16594
16669
  });
16595
16670
  await ctx.answerCallbackQuery();
16596
16671
  if (response.type !== "silent") {
16597
- await this.renderCommandResponse(response, chatId, topicId);
16672
+ if (response.type === "menu") {
16673
+ const keyboard = response.options.map((opt) => [
16674
+ {
16675
+ text: `${opt.label}${opt.hint ? ` \u2014 ${opt.hint}` : ""}`,
16676
+ callback_data: this.toCallbackData(opt.command)
16677
+ }
16678
+ ]);
16679
+ try {
16680
+ await ctx.editMessageText(response.title, {
16681
+ reply_markup: { inline_keyboard: keyboard }
16682
+ });
16683
+ } catch {
16684
+ }
16685
+ } else if (response.type === "text" || response.type === "error") {
16686
+ const text6 = response.type === "text" ? response.text : `\u274C ${response.message}`;
16687
+ try {
16688
+ await ctx.editMessageText(text6, { parse_mode: "Markdown" });
16689
+ } catch {
16690
+ }
16691
+ }
16598
16692
  }
16599
16693
  } catch {
16600
16694
  await ctx.answerCallbackQuery({ text: "Command failed" });
@@ -16624,6 +16718,9 @@ var init_adapter2 = __esm({
16624
16718
  topicId: this.assistantTopicId,
16625
16719
  enqueuePrompt: (p2) => this.assistantSession.enqueuePrompt(p2)
16626
16720
  };
16721
+ },
16722
+ (sessionId, msgId) => {
16723
+ this.storeControlMsgId(sessionId, msgId);
16627
16724
  }
16628
16725
  );
16629
16726
  setupCommands(
@@ -16633,6 +16730,9 @@ var init_adapter2 = __esm({
16633
16730
  {
16634
16731
  topicId: this.assistantTopicId,
16635
16732
  getSession: () => this.assistantSession,
16733
+ setControlMessage: (sessionId, msgId) => {
16734
+ this.storeControlMsgId(sessionId, msgId);
16735
+ },
16636
16736
  respawn: async () => {
16637
16737
  if (this.assistantSession) {
16638
16738
  await this.assistantSession.destroy();
@@ -16696,6 +16796,10 @@ var init_adapter2 = __esm({
16696
16796
  }
16697
16797
  );
16698
16798
  });
16799
+ this.core.eventBus.on("session:configChanged", ({ sessionId }) => {
16800
+ this.updateControlMessage(sessionId).catch(() => {
16801
+ });
16802
+ });
16699
16803
  this.setupRoutes();
16700
16804
  this.bot.start({
16701
16805
  allowed_updates: ["message", "callback_query"],
@@ -16900,7 +17004,10 @@ ${lines.join("\n")}`;
16900
17004
  ctx,
16901
17005
  this.core,
16902
17006
  this.telegramConfig.chatId,
16903
- this.assistantTopicId
17007
+ this.assistantTopicId,
17008
+ (sessionId2, msgId) => {
17009
+ this.storeControlMsgId(sessionId2, msgId);
17010
+ }
16904
17011
  )) {
16905
17012
  return;
16906
17013
  }
@@ -17279,6 +17386,41 @@ Task completed.
17279
17386
  );
17280
17387
  }
17281
17388
  }
17389
+ async handleConfigUpdate(sessionId, _content) {
17390
+ await this.updateControlMessage(sessionId);
17391
+ }
17392
+ /**
17393
+ * Edit the pinned control message to reflect current session state
17394
+ * (model, thought level, mode, bypass status).
17395
+ */
17396
+ async updateControlMessage(sessionId) {
17397
+ const session = this.core.sessionManager.getSession(sessionId);
17398
+ if (!session) return;
17399
+ const controlMsgId = this.getControlMsgId(sessionId);
17400
+ if (!controlMsgId) return;
17401
+ const threadId = Number(session.threadId);
17402
+ if (!threadId || isNaN(threadId)) return;
17403
+ const text6 = buildSessionStatusText(session);
17404
+ const keyboard = buildSessionControlKeyboard(
17405
+ sessionId,
17406
+ isBypassActive(session),
17407
+ session.voiceMode === "on"
17408
+ );
17409
+ try {
17410
+ await this.bot.api.editMessageText(
17411
+ this.telegramConfig.chatId,
17412
+ controlMsgId,
17413
+ text6,
17414
+ { parse_mode: "HTML" }
17415
+ );
17416
+ await this.bot.api.editMessageReplyMarkup(
17417
+ this.telegramConfig.chatId,
17418
+ controlMsgId,
17419
+ { reply_markup: keyboard }
17420
+ );
17421
+ } catch {
17422
+ }
17423
+ }
17282
17424
  async handleError(sessionId, content) {
17283
17425
  this.getTracer(sessionId)?.log("telegram", { action: "handle:error", sessionId, text: content.text });
17284
17426
  const threadId = this.getThreadId(sessionId);
@@ -20016,19 +20158,6 @@ var init_session_manager = __esm({
20016
20158
  }
20017
20159
  });
20018
20160
 
20019
- // src/core/utils/bypass-detection.ts
20020
- function isPermissionBypass(value) {
20021
- const lower = value.toLowerCase();
20022
- return BYPASS_KEYWORDS.some((kw) => lower.includes(kw));
20023
- }
20024
- var BYPASS_KEYWORDS;
20025
- var init_bypass_detection = __esm({
20026
- "src/core/utils/bypass-detection.ts"() {
20027
- "use strict";
20028
- BYPASS_KEYWORDS = ["bypass", "dangerous", "skip", "dontask", "dont_ask", "auto_accept"];
20029
- }
20030
- });
20031
-
20032
20161
  // src/core/sessions/session-bridge.ts
20033
20162
  var log29, SessionBridge;
20034
20163
  var init_session_bridge = __esm({
@@ -23773,6 +23902,7 @@ function registerCategoryCommand(registry, core, category, commandName) {
23773
23902
  if (response.configOptions) {
23774
23903
  session.configOptions = response.configOptions;
23775
23904
  }
23905
+ core.eventBus.emit("session:configChanged", { sessionId: session.id });
23776
23906
  return { type: "text", text: labels.successMsg(match.name, configOption.name) };
23777
23907
  } catch (err) {
23778
23908
  const msg = err instanceof Error ? err.message : String(err);
@@ -23839,9 +23969,10 @@ function registerDangerousCommand(registry, core) {
23839
23969
  if (response.configOptions) {
23840
23970
  session.configOptions = response.configOptions;
23841
23971
  }
23972
+ core.eventBus.emit("session:configChanged", { sessionId: session.id });
23842
23973
  return {
23843
23974
  type: "text",
23844
- text: wantOn ? "\u2620\uFE0F **Bypass enabled** \u2014 all permission requests will be auto-approved. The agent can run any action without asking." : "\u{1F510} **Bypass disabled** \u2014 you will be asked to approve risky actions."
23975
+ text: wantOn ? "\u2620\uFE0F **Bypass Permissions enabled** \u2014 all permission requests will be auto-approved. The agent can run any action without asking." : "\u{1F510} **Bypass Permissions disabled** \u2014 you will be asked to approve risky actions."
23845
23976
  };
23846
23977
  } catch (err) {
23847
23978
  const msg = err instanceof Error ? err.message : String(err);
@@ -23852,9 +23983,10 @@ function registerDangerousCommand(registry, core) {
23852
23983
  await core.sessionManager.patchRecord(session.id, {
23853
23984
  clientOverrides: { ...session.clientOverrides }
23854
23985
  });
23986
+ core.eventBus.emit("session:configChanged", { sessionId: session.id });
23855
23987
  return {
23856
23988
  type: "text",
23857
- text: wantOn ? "\u2620\uFE0F **Bypass enabled** (client-side) \u2014 all permission requests will be auto-approved.\n\n_Note: This agent doesn't natively support bypass mode, so OpenACP will auto-approve on your behalf._" : "\u{1F510} **Bypass disabled** \u2014 you will be asked to approve risky actions."
23989
+ text: wantOn ? "\u2620\uFE0F **Bypass Permissions enabled** (client-side) \u2014 all permission requests will be auto-approved.\n\n_Note: This agent doesn't natively support bypass mode, so OpenACP will auto-approve on your behalf._" : "\u{1F510} **Bypass Permissions disabled** \u2014 you will be asked to approve risky actions."
23858
23990
  };
23859
23991
  }
23860
23992
  });
@@ -25735,6 +25867,12 @@ async function startServer(opts) {
25735
25867
  removePidFile2(ctx.paths.pid);
25736
25868
  }
25737
25869
  if (exitCode === RESTART_EXIT_CODE) {
25870
+ if (process.env.OPENACP_DEV_LOOP) {
25871
+ const fsMod = await import("fs");
25872
+ const osMod = await import("os");
25873
+ const pathMod2 = await import("path");
25874
+ fsMod.writeFileSync(pathMod2.join(osMod.tmpdir(), "openacp-dev-loop-root"), ctx.root, "utf-8");
25875
+ }
25738
25876
  if (isDaemon) {
25739
25877
  const { spawn: spawnChild } = await import("child_process");
25740
25878
  const { expandHome: expandHome4 } = await Promise.resolve().then(() => (init_config2(), config_exports));
@@ -25749,7 +25887,7 @@ async function startServer(opts) {
25749
25887
  const child = spawnChild(process.execPath, [cliPath, "--daemon-child"], {
25750
25888
  detached: true,
25751
25889
  stdio: ["ignore", out, err],
25752
- env: { ...process.env, OPENACP_SKIP_UPDATE_CHECK: "1" }
25890
+ env: { ...process.env, OPENACP_SKIP_UPDATE_CHECK: "1", OPENACP_INSTANCE_ROOT: ctx.root }
25753
25891
  });
25754
25892
  fs53.closeSync(out);
25755
25893
  fs53.closeSync(err);
@@ -25759,7 +25897,7 @@ async function startServer(opts) {
25759
25897
  const { spawn: spawnChild } = await import("child_process");
25760
25898
  const child = spawnChild(process.execPath, process.argv.slice(1), {
25761
25899
  stdio: "inherit",
25762
- env: { ...process.env, OPENACP_SKIP_UPDATE_CHECK: "1" }
25900
+ env: { ...process.env, OPENACP_SKIP_UPDATE_CHECK: "1", OPENACP_INSTANCE_ROOT: ctx.root }
25763
25901
  });
25764
25902
  await shutdownLogger();
25765
25903
  child.on("exit", (code) => process.exit(code ?? 0));
@@ -29401,7 +29539,7 @@ async function cmdDev(args2 = []) {
29401
29539
  `);
29402
29540
  return;
29403
29541
  }
29404
- const pluginPathArg = args2.slice(1).find((a) => !a.startsWith("--"));
29542
+ const pluginPathArg = args2.find((a) => !a.startsWith("--"));
29405
29543
  const noWatch = args2.includes("--no-watch");
29406
29544
  const verbose = args2.includes("--verbose");
29407
29545
  if (!pluginPathArg) {