@openacp/cli 0.2.11 → 0.2.12

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-5KBEVENA.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
@@ -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,20 @@ 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
+ log6.error({ err }, "Telegram bot error");
1261
1420
  });
1262
1421
  this.bot.api.config.use((prev, method, payload, signal) => {
1263
1422
  if (method === "getUpdates") {
1264
- payload.allowed_updates = payload.allowed_updates ?? ["message", "callback_query"];
1423
+ payload.allowed_updates = payload.allowed_updates ?? [
1424
+ "message",
1425
+ "callback_query"
1426
+ ];
1265
1427
  }
1266
1428
  return prev(method, payload, signal);
1267
1429
  });
1430
+ await this.bot.api.setMyCommands(STATIC_COMMANDS, {
1431
+ scope: { type: "chat", chat_id: this.telegramConfig.chatId }
1432
+ });
1268
1433
  this.bot.use((ctx, next) => {
1269
1434
  const chatId = ctx.chat?.id ?? ctx.callbackQuery?.message?.chat?.id;
1270
1435
  if (chatId !== this.telegramConfig.chatId) return;
@@ -1288,12 +1453,25 @@ var TelegramAdapter = class extends ChannelAdapter {
1288
1453
  (sessionId) => this.core.sessionManager.getSession(sessionId),
1289
1454
  (notification) => this.sendNotification(notification)
1290
1455
  );
1456
+ setupSkillCallbacks(this.bot, this.core);
1457
+ setupMenuCallbacks(
1458
+ this.bot,
1459
+ this.core,
1460
+ this.telegramConfig.chatId
1461
+ );
1462
+ setupCommands(
1463
+ this.bot,
1464
+ this.core,
1465
+ this.telegramConfig.chatId
1466
+ );
1291
1467
  this.permissionHandler.setupCallbackHandler();
1292
- setupCommands(this.bot, this.core, this.telegramConfig.chatId);
1293
1468
  this.setupRoutes();
1294
1469
  this.bot.start({
1295
1470
  allowed_updates: ["message", "callback_query"],
1296
- onStart: () => log.info("Telegram bot started")
1471
+ onStart: () => log6.info(
1472
+ { chatId: this.telegramConfig.chatId },
1473
+ "Telegram bot started"
1474
+ )
1297
1475
  });
1298
1476
  try {
1299
1477
  this.assistantSession = await spawnAssistant(
@@ -1302,7 +1480,28 @@ var TelegramAdapter = class extends ChannelAdapter {
1302
1480
  this.assistantTopicId
1303
1481
  );
1304
1482
  } catch (err) {
1305
- log.error("Failed to spawn assistant:", err);
1483
+ log6.error({ err }, "Failed to spawn assistant");
1484
+ }
1485
+ try {
1486
+ const config = this.core.configManager.get();
1487
+ const agents = this.core.agentManager.getAvailableAgents();
1488
+ const agentList = agents.map(
1489
+ (a) => `${escapeHtml(a.name)}${a.name === config.defaultAgent ? " (default)" : ""}`
1490
+ ).join(", ");
1491
+ const workspace = escapeHtml(config.workspace.baseDir);
1492
+ const welcomeText = `\u{1F44B} <b>OpenACP Assistant</b> is online.
1493
+
1494
+ Available agents: ${agentList}
1495
+ Workspace: <code>${workspace}</code>
1496
+
1497
+ <b>Select an action:</b>`;
1498
+ await this.bot.api.sendMessage(this.telegramConfig.chatId, welcomeText, {
1499
+ message_thread_id: this.assistantTopicId,
1500
+ parse_mode: "HTML",
1501
+ reply_markup: buildMenuKeyboard()
1502
+ });
1503
+ } catch (err) {
1504
+ log6.warn({ err }, "Failed to send welcome message");
1306
1505
  }
1307
1506
  }
1308
1507
  async stop() {
@@ -1310,32 +1509,43 @@ var TelegramAdapter = class extends ChannelAdapter {
1310
1509
  await this.assistantSession.destroy();
1311
1510
  }
1312
1511
  await this.bot.stop();
1512
+ log6.info("Telegram bot stopped");
1313
1513
  }
1314
1514
  setupRoutes() {
1315
1515
  this.bot.on("message:text", async (ctx) => {
1316
1516
  const threadId = ctx.message.message_thread_id;
1317
1517
  if (!threadId) {
1318
- const html = redirectToAssistant(this.telegramConfig.chatId, this.assistantTopicId);
1518
+ const html = redirectToAssistant(
1519
+ this.telegramConfig.chatId,
1520
+ this.assistantTopicId
1521
+ );
1319
1522
  await ctx.reply(html, { parse_mode: "HTML" });
1320
1523
  return;
1321
1524
  }
1322
1525
  if (threadId === this.notificationTopicId) return;
1323
1526
  if (threadId === this.assistantTopicId) {
1324
- handleAssistantMessage(this.assistantSession, ctx.message.text).catch((err) => log.error("Assistant error:", err));
1527
+ ctx.replyWithChatAction("typing").catch(() => {
1528
+ });
1529
+ handleAssistantMessage(this.assistantSession, ctx.message.text).catch(
1530
+ (err) => log6.error({ err }, "Assistant error")
1531
+ );
1325
1532
  return;
1326
1533
  }
1327
- ;
1534
+ ctx.replyWithChatAction("typing").catch(() => {
1535
+ });
1328
1536
  this.core.handleMessage({
1329
1537
  channelId: "telegram",
1330
1538
  threadId: String(threadId),
1331
1539
  userId: String(ctx.from.id),
1332
1540
  text: ctx.message.text
1333
- }).catch((err) => log.error("handleMessage error:", err));
1541
+ }).catch((err) => log6.error({ err }, "handleMessage error"));
1334
1542
  });
1335
1543
  }
