@openacp/cli 0.2.11 → 0.2.14

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.
@@ -1,6 +1,7 @@
1
1
  import {
2
- log
3
- } from "./chunk-KADEDKIM.js";
2
+ createChildLogger,
3
+ createSessionLogger
4
+ } from "./chunk-HTUZOMIT.js";
4
5
 
5
6
  // src/core/streams.ts
6
7
  function nodeToWebWritable(nodeStream) {
@@ -46,10 +47,12 @@ var StderrCapture = class {
46
47
 
47
48
  // src/core/agent-instance.ts
48
49
  import { spawn, execSync } from "child_process";
50
+ import { Transform } from "stream";
49
51
  import fs from "fs";
50
52
  import path from "path";
51
53
  import { randomUUID } from "crypto";
52
54
  import { ClientSideConnection, ndJsonStream } from "@agentclientprotocol/sdk";
55
+ var log = createChildLogger({ module: "agent-instance" });
53
56
  function resolveAgentCommand(cmd) {
54
57
  const packageDirs = [
55
58
  path.resolve(process.cwd(), "node_modules", "@zed-industries", cmd, "dist", "index.js"),
@@ -103,7 +106,8 @@ var AgentInstance = class _AgentInstance {
103
106
  static async spawn(agentDef, workingDirectory) {
104
107
  const instance = new _AgentInstance(agentDef.name);
105
108
  const resolved = resolveAgentCommand(agentDef.command);
106
- log.debug(`Spawning agent "${agentDef.name}" \u2192 ${resolved.command} ${resolved.args.join(" ")}`);
109
+ log.debug({ agentName: agentDef.name, command: resolved.command, args: resolved.args }, "Spawning agent");
110
+ const spawnStart = Date.now();
107
111
  instance.child = spawn(resolved.command, [...resolved.args, ...agentDef.args], {
108
112
  stdio: ["pipe", "pipe", "pipe"],
109
113
  cwd: workingDirectory,
@@ -119,8 +123,22 @@ var AgentInstance = class _AgentInstance {
119
123
  instance.child.stderr.on("data", (chunk) => {
120
124
  instance.stderrCapture.append(chunk.toString());
121
125
  });
122
- const toAgent = nodeToWebWritable(instance.child.stdin);
123
- const fromAgent = nodeToWebReadable(instance.child.stdout);
126
+ const stdinLogger = new Transform({
127
+ transform(chunk, _enc, cb) {
128
+ log.debug({ direction: "send", raw: chunk.toString().trimEnd() }, "ACP raw");
129
+ cb(null, chunk);
130
+ }
131
+ });
132
+ stdinLogger.pipe(instance.child.stdin);
133
+ const stdoutLogger = new Transform({
134
+ transform(chunk, _enc, cb) {
135
+ log.debug({ direction: "recv", raw: chunk.toString().trimEnd() }, "ACP raw");
136
+ cb(null, chunk);
137
+ }
138
+ });
139
+ instance.child.stdout.pipe(stdoutLogger);
140
+ const toAgent = nodeToWebWritable(stdinLogger);
141
+ const fromAgent = nodeToWebReadable(stdoutLogger);
124
142
  const stream = ndJsonStream(toAgent, fromAgent);
125
143
  instance.connection = new ClientSideConnection(
126
144
  (_agent) => instance.createClient(_agent),
@@ -139,6 +157,7 @@ var AgentInstance = class _AgentInstance {
139
157
  });
140
158
  instance.sessionId = response.sessionId;
141
159
  instance.child.on("exit", (code, signal) => {
160
+ log.info({ sessionId: instance.sessionId, exitCode: code, signal }, "Agent process exited");
142
161
  if (code !== 0 && code !== null) {
143
162
  const stderr = instance.stderrCapture.getLastLines();
144
163
  instance.onSessionUpdate({
@@ -149,9 +168,9 @@ ${stderr}`
149
168
  }
150
169
  });
151
170
  instance.connection.closed.then(() => {
152
- log.debug("ACP connection closed for", instance.agentName);
171
+ log.debug({ sessionId: instance.sessionId }, "ACP connection closed");
153
172
  });
154
- log.info(`Agent "${agentDef.name}" spawned with session ${response.sessionId}`);
173
+ log.info({ sessionId: response.sessionId, durationMs: Date.now() - spawnStart }, "Agent spawn complete");
155
174
  return instance;
156
175
  }
157
176
  // createClient — implemented in Task 6b
@@ -366,6 +385,7 @@ var AgentManager = class {
366
385
 
367
386
  // src/core/session.ts
368
387
  import { nanoid } from "nanoid";
388
+ var moduleLog = createChildLogger({ module: "session" });
369
389
  var Session = class {
370
390
  id;
371
391
  channelId;
@@ -381,17 +401,20 @@ var Session = class {
381
401
  adapter;
382
402
  // Set by wireSessionEvents for renaming
383
403
  pendingPermission;
404
+ log;
384
405
  constructor(opts) {
385
406
  this.id = opts.id || nanoid(12);
386
407
  this.channelId = opts.channelId;
387
408
  this.agentName = opts.agentName;
388
409
  this.workingDirectory = opts.workingDirectory;
389
410
  this.agentInstance = opts.agentInstance;
411
+ this.log = createSessionLogger(this.id, moduleLog);
412
+ this.log.info({ agentName: this.agentName }, "Session created");
390
413
  }
391
414
  async enqueuePrompt(text) {
392
415
  if (this.promptRunning) {
393
416
  this.promptQueue.push(text);
394
- log.debug(`Prompt queued for session ${this.id} (${this.promptQueue.length} in queue)`);
417
+ this.log.debug({ queueDepth: this.promptQueue.length }, "Prompt queued");
395
418
  return;
396
419
  }
397
420
  await this.runPrompt(text);
@@ -399,14 +422,17 @@ var Session = class {
399
422
  async runPrompt(text) {
400
423
  this.promptRunning = true;
401
424
  this.status = "active";
425
+ const promptStart = Date.now();
426
+ this.log.debug("Prompt execution started");
402
427
  try {
403
428
  await this.agentInstance.prompt(text);
429
+ this.log.info({ durationMs: Date.now() - promptStart }, "Prompt execution completed");
404
430
  if (!this.name) {
405
431
  await this.autoName();
406
432
  }
407
433
  } catch (err) {
408
434
  this.status = "error";
409
- log.error(`Prompt failed for session ${this.id}:`, err);
435
+ this.log.error({ err }, "Prompt execution failed");
410
436
  } finally {
411
437
  this.promptRunning = false;
412
438
  if (this.promptQueue.length > 0) {
@@ -428,6 +454,7 @@ var Session = class {
428
454
  "Summarize this conversation in max 5 words for a topic title. Reply ONLY with the title, nothing else."
429
455
  );
430
456
  this.name = title.trim().slice(0, 50);
457
+ this.log.info({ name: this.name }, "Session auto-named");
431
458
  if (this.adapter && this.name) {
432
459
  await this.adapter.renameSessionThread(this.id, this.name);
433
460
  }
@@ -437,11 +464,34 @@ var Session = class {
437
464
  this.agentInstance.onSessionUpdate = prevHandler;
438
465
  }
439
466
  }
467
+ /** Fire-and-forget warm-up: primes model cache while user types their first message */
468
+ async warmup() {
469
+ this.promptRunning = true;
470
+ const prevHandler = this.agentInstance.onSessionUpdate;
471
+ this.agentInstance.onSessionUpdate = () => {
472
+ };
473
+ try {
474
+ const start = Date.now();
475
+ await this.agentInstance.prompt('Reply with only "ready".');
476
+ this.log.info({ durationMs: Date.now() - start }, "Warm-up complete");
477
+ } catch (err) {
478
+ this.log.error({ err }, "Warm-up failed");
479
+ } finally {
480
+ this.agentInstance.onSessionUpdate = prevHandler;
481
+ this.promptRunning = false;
482
+ if (this.promptQueue.length > 0) {
483
+ const next = this.promptQueue.shift();
484
+ await this.runPrompt(next);
485
+ }
486
+ }
487
+ }
440
488
  async cancel() {
441
489
  this.status = "cancelled";
490
+ this.log.info("Session cancelled");
442
491
  await this.agentInstance.cancel();
443
492
  }
444
493
  async destroy() {
494
+ this.log.info("Session destroyed");
445
495
  await this.agentInstance.destroy();
446
496
  }
447
497
  };
@@ -502,6 +552,7 @@ var NotificationManager = class {
502
552
  };
503
553
 
504
554
  // src/core/core.ts
555
+ var log2 = createChildLogger({ module: "core" });
505
556
  var OpenACPCore = class {
506
557
  configManager;
507
558
  agentManager;
@@ -540,11 +591,16 @@ var OpenACPCore = class {
540
591
  // --- Message Routing ---
541
592
  async handleMessage(message) {
542
593
  const config = this.configManager.get();
594
+ log2.debug({ channelId: message.channelId, threadId: message.threadId, userId: message.userId }, "Incoming message");
543
595
  if (config.security.allowedUserIds.length > 0) {
544
- if (!config.security.allowedUserIds.includes(message.userId)) return;
596
+ if (!config.security.allowedUserIds.includes(message.userId)) {
597
+ log2.warn({ userId: message.userId }, "Rejected message from unauthorized user");
598
+ return;
599
+ }
545
600
  }
546
601
  const activeSessions = this.sessionManager.listSessions().filter((s) => s.status === "active" || s.status === "initializing");
547
602
  if (activeSessions.length >= config.security.maxConcurrentSessions) {
603
+ log2.warn({ userId: message.userId, currentCount: activeSessions.length, max: config.security.maxConcurrentSessions }, "Session limit reached");
548
604
  const adapter = this.adapters.get(message.channelId);
549
605
  if (adapter) {
550
606
  await adapter.sendMessage("system", {
@@ -561,6 +617,7 @@ var OpenACPCore = class {
561
617
  async handleNewSession(channelId, agentName, workspacePath) {
562
618
  const config = this.configManager.get();
563
619
  const resolvedAgent = agentName || config.defaultAgent;
620
+ log2.info({ channelId, agentName: resolvedAgent }, "New session request");
564
621
  const resolvedWorkspace = this.configManager.resolveWorkspace(
565
622
  workspacePath || config.agents[resolvedAgent]?.workingDirectory
566
623
  );
@@ -600,10 +657,6 @@ var OpenACPCore = class {
600
657
  return { type: "plan", text: "", metadata: { entries: event.entries } };
601
658
  case "usage":
602
659
  return { type: "usage", text: "", metadata: { tokensUsed: event.tokensUsed, contextSize: event.contextSize, cost: event.cost } };
603
- case "commands_update":
604
- log.debug("Commands update:", event.commands);
605
- return { type: "text", text: "" };
606
- // no-op for now
607
660
  default:
608
661
  return { type: "text", text: "" };
609
662
  }
@@ -623,6 +676,7 @@ var OpenACPCore = class {
623
676
  break;
624
677
  case "session_end":
625
678
  session.status = "finished";
679
+ adapter.cleanupSkillCommands(session.id);
626
680
  adapter.sendMessage(session.id, { type: "session_end", text: `Done (${event.reason})` });
627
681
  this.notificationManager.notify(session.channelId, {
628
682
  sessionId: session.id,
@@ -632,6 +686,7 @@ var OpenACPCore = class {
632
686
  });
633
687
  break;
634
688
  case "error":
689
+ adapter.cleanupSkillCommands(session.id);
635
690
  adapter.sendMessage(session.id, { type: "error", text: event.message });
636
691
  this.notificationManager.notify(session.channelId, {
637
692
  sessionId: session.id,
@@ -641,7 +696,8 @@ var OpenACPCore = class {
641
696
  });
642
697
  break;
643
698
  case "commands_update":
644
- log.debug("Commands available:", event.commands);
699
+ log2.debug({ commands: event.commands }, "Commands available");
700
+ adapter.sendSkillCommands(session.id, event.commands);
645
701
  break;
646
702
  }
647
703
  };
@@ -661,6 +717,11 @@ var ChannelAdapter = class {
661
717
  this.core = core;
662
718
  this.config = config;
663
719
  }
720
+ // Skill commands — override in adapters that support dynamic commands
721
+ async sendSkillCommands(_sessionId, _commands) {
722
+ }
723
+ async cleanupSkillCommands(_sessionId) {
724
+ }
664
725
  };
665
726
 
666
727
  // src/adapters/telegram/adapter.ts
@@ -715,19 +776,19 @@ var KIND_ICON = {
715
776
  move: "\u{1F4E6}",
716
777
  other: "\u{1F6E0}\uFE0F"
717
778
  };
718
- function extractContentText(content) {
719
- if (!content) return "";
779
+ function extractContentText(content, depth = 0) {
780
+ if (!content || depth > 5) return "";
720
781
  if (typeof content === "string") return content;
721
782
  if (Array.isArray(content)) {
722
- return content.map((c) => extractContentText(c)).filter(Boolean).join("\n");
783
+ return content.map((c) => extractContentText(c, depth + 1)).filter(Boolean).join("\n");
723
784
  }
724
785
  if (typeof content === "object" && content !== null) {
725
786
  const c = content;
726
787
  if (c.type === "text" && typeof c.text === "string") return c.text;
727
788
  if (typeof c.text === "string") return c.text;
728
789
  if (typeof c.content === "string") return c.content;
729
- if (c.input) return extractContentText(c.input);
730
- if (c.output) return extractContentText(c.output);
790
+ if (c.input) return extractContentText(c.input, depth + 1);
791
+ if (c.output) return extractContentText(c.output, depth + 1);
731
792
  const keys = Object.keys(c).filter((k) => k !== "type");
732
793
  if (keys.length === 0) return "";
733
794
  return JSON.stringify(c, null, 2);
@@ -937,6 +998,8 @@ function buildDeepLink(chatId, messageId) {
937
998
 
938
999
  // src/adapters/telegram/commands.ts
939
1000
  import { InlineKeyboard } from "grammy";
1001
+ import { nanoid as nanoid2 } from "nanoid";
1002
+ var log4 = createChildLogger({ module: "telegram-commands" });
940
1003
  function setupCommands(bot, core, chatId) {
941
1004
  bot.command("new", (ctx) => handleNew(ctx, core, chatId));
942
1005
  bot.command("new_chat", (ctx) => handleNewChat(ctx, core, chatId));
@@ -949,6 +1012,35 @@ function setupCommands(bot, core, chatId) {
949
1012
  function buildMenuKeyboard() {
950
1013
  return new InlineKeyboard().text("\u{1F195} New Session", "m:new").text("\u{1F4AC} New Chat", "m:new_chat").row().text("\u26D4 Cancel", "m:cancel").text("\u{1F4CA} Status", "m:status").row().text("\u{1F916} Agents", "m:agents").text("\u2753 Help", "m:help");
951
1014
  }
1015
+ function setupMenuCallbacks(bot, core, chatId) {
1016
+ bot.callbackQuery(/^m:/, async (ctx) => {
1017
+ const data = ctx.callbackQuery.data;
1018
+ try {
1019
+ await ctx.answerCallbackQuery();
1020
+ } catch {
1021
+ }
1022
+ switch (data) {
1023
+ case "m:new":
1024
+ await handleNew(ctx, core, chatId);
1025
+ break;
1026
+ case "m:new_chat":
1027
+ await handleNewChat(ctx, core, chatId);
1028
+ break;
1029
+ case "m:cancel":
1030
+ await handleCancel(ctx, core);
1031
+ break;
1032
+ case "m:status":
1033
+ await handleStatus(ctx, core);
1034
+ break;
1035
+ case "m:agents":
1036
+ await handleAgents(ctx, core);
1037
+ break;
1038
+ case "m:help":
1039
+ await handleHelp(ctx);
1040
+ break;
1041
+ }
1042
+ });
1043
+ }
952
1044
  async function handleMenu(ctx) {
953
1045
  await ctx.reply(`<b>OpenACP Menu</b>
954
1046
  Choose an action:`, {
@@ -962,10 +1054,15 @@ async function handleNew(ctx, core, chatId) {
962
1054
  const args = matchStr.split(" ").filter(Boolean);
963
1055
  const agentName = args[0];
964
1056
  const workspace = args[1];
1057
+ log4.info({ userId: ctx.from?.id, agentName }, "New session command");
965
1058
  let threadId;
966
1059
  try {
967
1060
  const topicName = `\u{1F504} New Session`;
968
1061
  threadId = await createSessionTopic(botFromCtx(ctx), chatId, topicName);
1062
+ await ctx.api.sendMessage(chatId, `\u23F3 Setting up session, please wait...`, {
1063
+ message_thread_id: threadId,
1064
+ parse_mode: "HTML"
1065
+ });
969
1066
  const session = await core.handleNewSession(
970
1067
  "telegram",
971
1068
  agentName,
@@ -987,6 +1084,7 @@ async function handleNew(ctx, core, chatId) {
987
1084
  parse_mode: "HTML"
988
1085
  }
989
1086
  );
1087
+ session.warmup().catch((err) => log4.error({ err }, "Warm-up error"));
990
1088
  } catch (err) {
991
1089
  if (threadId) {
992
1090
  try {
@@ -1021,6 +1119,10 @@ async function handleNewChat(ctx, core, chatId) {
1021
1119
  chatId,
1022
1120
  topicName
1023
1121
  );
1122
+ await ctx.api.sendMessage(chatId, `\u23F3 Setting up session, please wait...`, {
1123
+ message_thread_id: newThreadId,
1124
+ parse_mode: "HTML"
1125
+ });
1024
1126
  session.threadId = String(newThreadId);
1025
1127
  await ctx.api.sendMessage(
1026
1128
  chatId,
@@ -1032,6 +1134,7 @@ async function handleNewChat(ctx, core, chatId) {
1032
1134
  parse_mode: "HTML"
1033
1135
  }
1034
1136
  );
1137
+ session.warmup().catch((err) => log4.error({ err }, "Warm-up error"));
1035
1138
  } catch (err) {
1036
1139
  const message = err instanceof Error ? err.message : String(err);
1037
1140
  await ctx.reply(`\u274C ${escapeHtml(message)}`, { parse_mode: "HTML" });
@@ -1045,6 +1148,7 @@ async function handleCancel(ctx, core) {
1045
1148
  String(threadId)
1046
1149
  );
1047
1150
  if (session) {
1151
+ log4.info({ sessionId: session.id }, "Cancel session command");
1048
1152
  await session.cancel();
1049
1153
  await ctx.reply("\u26D4 Session cancelled.", { parse_mode: "HTML" });
1050
1154
  }
@@ -1116,10 +1220,56 @@ Or just chat in the \u{1F916} Assistant topic for help!`,
1116
1220
  function botFromCtx(ctx) {
1117
1221
  return { api: ctx.api };
1118
1222
  }
1223
+ var skillCallbackMap = /* @__PURE__ */ new Map();
1224
+ function buildSkillKeyboard(sessionId, commands) {
1225
+ const keyboard = new InlineKeyboard();
1226
+ const sorted = [...commands].sort((a, b) => a.name.localeCompare(b.name));
1227
+ for (let i = 0; i < sorted.length; i++) {
1228
+ const cmd = sorted[i];
1229
+ const key = nanoid2(8);
1230
+ skillCallbackMap.set(key, { sessionId, commandName: cmd.name });
1231
+ keyboard.text(`/${cmd.name}`, `s:${key}`);
1232
+ if (i % 2 === 1 && i < sorted.length - 1) {
1233
+ keyboard.row();
1234
+ }
1235
+ }
1236
+ return keyboard;
1237
+ }
1238
+ function clearSkillCallbacks(sessionId) {
1239
+ for (const [key, entry] of skillCallbackMap) {
1240
+ if (entry.sessionId === sessionId) {
1241
+ skillCallbackMap.delete(key);
1242
+ }
1243
+ }
1244
+ }
1245
+ function setupSkillCallbacks(bot, core) {
1246
+ bot.callbackQuery(/^s:/, async (ctx) => {
1247
+ try {
1248
+ await ctx.answerCallbackQuery();
1249
+ } catch {
1250
+ }
1251
+ const key = ctx.callbackQuery.data.slice(2);
1252
+ const entry = skillCallbackMap.get(key);
1253
+ if (!entry) return;
1254
+ const session = core.sessionManager.getSession(entry.sessionId);
1255
+ if (!session || session.status !== "active") return;
1256
+ await session.enqueuePrompt(`/${entry.commandName}`);
1257
+ });
1258
+ }
1259
+ var STATIC_COMMANDS = [
1260
+ { command: "new", description: "Create new session" },
1261
+ { command: "new_chat", description: "New chat, same agent & workspace" },
1262
+ { command: "cancel", description: "Cancel current session" },
1263
+ { command: "status", description: "Show status" },
1264
+ { command: "agents", description: "List available agents" },
1265
+ { command: "help", description: "Help" },
1266
+ { command: "menu", description: "Show menu" }
1267
+ ];
1119
1268
 
1120
1269
  // src/adapters/telegram/permissions.ts
1121
1270
  import { InlineKeyboard as InlineKeyboard2 } from "grammy";
1122
- import { nanoid as nanoid2 } from "nanoid";
1271
+ import { nanoid as nanoid3 } from "nanoid";
1272
+ var log5 = createChildLogger({ module: "telegram-permissions" });
1123
1273
  var PermissionHandler = class {
1124
1274
  constructor(bot, chatId, getSession, sendNotification) {
1125
1275
  this.bot = bot;
@@ -1130,8 +1280,12 @@ var PermissionHandler = class {
1130
1280
  pending = /* @__PURE__ */ new Map();
1131
1281
  async sendPermissionRequest(session, request) {
1132
1282
  const threadId = Number(session.threadId);
1133
- const callbackKey = nanoid2(8);
1134
- this.pending.set(callbackKey, { sessionId: session.id, requestId: request.id });
1283
+ const callbackKey = nanoid3(8);
1284
+ this.pending.set(callbackKey, {
1285
+ sessionId: session.id,
1286
+ requestId: request.id,
1287
+ options: request.options.map((o) => ({ id: o.id, isAllow: o.isAllow }))
1288
+ });
1135
1289
  const keyboard = new InlineKeyboard2();
1136
1290
  for (const option of request.options) {
1137
1291
  const emoji = option.isAllow ? "\u2705" : "\u274C";
@@ -1174,6 +1328,8 @@ ${escapeHtml(request.description)}`,
1174
1328
  return;
1175
1329
  }
1176
1330
  const session = this.getSession(pending.sessionId);
1331
+ const isAllow = pending.options.find((o) => o.id === optionId)?.isAllow ?? false;
1332
+ log5.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
1177
1333
  if (session?.pendingPermission?.requestId === pending.requestId) {
1178
1334
  session.pendingPermission.resolve(optionId);
1179
1335
  session.pendingPermission = void 0;
@@ -1240,6 +1396,7 @@ function redirectToAssistant(chatId, assistantTopicId) {
1240
1396
  }
1241
1397
 
1242
1398
  // src/adapters/telegram/adapter.ts
1399
+ var log6 = createChildLogger({ module: "telegram" });
1243
1400
  var TelegramAdapter = class extends ChannelAdapter {
1244
1401
  bot;
1245
1402
  telegramConfig;
@@ -1250,6 +1407,8 @@ var TelegramAdapter = class extends ChannelAdapter {
1250
1407
  assistantSession = null;
1251
1408
  notificationTopicId;
1252
1409
  assistantTopicId;
1410
+ skillMessages = /* @__PURE__ */ new Map();
1411
+ // sessionId → pinned messageId
1253
1412
  constructor(core, config) {
1254
1413
  super(core, config);
1255
1414
  this.telegramConfig = config;
@@ -1257,14 +1416,22 @@ var TelegramAdapter = class extends ChannelAdapter {
1257
1416
  async start() {
1258
1417
  this.bot = new Bot(this.telegramConfig.botToken);
1259
1418
  this.bot.catch((err) => {
1260
- log.error("Bot error:", err.message || err);
1419
+ const rootCause = err.error instanceof Error ? err.error : err;
1420
+ log6.error({ err: rootCause }, "Telegram bot error");
1261
1421
  });
1262
1422
  this.bot.api.config.use((prev, method, payload, signal) => {
1263
1423
  if (method === "getUpdates") {
1264
- payload.allowed_updates = payload.allowed_updates ?? ["message", "callback_query"];
1424
+ const p = payload;
1425
+ p.allowed_updates = p.allowed_updates ?? [
1426
+ "message",
1427
+ "callback_query"
1428
+ ];
1265
1429
  }
1266
1430
  return prev(method, payload, signal);
1267
1431
  });
1432
+ await this.bot.api.setMyCommands(STATIC_COMMANDS, {
1433
+ scope: { type: "chat", chat_id: this.telegramConfig.chatId }
1434
+ });
1268
1435
  this.bot.use((ctx, next) => {
1269
1436
  const chatId = ctx.chat?.id ?? ctx.callbackQuery?.message?.chat?.id;
1270
1437
  if (chatId !== this.telegramConfig.chatId) return;
@@ -1288,12 +1455,25 @@ var TelegramAdapter = class extends ChannelAdapter {
1288
1455
  (sessionId) => this.core.sessionManager.getSession(sessionId),
1289
1456
  (notification) => this.sendNotification(notification)
1290
1457
  );
1458
+ setupSkillCallbacks(this.bot, this.core);
1459
+ setupMenuCallbacks(
1460
+ this.bot,
1461
+ this.core,
1462
+ this.telegramConfig.chatId
1463
+ );
1464
+ setupCommands(
1465
+ this.bot,
1466
+ this.core,
1467
+ this.telegramConfig.chatId
1468
+ );
1291
1469
  this.permissionHandler.setupCallbackHandler();
1292
- setupCommands(this.bot, this.core, this.telegramConfig.chatId);
1293
1470
  this.setupRoutes();
1294
1471
  this.bot.start({
1295
1472
  allowed_updates: ["message", "callback_query"],
1296
- onStart: () => log.info("Telegram bot started")
1473
+ onStart: () => log6.info(
1474
+ { chatId: this.telegramConfig.chatId },
1475
+ "Telegram bot started"
1476
+ )
1297
1477
  });
1298
1478
  try {
1299
1479
  this.assistantSession = await spawnAssistant(
@@ -1302,7 +1482,28 @@ var TelegramAdapter = class extends ChannelAdapter {
1302
1482
  this.assistantTopicId
1303
1483
  );
1304
1484
  } catch (err) {
1305
- log.error("Failed to spawn assistant:", err);
1485
+ log6.error({ err }, "Failed to spawn assistant");
1486
+ }
1487
+ try {
1488
+ const config = this.core.configManager.get();
1489
+ const agents = this.core.agentManager.getAvailableAgents();
1490
+ const agentList = agents.map(
1491
+ (a) => `${escapeHtml(a.name)}${a.name === config.defaultAgent ? " (default)" : ""}`
1492
+ ).join(", ");
1493
+ const workspace = escapeHtml(config.workspace.baseDir);
1494
+ const welcomeText = `\u{1F44B} <b>OpenACP Assistant</b> is online.
1495
+
1496
+ Available agents: ${agentList}
1497
+ Workspace: <code>${workspace}</code>
1498
+
1499
+ <b>Select an action:</b>`;
1500
+ await this.bot.api.sendMessage(this.telegramConfig.chatId, welcomeText, {
1501
+ message_thread_id: this.assistantTopicId,
1502
+ parse_mode: "HTML",
1503
+ reply_markup: buildMenuKeyboard()
1504
+ });
1505
+ } catch (err) {
1506
+ log6.warn({ err }, "Failed to send welcome message");
1306
1507
  }
1307
1508
  }
1308
1509
  async stop() {
@@ -1310,32 +1511,43 @@ var TelegramAdapter = class extends ChannelAdapter {
1310
1511
  await this.assistantSession.destroy();
1311
1512
  }
1312
1513
  await this.bot.stop();
1514
+ log6.info("Telegram bot stopped");
1313
1515
  }
1314
1516
  setupRoutes() {
1315
1517
  this.bot.on("message:text", async (ctx) => {
1316
1518
  const threadId = ctx.message.message_thread_id;
1317
1519
  if (!threadId) {
1318
- const html = redirectToAssistant(this.telegramConfig.chatId, this.assistantTopicId);
1520
+ const html = redirectToAssistant(
1521
+ this.telegramConfig.chatId,
1522
+ this.assistantTopicId
1523
+ );
1319
1524
  await ctx.reply(html, { parse_mode: "HTML" });
1320
1525
  return;
1321
1526
  }
1322
1527
  if (threadId === this.notificationTopicId) return;
1323
1528
  if (threadId === this.assistantTopicId) {
1324
- handleAssistantMessage(this.assistantSession, ctx.message.text).catch((err) => log.error("Assistant error:", err));
1529
+ ctx.replyWithChatAction("typing").catch(() => {
1530
+ });
1531
+ handleAssistantMessage(this.assistantSession, ctx.message.text).catch(
1532
+ (err) => log6.error({ err }, "Assistant error")
1533
+ );
1325
1534
  return;
1326
1535
  }
1327
- ;
1536
+ ctx.replyWithChatAction("typing").catch(() => {
1537
+ });
1328
1538
  this.core.handleMessage({
1329
1539
  channelId: "telegram",
1330
1540
  threadId: String(threadId),
1331
1541
  userId: String(ctx.from.id),
1332
1542
  text: ctx.message.text
1333
- }).catch((err) => log.error("handleMessage error:", err));
1543
+ }).catch((err) => log6.error({ err }, "handleMessage error"));
1334
1544
  });
1335
1545
  }
1336
1546
  // --- ChannelAdapter implementations ---
1337
1547
  async sendMessage(sessionId, content) {
1338
- const session = this.core.sessionManager.getSession(sessionId);
1548
+ const session = this.core.sessionManager.getSession(
1549
+ sessionId
1550
+ );
1339
1551
  if (!session) return;
1340
1552
  const threadId = Number(session.threadId);
1341
1553
  switch (content.type) {
@@ -1345,7 +1557,11 @@ var TelegramAdapter = class extends ChannelAdapter {
1345
1557
  case "text": {
1346
1558
  let draft = this.sessionDrafts.get(sessionId);
1347
1559
  if (!draft) {
1348
- draft = new MessageDraft(this.bot, this.telegramConfig.chatId, threadId);
1560
+ draft = new MessageDraft(
1561
+ this.bot,
1562
+ this.telegramConfig.chatId,
1563
+ threadId
1564
+ );
1349
1565
  this.sessionDrafts.set(sessionId, draft);
1350
1566
  }
1351
1567
  draft.append(content.text);
@@ -1357,19 +1573,31 @@ var TelegramAdapter = class extends ChannelAdapter {
1357
1573
  const msg = await this.bot.api.sendMessage(
1358
1574
  this.telegramConfig.chatId,
1359
1575
  formatToolCall(meta),
1360
- { message_thread_id: threadId, parse_mode: "HTML", disable_notification: true }
1576
+ {
1577
+ message_thread_id: threadId,
1578
+ parse_mode: "HTML",
1579
+ disable_notification: true
1580
+ }
1361
1581
  );
1362
1582
  if (!this.toolCallMessages.has(sessionId)) {
1363
1583
  this.toolCallMessages.set(sessionId, /* @__PURE__ */ new Map());
1364
1584
  }
1365
- this.toolCallMessages.get(sessionId).set(meta.id, { msgId: msg.message_id, name: meta.name, kind: meta.kind });
1585
+ this.toolCallMessages.get(sessionId).set(meta.id, {
1586
+ msgId: msg.message_id,
1587
+ name: meta.name,
1588
+ kind: meta.kind
1589
+ });
1366
1590
  break;
1367
1591
  }
1368
1592
  case "tool_update": {
1369
1593
  const meta = content.metadata;
1370
1594
  const toolState = this.toolCallMessages.get(sessionId)?.get(meta.id);
1371
1595
  if (toolState) {
1372
- const merged = { ...meta, name: meta.name || toolState.name, kind: meta.kind || toolState.kind };
1596
+ const merged = {
1597
+ ...meta,
1598
+ name: meta.name || toolState.name,
1599
+ kind: meta.kind || toolState.kind
1600
+ };
1373
1601
  try {
1374
1602
  await this.bot.api.editMessageText(
1375
1603
  this.telegramConfig.chatId,
@@ -1387,7 +1615,11 @@ var TelegramAdapter = class extends ChannelAdapter {
1387
1615
  await this.bot.api.sendMessage(
1388
1616
  this.telegramConfig.chatId,
1389
1617
  formatPlan(content.metadata),
1390
- { message_thread_id: threadId, parse_mode: "HTML", disable_notification: true }
1618
+ {
1619
+ message_thread_id: threadId,
1620
+ parse_mode: "HTML",
1621
+ disable_notification: true
1622
+ }
1391
1623
  );
1392
1624
  break;
1393
1625
  }
@@ -1395,7 +1627,11 @@ var TelegramAdapter = class extends ChannelAdapter {
1395
1627
  await this.bot.api.sendMessage(
1396
1628
  this.telegramConfig.chatId,
1397
1629
  formatUsage(content.metadata),
1398
- { message_thread_id: threadId, parse_mode: "HTML", disable_notification: true }
1630
+ {
1631
+ message_thread_id: threadId,
1632
+ parse_mode: "HTML",
1633
+ disable_notification: true
1634
+ }
1399
1635
  );
1400
1636
  break;
1401
1637
  }
@@ -1403,10 +1639,15 @@ var TelegramAdapter = class extends ChannelAdapter {
1403
1639
  await this.finalizeDraft(sessionId);
1404
1640
  this.sessionDrafts.delete(sessionId);
1405
1641
  this.toolCallMessages.delete(sessionId);
1642
+ await this.cleanupSkillCommands(sessionId);
1406
1643
  await this.bot.api.sendMessage(
1407
1644
  this.telegramConfig.chatId,
1408
1645
  `\u2705 <b>Done</b>`,
1409
- { message_thread_id: threadId, parse_mode: "HTML", disable_notification: true }
1646
+ {
1647
+ message_thread_id: threadId,
1648
+ parse_mode: "HTML",
1649
+ disable_notification: true
1650
+ }
1410
1651
  );
1411
1652
  break;
1412
1653
  }
@@ -1415,18 +1656,29 @@ var TelegramAdapter = class extends ChannelAdapter {
1415
1656
  await this.bot.api.sendMessage(
1416
1657
  this.telegramConfig.chatId,
1417
1658
  `\u274C <b>Error:</b> ${escapeHtml(content.text)}`,
1418
- { message_thread_id: threadId, parse_mode: "HTML", disable_notification: true }
1659
+ {
1660
+ message_thread_id: threadId,
1661
+ parse_mode: "HTML",
1662
+ disable_notification: true
1663
+ }
1419
1664
  );
1420
1665
  break;
1421
1666
  }
1422
1667
  }
1423
1668
  }
1424
1669
  async sendPermissionRequest(sessionId, request) {
1425
- const session = this.core.sessionManager.getSession(sessionId);
1670
+ log6.info({ sessionId, requestId: request.id }, "Permission request sent");
1671
+ const session = this.core.sessionManager.getSession(
1672
+ sessionId
1673
+ );
1426
1674
  if (!session) return;
1427
1675
  await this.permissionHandler.sendPermissionRequest(session, request);
1428
1676
  }
1429
1677
  async sendNotification(notification) {
1678
+ log6.info(
1679
+ { sessionId: notification.sessionId, type: notification.type },
1680
+ "Notification sent"
1681
+ );
1430
1682
  if (!this.notificationTopicId) return;
1431
1683
  const emoji = {
1432
1684
  completed: "\u2705",
@@ -1449,12 +1701,99 @@ var TelegramAdapter = class extends ChannelAdapter {
1449
1701
  });
1450
1702
  }
1451
1703
  async createSessionThread(sessionId, name) {
1452
- return String(await createSessionTopic(this.bot, this.telegramConfig.chatId, name));
1704
+ log6.info({ sessionId, name }, "Session topic created");
1705
+ return String(
1706
+ await createSessionTopic(this.bot, this.telegramConfig.chatId, name)
1707
+ );
1453
1708
  }
1454
1709
  async renameSessionThread(sessionId, newName) {
1710
+ const session = this.core.sessionManager.getSession(
1711
+ sessionId
1712
+ );
1713
+ if (!session) return;
1714
+ await renameSessionTopic(
1715
+ this.bot,
1716
+ this.telegramConfig.chatId,
1717
+ Number(session.threadId),
1718
+ newName
1719
+ );
1720
+ }
1721
+ async sendSkillCommands(sessionId, commands) {
1455
1722
  const session = this.core.sessionManager.getSession(sessionId);
1456
1723
  if (!session) return;
1457
- await renameSessionTopic(this.bot, this.telegramConfig.chatId, Number(session.threadId), newName);
1724
+ const threadId = Number(session.threadId);
1725
+ if (!threadId) return;
1726
+ if (commands.length === 0) {
1727
+ await this.cleanupSkillCommands(sessionId);
1728
+ return;
1729
+ }
1730
+ clearSkillCallbacks(sessionId);
1731
+ const keyboard = buildSkillKeyboard(sessionId, commands);
1732
+ const text = "\u{1F6E0} <b>Available commands:</b>";
1733
+ const existingMsgId = this.skillMessages.get(sessionId);
1734
+ if (existingMsgId) {
1735
+ try {
1736
+ await this.bot.api.editMessageText(
1737
+ this.telegramConfig.chatId,
1738
+ existingMsgId,
1739
+ text,
1740
+ { parse_mode: "HTML", reply_markup: keyboard }
1741
+ );
1742
+ return;
1743
+ } catch {
1744
+ }
1745
+ }
1746
+ try {
1747
+ const msg = await this.bot.api.sendMessage(
1748
+ this.telegramConfig.chatId,
1749
+ text,
1750
+ {
1751
+ message_thread_id: threadId,
1752
+ parse_mode: "HTML",
1753
+ reply_markup: keyboard,
1754
+ disable_notification: true
1755
+ }
1756
+ );
1757
+ this.skillMessages.set(sessionId, msg.message_id);
1758
+ await this.bot.api.pinChatMessage(this.telegramConfig.chatId, msg.message_id, {
1759
+ disable_notification: true
1760
+ });
1761
+ } catch (err) {
1762
+ log6.error({ err, sessionId }, "Failed to send skill commands");
1763
+ }
1764
+ await this.updateCommandAutocomplete(session.agentName, commands);
1765
+ }
1766
+ async cleanupSkillCommands(sessionId) {
1767
+ const msgId = this.skillMessages.get(sessionId);
1768
+ if (!msgId) return;
1769
+ try {
1770
+ await this.bot.api.editMessageText(
1771
+ this.telegramConfig.chatId,
1772
+ msgId,
1773
+ "\u{1F6E0} <i>Session ended</i>",
1774
+ { parse_mode: "HTML" }
1775
+ );
1776
+ await this.bot.api.unpinChatMessage(this.telegramConfig.chatId, msgId);
1777
+ } catch {
1778
+ }
1779
+ this.skillMessages.delete(sessionId);
1780
+ clearSkillCallbacks(sessionId);
1781
+ }
1782
+ async updateCommandAutocomplete(agentName, skillCommands) {
1783
+ const prefix = `[${agentName}] `;
1784
+ const validSkills = skillCommands.map((c) => ({
1785
+ command: c.name.toLowerCase().replace(/[^a-z0-9_]/g, "_").slice(0, 32),
1786
+ description: (prefix + (c.description || c.name).replace(/\n/g, " ")).slice(0, 256)
1787
+ })).filter((c) => c.command.length > 0);
1788
+ const all = [...STATIC_COMMANDS, ...validSkills];
1789
+ try {
1790
+ await this.bot.api.setMyCommands(all, {
1791
+ scope: { type: "chat", chat_id: this.telegramConfig.chatId }
1792
+ });
1793
+ log6.info({ count: all.length, skills: validSkills.length }, "Updated command autocomplete");
1794
+ } catch (err) {
1795
+ log6.error({ err, commands: all }, "Failed to update command autocomplete");
1796
+ }
1458
1797
  }
1459
1798
  async finalizeDraft(sessionId) {
1460
1799
  const draft = this.sessionDrafts.get(sessionId);
@@ -1478,4 +1817,4 @@ export {
1478
1817
  ChannelAdapter,
1479
1818
  TelegramAdapter
1480
1819
  };
1481
- //# sourceMappingURL=chunk-I6KXISAR.js.map
1820
+ //# sourceMappingURL=chunk-TKOYKVXH.js.map