1336
1544
  // --- ChannelAdapter implementations ---
1337
1545
  async sendMessage(sessionId, content) {
1338
- const session = this.core.sessionManager.getSession(sessionId);
1546
+ const session = this.core.sessionManager.getSession(
1547
+ sessionId
1548
+ );
1339
1549
  if (!session) return;
1340
1550
  const threadId = Number(session.threadId);
1341
1551
  switch (content.type) {
@@ -1345,7 +1555,11 @@ var TelegramAdapter = class extends ChannelAdapter {
1345
1555
  case "text": {
1346
1556
  let draft = this.sessionDrafts.get(sessionId);
1347
1557
  if (!draft) {
1348
- draft = new MessageDraft(this.bot, this.telegramConfig.chatId, threadId);
1558
+ draft = new MessageDraft(
1559
+ this.bot,
1560
+ this.telegramConfig.chatId,
1561
+ threadId
1562
+ );
1349
1563
  this.sessionDrafts.set(sessionId, draft);
1350
1564
  }
1351
1565
  draft.append(content.text);
@@ -1357,19 +1571,31 @@ var TelegramAdapter = class extends ChannelAdapter {
1357
1571
  const msg = await this.bot.api.sendMessage(
1358
1572
  this.telegramConfig.chatId,
1359
1573
  formatToolCall(meta),
1360
- { message_thread_id: threadId, parse_mode: "HTML", disable_notification: true }
1574
+ {
1575
+ message_thread_id: threadId,
1576
+ parse_mode: "HTML",
1577
+ disable_notification: true
1578
+ }
1361
1579
  );
1362
1580
  if (!this.toolCallMessages.has(sessionId)) {
1363
1581
  this.toolCallMessages.set(sessionId, /* @__PURE__ */ new Map());
1364
1582
  }
1365
- this.toolCallMessages.get(sessionId).set(meta.id, { msgId: msg.message_id, name: meta.name, kind: meta.kind });
1583
+ this.toolCallMessages.get(sessionId).set(meta.id, {
1584
+ msgId: msg.message_id,
1585
+ name: meta.name,
1586
+ kind: meta.kind
1587
+ });
1366
1588
  break;
1367
1589
  }
1368
1590
  case "tool_update": {
1369
1591
  const meta = content.metadata;
1370
1592
  const toolState = this.toolCallMessages.get(sessionId)?.get(meta.id);
1371
1593
  if (toolState) {
1372
- const merged = { ...meta, name: meta.name || toolState.name, kind: meta.kind || toolState.kind };
1594
+ const merged = {
1595
+ ...meta,
1596
+ name: meta.name || toolState.name,
1597
+ kind: meta.kind || toolState.kind
1598
+ };
1373
1599
  try {
1374
1600
  await this.bot.api.editMessageText(
1375
1601
  this.telegramConfig.chatId,
@@ -1387,7 +1613,11 @@ var TelegramAdapter = class extends ChannelAdapter {
1387
1613
  await this.bot.api.sendMessage(
1388
1614
  this.telegramConfig.chatId,
1389
1615
  formatPlan(content.metadata),
1390
- { message_thread_id: threadId, parse_mode: "HTML", disable_notification: true }
1616
+ {
1617
+ message_thread_id: threadId,
1618
+ parse_mode: "HTML",
1619
+ disable_notification: true
1620
+ }
1391
1621
  );
1392
1622
  break;
1393
1623
  }
@@ -1395,7 +1625,11 @@ var TelegramAdapter = class extends ChannelAdapter {
1395
1625
  await this.bot.api.sendMessage(
1396
1626
  this.telegramConfig.chatId,
1397
1627
  formatUsage(content.metadata),
1398
- { message_thread_id: threadId, parse_mode: "HTML", disable_notification: true }
1628
+ {
1629
+ message_thread_id: threadId,
1630
+ parse_mode: "HTML",
1631
+ disable_notification: true
1632
+ }
1399
1633
  );
1400
1634
  break;
1401
1635
  }
@@ -1403,10 +1637,15 @@ var TelegramAdapter = class extends ChannelAdapter {
1403
1637
  await this.finalizeDraft(sessionId);
1404
1638
  this.sessionDrafts.delete(sessionId);
1405
1639
  this.toolCallMessages.delete(sessionId);
1640
+ await this.cleanupSkillCommands(sessionId);
1406
1641
  await this.bot.api.sendMessage(
1407
1642
  this.telegramConfig.chatId,
1408
1643
  `\u2705 <b>Done</b>`,
1409
- { message_thread_id: threadId, parse_mode: "HTML", disable_notification: true }
1644
+ {
1645
+ message_thread_id: threadId,
1646
+ parse_mode: "HTML",
1647
+ disable_notification: true
1648
+ }
1410
1649
  );
1411
1650
  break;
1412
1651
  }
@@ -1415,18 +1654,29 @@ var TelegramAdapter = class extends ChannelAdapter {
1415
1654
  await this.bot.api.sendMessage(
1416
1655
  this.telegramConfig.chatId,
1417
1656
  `\u274C <b>Error:</b> ${escapeHtml(content.text)}`,
1418
- { message_thread_id: threadId, parse_mode: "HTML", disable_notification: true }
1657
+ {
1658
+ message_thread_id: threadId,
1659
+ parse_mode: "HTML",
1660
+ disable_notification: true
1661
+ }
1419
1662
  );
1420
1663
  break;
1421
1664
  }
1422
1665
  }
1423
1666
  }
1424
1667
  async sendPermissionRequest(sessionId, request) {
1425
- const session = this.core.sessionManager.getSession(sessionId);
1668
+ log6.info({ sessionId, requestId: request.id }, "Permission request sent");
1669
+ const session = this.core.sessionManager.getSession(
1670
+ sessionId
1671
+ );
1426
1672
  if (!session) return;
1427
1673
  await this.permissionHandler.sendPermissionRequest(session, request);
1428
1674
  }
1429
1675
  async sendNotification(notification) {
1676
+ log6.info(
1677
+ { sessionId: notification.sessionId, type: notification.type },
1678
+ "Notification sent"
1679
+ );
1430
1680
  if (!this.notificationTopicId) return;
1431
1681
  const emoji = {
1432
1682
  completed: "\u2705",
@@ -1449,12 +1699,99 @@ var TelegramAdapter = class extends ChannelAdapter {
1449
1699
  });
1450
1700
  }
1451
1701
  async createSessionThread(sessionId, name) {
1452
- return String(await createSessionTopic(this.bot, this.telegramConfig.chatId, name));
1702
+ log6.info({ sessionId, name }, "Session topic created");
1703
+ return String(
1704
+ await createSessionTopic(this.bot, this.telegramConfig.chatId, name)
1705
+ );
1453
1706
  }
1454
1707
  async renameSessionThread(sessionId, newName) {
1708
+ const session = this.core.sessionManager.getSession(
1709
+ sessionId
1710
+ );
1711
+ if (!session) return;
1712
+ await renameSessionTopic(
1713
+ this.bot,
1714
+ this.telegramConfig.chatId,
1715
+ Number(session.threadId),
1716
+ newName
1717
+ );
1718
+ }
1719
+ async sendSkillCommands(sessionId, commands) {
1455
1720
  const session = this.core.sessionManager.getSession(sessionId);
1456
1721
  if (!session) return;
1457
- await renameSessionTopic(this.bot, this.telegramConfig.chatId, Number(session.threadId), newName);
1722
+ const threadId = Number(session.threadId);
1723
+ if (!threadId) return;
1724
+ if (commands.length === 0) {
1725
+ await this.cleanupSkillCommands(sessionId);
1726
+ return;
1727
+ }
1728
+ clearSkillCallbacks(sessionId);
1729
+ const keyboard = buildSkillKeyboard(sessionId, commands);
1730
+ const text = "\u{1F6E0} <b>Available commands:</b>";
1731
+ const existingMsgId = this.skillMessages.get(sessionId);
1732
+ if (existingMsgId) {
1733
+ try {
1734
+ await this.bot.api.editMessageText(
1735
+ this.telegramConfig.chatId,
1736
+ existingMsgId,
1737
+ text,
1738
+ { parse_mode: "HTML", reply_markup: keyboard }
1739
+ );
1740
+ return;
1741
+ } catch {
1742
+ }
1743
+ }
1744
+ try {
1745
+ const msg = await this.bot.api.sendMessage(
1746
+ this.telegramConfig.chatId,
1747
+ text,
1748
+ {
1749
+ message_thread_id: threadId,
1750
+ parse_mode: "HTML",
1751
+ reply_markup: keyboard,
1752
+ disable_notification: true
1753
+ }
1754
+ );
1755
+ this.skillMessages.set(sessionId, msg.message_id);
1756
+ await this.bot.api.pinChatMessage(this.telegramConfig.chatId, msg.message_id, {
1757
+ disable_notification: true
1758
+ });
1759
+ } catch (err) {
1760
+ log6.error({ err, sessionId }, "Failed to send skill commands");
1761
+ }
1762
+ await this.updateCommandAutocomplete(commands);
1763
+ }
1764
+ async cleanupSkillCommands(sessionId) {
1765
+ const msgId = this.skillMessages.get(sessionId);
1766
+ if (!msgId) return;
1767
+ try {
1768
+ await this.bot.api.editMessageText(
1769
+ this.telegramConfig.chatId,
1770
+ msgId,
1771
+ "\u{1F6E0} <i>Session ended</i>",
1772
+ { parse_mode: "HTML" }
1773
+ );
1774
+ await this.bot.api.unpinChatMessage(this.telegramConfig.chatId, msgId);
1775
+ } catch {
1776
+ }
1777
+ this.skillMessages.delete(sessionId);
1778
+ clearSkillCallbacks(sessionId);
1779
+ await this.updateCommandAutocomplete([]);
1780
+ }
1781
+ async updateCommandAutocomplete(skillCommands) {
1782
+ const validSkills = skillCommands.map((c) => ({
1783
+ command: c.name.toLowerCase().replace(/[^a-z0-9_]/g, "_").slice(0, 32),
1784
+ description: (c.description || c.name).replace(/\n/g, " ").slice(0, 256)
1785
+ })).filter((c) => c.command.length > 0);
1786
+ const all = [...STATIC_COMMANDS, ...validSkills];
1787
+ try {
1788
+ await this.bot.api.setMyCommands(all, {
1789
+ scope: { type: "chat", chat_id: this.telegramConfig.chatId }
1790
+ });
1791
+ log6.info({ count: all.length, skills: validSkills.length }, "Updated command autocomplete");
1792
+ } catch (err) {
1793
+ log6.error({ err, commands: all }, "Failed to update command autocomplete");
1794
+ }
1458
1795
  }
1459
1796
  async finalizeDraft(sessionId) {
1460
1797
  const draft = this.sessionDrafts.get(sessionId);
@@ -1478,4 +1815,4 @@ export {
1478
1815
  ChannelAdapter,
1479
1816
  TelegramAdapter
1480
1817
  };
1481
- //# sourceMappingURL=chunk-I6KXISAR.js.map
1818
+ //# sourceMappingURL=chunk-TZGP3JSE.js.map