@openacp/cli 2026.402.1 → 2026.402.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -198,11 +198,11 @@ async function shutdownLogger() {
198
198
  logDir = void 0;
199
199
  initialized = false;
200
200
  if (transport) {
201
- await new Promise((resolve5) => {
202
- const timeout = setTimeout(resolve5, 3e3);
201
+ await new Promise((resolve6) => {
202
+ const timeout = setTimeout(resolve6, 3e3);
203
203
  transport.on("close", () => {
204
204
  clearTimeout(timeout);
205
- resolve5();
205
+ resolve6();
206
206
  });
207
207
  transport.end();
208
208
  });
@@ -1263,7 +1263,7 @@ import https from "https";
1263
1263
  import os9 from "os";
1264
1264
  import { execSync } from "child_process";
1265
1265
  function downloadFile(url, dest) {
1266
- return new Promise((resolve5, reject) => {
1266
+ return new Promise((resolve6, reject) => {
1267
1267
  const file = fs22.createWriteStream(dest);
1268
1268
  const cleanup = () => {
1269
1269
  try {
@@ -1275,7 +1275,7 @@ function downloadFile(url, dest) {
1275
1275
  if (response.statusCode === 301 || response.statusCode === 302) {
1276
1276
  file.close(() => {
1277
1277
  cleanup();
1278
- downloadFile(response.headers.location, dest).then(resolve5).catch(reject);
1278
+ downloadFile(response.headers.location, dest).then(resolve6).catch(reject);
1279
1279
  });
1280
1280
  return;
1281
1281
  }
@@ -1287,7 +1287,7 @@ function downloadFile(url, dest) {
1287
1287
  return;
1288
1288
  }
1289
1289
  response.pipe(file);
1290
- file.on("finish", () => file.close(() => resolve5(dest)));
1290
+ file.on("finish", () => file.close(() => resolve6(dest)));
1291
1291
  file.on("error", (err) => {
1292
1292
  file.close(() => {
1293
1293
  cleanup();
@@ -2306,6 +2306,160 @@ var init_static_server = __esm({
2306
2306
  }
2307
2307
  });
2308
2308
 
2309
+ // src/plugins/telegram/topics.ts
2310
+ var topics_exports = {};
2311
+ __export(topics_exports, {
2312
+ buildDeepLink: () => buildDeepLink,
2313
+ createSessionTopic: () => createSessionTopic,
2314
+ deleteSessionTopic: () => deleteSessionTopic,
2315
+ ensureTopics: () => ensureTopics,
2316
+ renameSessionTopic: () => renameSessionTopic
2317
+ });
2318
+ async function ensureTopics(bot, chatId, config, saveConfig) {
2319
+ let notificationTopicId = config.notificationTopicId;
2320
+ let assistantTopicId = config.assistantTopicId;
2321
+ if (notificationTopicId === null) {
2322
+ const topic = await bot.api.createForumTopic(chatId, "\u{1F4CB} Notifications");
2323
+ notificationTopicId = topic.message_thread_id;
2324
+ await saveConfig({ notificationTopicId });
2325
+ }
2326
+ if (assistantTopicId === null) {
2327
+ const topic = await bot.api.createForumTopic(chatId, "\u{1F916} Assistant");
2328
+ assistantTopicId = topic.message_thread_id;
2329
+ await saveConfig({ assistantTopicId });
2330
+ }
2331
+ return { notificationTopicId, assistantTopicId };
2332
+ }
2333
+ async function createSessionTopic(bot, chatId, name) {
2334
+ const topic = await bot.api.createForumTopic(chatId, name);
2335
+ return topic.message_thread_id;
2336
+ }
2337
+ async function renameSessionTopic(bot, chatId, threadId, name) {
2338
+ try {
2339
+ await bot.api.editForumTopic(chatId, threadId, { name });
2340
+ } catch {
2341
+ }
2342
+ }
2343
+ async function deleteSessionTopic(bot, chatId, threadId) {
2344
+ await bot.api.deleteForumTopic(chatId, threadId);
2345
+ }
2346
+ function buildDeepLink(chatId, threadId, messageId) {
2347
+ const cleanId = String(chatId).replace("-100", "");
2348
+ if (messageId && messageId !== threadId) {
2349
+ return `https://t.me/c/${cleanId}/${threadId}/${messageId}`;
2350
+ }
2351
+ return `https://t.me/c/${cleanId}/${threadId}`;
2352
+ }
2353
+ var init_topics = __esm({
2354
+ "src/plugins/telegram/topics.ts"() {
2355
+ "use strict";
2356
+ }
2357
+ });
2358
+
2359
+ // src/cli/version.ts
2360
+ var version_exports = {};
2361
+ __export(version_exports, {
2362
+ checkAndPromptUpdate: () => checkAndPromptUpdate,
2363
+ compareVersions: () => compareVersions,
2364
+ getCurrentVersion: () => getCurrentVersion,
2365
+ getLatestVersion: () => getLatestVersion,
2366
+ runUpdate: () => runUpdate
2367
+ });
2368
+ import { fileURLToPath as fileURLToPath2 } from "url";
2369
+ import { dirname as dirname11, join as join20, resolve as resolve5 } from "path";
2370
+ import { existsSync as existsSync18, readFileSync as readFileSync14 } from "fs";
2371
+ function findPackageJson() {
2372
+ let dir = dirname11(fileURLToPath2(import.meta.url));
2373
+ for (let i = 0; i < 5; i++) {
2374
+ const candidate = join20(dir, "package.json");
2375
+ if (existsSync18(candidate)) return candidate;
2376
+ const parent = resolve5(dir, "..");
2377
+ if (parent === dir) break;
2378
+ dir = parent;
2379
+ }
2380
+ return null;
2381
+ }
2382
+ function getCurrentVersion() {
2383
+ try {
2384
+ const pkgPath = findPackageJson();
2385
+ if (!pkgPath) return "0.0.0-dev";
2386
+ const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
2387
+ return pkg.version;
2388
+ } catch {
2389
+ return "0.0.0-dev";
2390
+ }
2391
+ }
2392
+ async function getLatestVersion() {
2393
+ try {
2394
+ const res = await fetch(`https://registry.npmjs.org/${NPM_PACKAGE}/latest`, {
2395
+ signal: AbortSignal.timeout(5e3)
2396
+ });
2397
+ if (!res.ok) return null;
2398
+ const data = await res.json();
2399
+ return data.version ?? null;
2400
+ } catch {
2401
+ return null;
2402
+ }
2403
+ }
2404
+ function compareVersions(current, latest) {
2405
+ const a = current.split(".").map(Number);
2406
+ const b = latest.split(".").map(Number);
2407
+ for (let i = 0; i < 3; i++) {
2408
+ if ((a[i] ?? 0) < (b[i] ?? 0)) return -1;
2409
+ if ((a[i] ?? 0) > (b[i] ?? 0)) return 1;
2410
+ }
2411
+ return 0;
2412
+ }
2413
+ async function runUpdate() {
2414
+ const { spawn: spawn4 } = await import("child_process");
2415
+ return new Promise((resolve6) => {
2416
+ const child = spawn4("npm", ["install", "-g", `${NPM_PACKAGE}@latest`], {
2417
+ stdio: "inherit",
2418
+ shell: true
2419
+ });
2420
+ const onSignal = () => {
2421
+ child.kill("SIGTERM");
2422
+ resolve6(false);
2423
+ };
2424
+ process.on("SIGINT", onSignal);
2425
+ process.on("SIGTERM", onSignal);
2426
+ child.on("close", (code) => {
2427
+ process.off("SIGINT", onSignal);
2428
+ process.off("SIGTERM", onSignal);
2429
+ resolve6(code === 0);
2430
+ });
2431
+ });
2432
+ }
2433
+ async function checkAndPromptUpdate() {
2434
+ if (process.env.OPENACP_DEV_LOOP || process.env.OPENACP_SKIP_UPDATE_CHECK) return;
2435
+ const current = getCurrentVersion();
2436
+ if (current === "0.0.0-dev") return;
2437
+ const latest = await getLatestVersion();
2438
+ if (!latest || compareVersions(current, latest) >= 0) return;
2439
+ console.log(`\x1B[33mUpdate available: v${current} \u2192 v${latest}\x1B[0m`);
2440
+ const clack3 = await import("@clack/prompts");
2441
+ const yes = await clack3.confirm({
2442
+ message: "Update now before starting?"
2443
+ });
2444
+ if (clack3.isCancel(yes) || !yes) {
2445
+ return;
2446
+ }
2447
+ const ok2 = await runUpdate();
2448
+ if (ok2) {
2449
+ console.log(`\x1B[32m\u2713 Updated to v${latest}. Please re-run your command.\x1B[0m`);
2450
+ process.exit(0);
2451
+ } else {
2452
+ console.error("\x1B[31mUpdate failed. Continuing with current version.\x1B[0m");
2453
+ }
2454
+ }
2455
+ var NPM_PACKAGE;
2456
+ var init_version = __esm({
2457
+ "src/cli/version.ts"() {
2458
+ "use strict";
2459
+ NPM_PACKAGE = "@openacp/cli";
2460
+ }
2461
+ });
2462
+
2309
2463
  // src/cli/integrate.ts
2310
2464
  var integrate_exports = {};
2311
2465
  __export(integrate_exports, {
@@ -2314,8 +2468,8 @@ __export(integrate_exports, {
2314
2468
  listIntegrations: () => listIntegrations,
2315
2469
  uninstallIntegration: () => uninstallIntegration
2316
2470
  });
2317
- import { existsSync as existsSync18, mkdirSync as mkdirSync12, readFileSync as readFileSync14, writeFileSync as writeFileSync12, unlinkSync as unlinkSync7, chmodSync, rmdirSync } from "fs";
2318
- import { join as join20, dirname as dirname11 } from "path";
2471
+ import { existsSync as existsSync19, mkdirSync as mkdirSync12, readFileSync as readFileSync15, writeFileSync as writeFileSync12, unlinkSync as unlinkSync7, chmodSync, rmdirSync } from "fs";
2472
+ import { join as join21, dirname as dirname12 } from "path";
2319
2473
  import { homedir as homedir10 } from "os";
2320
2474
  function expandPath(p) {
2321
2475
  return p.replace(/^~/, homedir10());
@@ -2437,8 +2591,8 @@ Examples:
2437
2591
  function mergeSettingsJson(settingsPath, hookEvent, hookScriptPath) {
2438
2592
  const fullPath = expandPath(settingsPath);
2439
2593
  let settings = {};
2440
- if (existsSync18(fullPath)) {
2441
- const raw = readFileSync14(fullPath, "utf-8");
2594
+ if (existsSync19(fullPath)) {
2595
+ const raw = readFileSync15(fullPath, "utf-8");
2442
2596
  writeFileSync12(`${fullPath}.bak`, raw);
2443
2597
  settings = JSON.parse(raw);
2444
2598
  }
@@ -2454,14 +2608,14 @@ function mergeSettingsJson(settingsPath, hookEvent, hookScriptPath) {
2454
2608
  hooks: [{ type: "command", command: hookScriptPath }]
2455
2609
  });
2456
2610
  }
2457
- mkdirSync12(dirname11(fullPath), { recursive: true });
2611
+ mkdirSync12(dirname12(fullPath), { recursive: true });
2458
2612
  writeFileSync12(fullPath, JSON.stringify(settings, null, 2) + "\n");
2459
2613
  }
2460
2614
  function mergeHooksJson(settingsPath, hookEvent, hookScriptPath) {
2461
2615
  const fullPath = expandPath(settingsPath);
2462
2616
  let config = { version: 1 };
2463
- if (existsSync18(fullPath)) {
2464
- const raw = readFileSync14(fullPath, "utf-8");
2617
+ if (existsSync19(fullPath)) {
2618
+ const raw = readFileSync15(fullPath, "utf-8");
2465
2619
  writeFileSync12(`${fullPath}.bak`, raw);
2466
2620
  config = JSON.parse(raw);
2467
2621
  }
@@ -2473,13 +2627,13 @@ function mergeHooksJson(settingsPath, hookEvent, hookScriptPath) {
2473
2627
  if (!alreadyInstalled) {
2474
2628
  eventHooks.push({ command: hookScriptPath });
2475
2629
  }
2476
- mkdirSync12(dirname11(fullPath), { recursive: true });
2630
+ mkdirSync12(dirname12(fullPath), { recursive: true });
2477
2631
  writeFileSync12(fullPath, JSON.stringify(config, null, 2) + "\n");
2478
2632
  }
2479
2633
  function removeFromSettingsJson(settingsPath, hookEvent) {
2480
2634
  const fullPath = expandPath(settingsPath);
2481
- if (!existsSync18(fullPath)) return;
2482
- const raw = readFileSync14(fullPath, "utf-8");
2635
+ if (!existsSync19(fullPath)) return;
2636
+ const raw = readFileSync15(fullPath, "utf-8");
2483
2637
  const settings = JSON.parse(raw);
2484
2638
  const hooks = settings.hooks;
2485
2639
  if (!hooks?.[hookEvent]) return;
@@ -2493,8 +2647,8 @@ function removeFromSettingsJson(settingsPath, hookEvent) {
2493
2647
  }
2494
2648
  function removeFromHooksJson(settingsPath, hookEvent) {
2495
2649
  const fullPath = expandPath(settingsPath);
2496
- if (!existsSync18(fullPath)) return;
2497
- const raw = readFileSync14(fullPath, "utf-8");
2650
+ if (!existsSync19(fullPath)) return;
2651
+ const raw = readFileSync15(fullPath, "utf-8");
2498
2652
  const config = JSON.parse(raw);
2499
2653
  const hooks = config.hooks;
2500
2654
  if (!hooks?.[hookEvent]) return;
@@ -2517,30 +2671,30 @@ async function installIntegration(agentKey, spec) {
2517
2671
  }
2518
2672
  const hooksDir = expandPath(spec.hooksDirPath);
2519
2673
  mkdirSync12(hooksDir, { recursive: true });
2520
- const injectPath = join20(hooksDir, "openacp-inject-session.sh");
2674
+ const injectPath = join21(hooksDir, "openacp-inject-session.sh");
2521
2675
  writeFileSync12(injectPath, generateInjectScript(agentKey, spec));
2522
2676
  chmodSync(injectPath, 493);
2523
2677
  logs.push(`Created ${injectPath}`);
2524
- const handoffPath = join20(hooksDir, "openacp-handoff.sh");
2678
+ const handoffPath = join21(hooksDir, "openacp-handoff.sh");
2525
2679
  writeFileSync12(handoffPath, generateHandoffScript(agentKey));
2526
2680
  chmodSync(handoffPath, 493);
2527
2681
  logs.push(`Created ${handoffPath}`);
2528
2682
  if (spec.commandsPath && spec.handoffCommandName) {
2529
2683
  if (spec.commandFormat === "skill") {
2530
- const skillDir = expandPath(join20(spec.commandsPath, spec.handoffCommandName));
2684
+ const skillDir = expandPath(join21(spec.commandsPath, spec.handoffCommandName));
2531
2685
  mkdirSync12(skillDir, { recursive: true });
2532
- const skillPath = join20(skillDir, "SKILL.md");
2686
+ const skillPath = join21(skillDir, "SKILL.md");
2533
2687
  writeFileSync12(skillPath, generateHandoffCommand(agentKey, spec));
2534
2688
  logs.push(`Created ${skillPath}`);
2535
2689
  } else {
2536
2690
  const cmdsDir = expandPath(spec.commandsPath);
2537
2691
  mkdirSync12(cmdsDir, { recursive: true });
2538
- const cmdPath = join20(cmdsDir, `${spec.handoffCommandName}.md`);
2692
+ const cmdPath = join21(cmdsDir, `${spec.handoffCommandName}.md`);
2539
2693
  writeFileSync12(cmdPath, generateHandoffCommand(agentKey, spec));
2540
2694
  logs.push(`Created ${cmdPath}`);
2541
2695
  }
2542
2696
  }
2543
- const injectFullPath = join20(hooksDir, "openacp-inject-session.sh");
2697
+ const injectFullPath = join21(hooksDir, "openacp-inject-session.sh");
2544
2698
  if (spec.settingsFormat === "hooks_json") {
2545
2699
  mergeHooksJson(spec.settingsPath, spec.hookEvent, injectFullPath);
2546
2700
  } else {
@@ -2558,17 +2712,17 @@ async function uninstallIntegration(agentKey, spec) {
2558
2712
  try {
2559
2713
  const hooksDir = expandPath(spec.hooksDirPath);
2560
2714
  for (const filename of ["openacp-inject-session.sh", "openacp-handoff.sh"]) {
2561
- const filePath = join20(hooksDir, filename);
2562
- if (existsSync18(filePath)) {
2715
+ const filePath = join21(hooksDir, filename);
2716
+ if (existsSync19(filePath)) {
2563
2717
  unlinkSync7(filePath);
2564
2718
  logs.push(`Removed ${filePath}`);
2565
2719
  }
2566
2720
  }
2567
2721
  if (spec.commandsPath && spec.handoffCommandName) {
2568
2722
  if (spec.commandFormat === "skill") {
2569
- const skillDir = expandPath(join20(spec.commandsPath, spec.handoffCommandName));
2570
- const skillPath = join20(skillDir, "SKILL.md");
2571
- if (existsSync18(skillPath)) {
2723
+ const skillDir = expandPath(join21(spec.commandsPath, spec.handoffCommandName));
2724
+ const skillPath = join21(skillDir, "SKILL.md");
2725
+ if (existsSync19(skillPath)) {
2572
2726
  unlinkSync7(skillPath);
2573
2727
  try {
2574
2728
  rmdirSync(skillDir);
@@ -2577,8 +2731,8 @@ async function uninstallIntegration(agentKey, spec) {
2577
2731
  logs.push(`Removed ${skillPath}`);
2578
2732
  }
2579
2733
  } else {
2580
- const cmdPath = expandPath(join20(spec.commandsPath, `${spec.handoffCommandName}.md`));
2581
- if (existsSync18(cmdPath)) {
2734
+ const cmdPath = expandPath(join21(spec.commandsPath, `${spec.handoffCommandName}.md`));
2735
+ if (existsSync19(cmdPath)) {
2582
2736
  unlinkSync7(cmdPath);
2583
2737
  logs.push(`Removed ${cmdPath}`);
2584
2738
  }
@@ -2603,7 +2757,7 @@ function buildHandoffItem(agentKey, spec) {
2603
2757
  name: "Handoff",
2604
2758
  description: "Transfer sessions between terminal and messaging platforms",
2605
2759
  isInstalled() {
2606
- return existsSync18(join20(hooksDir, "openacp-inject-session.sh")) && existsSync18(join20(hooksDir, "openacp-handoff.sh"));
2760
+ return existsSync19(join21(hooksDir, "openacp-inject-session.sh")) && existsSync19(join21(hooksDir, "openacp-handoff.sh"));
2607
2761
  },
2608
2762
  install: () => installIntegration(agentKey, spec),
2609
2763
  uninstall: () => uninstallIntegration(agentKey, spec)
@@ -2617,20 +2771,20 @@ function getSkillBasePath(spec) {
2617
2771
  function buildTunnelItem(spec) {
2618
2772
  if (!spec.commandsPath) return null;
2619
2773
  function getTunnelPath() {
2620
- return join20(getSkillBasePath(spec), "openacp-tunnel", "SKILL.md");
2774
+ return join21(getSkillBasePath(spec), "openacp-tunnel", "SKILL.md");
2621
2775
  }
2622
2776
  return {
2623
2777
  id: "tunnel",
2624
2778
  name: "Tunnel",
2625
2779
  description: "Expose local ports to the internet via OpenACP tunnel",
2626
2780
  isInstalled() {
2627
- return existsSync18(getTunnelPath());
2781
+ return existsSync19(getTunnelPath());
2628
2782
  },
2629
2783
  async install() {
2630
2784
  const logs = [];
2631
2785
  try {
2632
2786
  const skillPath = getTunnelPath();
2633
- mkdirSync12(dirname11(skillPath), { recursive: true });
2787
+ mkdirSync12(dirname12(skillPath), { recursive: true });
2634
2788
  writeFileSync12(skillPath, generateTunnelCommand());
2635
2789
  logs.push(`Created ${skillPath}`);
2636
2790
  return { success: true, logs };
@@ -2643,10 +2797,10 @@ function buildTunnelItem(spec) {
2643
2797
  const logs = [];
2644
2798
  try {
2645
2799
  const skillPath = getTunnelPath();
2646
- if (existsSync18(skillPath)) {
2800
+ if (existsSync19(skillPath)) {
2647
2801
  unlinkSync7(skillPath);
2648
2802
  try {
2649
- rmdirSync(dirname11(skillPath));
2803
+ rmdirSync(dirname12(skillPath));
2650
2804
  } catch {
2651
2805
  }
2652
2806
  logs.push(`Removed ${skillPath}`);
@@ -2688,13 +2842,13 @@ __export(menu_exports, {
2688
2842
  handleHelp: () => handleHelp,
2689
2843
  handleMenu: () => handleMenu
2690
2844
  });
2691
- import { InlineKeyboard as InlineKeyboard4 } from "grammy";
2845
+ import { InlineKeyboard as InlineKeyboard5 } from "grammy";
2692
2846
  function buildMenuKeyboard(menuRegistry) {
2693
2847
  if (!menuRegistry) {
2694
- return new InlineKeyboard4().text("\u{1F195} New Session", "m:core:new").text("\u{1F4CB} Sessions", "m:core:sessions").row().text("\u{1F4CA} Status", "m:core:status").text("\u{1F916} Agents", "m:core:agents").row().text("\u2753 Help", "m:core:help");
2848
+ return new InlineKeyboard5().text("\u{1F195} New Session", "m:core:new").text("\u{1F4CB} Sessions", "m:core:sessions").row().text("\u{1F4CA} Status", "m:core:status").text("\u{1F916} Agents", "m:core:agents").row().text("\u2753 Help", "m:core:help");
2695
2849
  }
2696
2850
  const items = menuRegistry.getItems();
2697
- const kb = new InlineKeyboard4();
2851
+ const kb = new InlineKeyboard5();
2698
2852
  let currentGroup;
2699
2853
  let rowCount = 0;
2700
2854
  for (const item of items) {
@@ -2712,11 +2866,11 @@ function buildMenuKeyboard(menuRegistry) {
2712
2866
  }
2713
2867
  return kb;
2714
2868
  }
2715
- async function handleMenu(ctx) {
2869
+ async function handleMenu(ctx, menuRegistry) {
2716
2870
  await ctx.reply(`<b>OpenACP Menu</b>
2717
2871
  Choose an action:`, {
2718
2872
  parse_mode: "HTML",
2719
- reply_markup: buildMenuKeyboard()
2873
+ reply_markup: buildMenuKeyboard(menuRegistry)
2720
2874
  });
2721
2875
  }
2722
2876
  async function handleHelp(ctx) {
@@ -2733,7 +2887,7 @@ Each session gets its own topic \u2014 chat there to work with the agent.
2733
2887
  /status \u2014 Show session or system status
2734
2888
  /sessions \u2014 List all sessions
2735
2889
  /agents \u2014 Browse & install agents
2736
- /install <name> \u2014 Install an agent
2890
+ /install &lt;name&gt; \u2014 Install an agent
2737
2891
 
2738
2892
  \u2699\uFE0F <b>System</b>
2739
2893
  /restart \u2014 Restart OpenACP
@@ -2803,16 +2957,19 @@ __export(assistant_exports, {
2803
2957
  redirectToAssistant: () => redirectToAssistant
2804
2958
  });
2805
2959
  function buildWelcomeMessage(ctx) {
2806
- const { activeCount, errorCount, totalCount, agents, defaultAgent } = ctx;
2960
+ const { activeCount, errorCount, totalCount, agents, defaultAgent, workspace } = ctx;
2807
2961
  const agentList = agents.map((a) => `${a}${a === defaultAgent ? " (default)" : ""}`).join(", ");
2808
2962
  if (totalCount === 0) {
2809
2963
  return `\u{1F44B} <b>OpenACP is ready!</b>
2810
2964
 
2965
+ \u{1F4C2} ${workspace}
2966
+
2811
2967
  No sessions yet. Tap \u{1F195} New Session to start, or ask me anything!`;
2812
2968
  }
2813
2969
  if (errorCount > 0) {
2814
2970
  return `\u{1F44B} <b>OpenACP is ready!</b>
2815
2971
 
2972
+ \u{1F4C2} ${workspace}
2816
2973
  \u{1F4CA} ${activeCount} active, ${errorCount} errors / ${totalCount} total
2817
2974
  \u26A0\uFE0F ${errorCount} session${errorCount > 1 ? "s have" : " has"} errors \u2014 ask me to check if you'd like.
2818
2975
 
@@ -2820,6 +2977,7 @@ Agents: ${agentList}`;
2820
2977
  }
2821
2978
  return `\u{1F44B} <b>OpenACP is ready!</b>
2822
2979
 
2980
+ \u{1F4C2} ${workspace}
2823
2981
  \u{1F4CA} ${activeCount} active / ${totalCount} total
2824
2982
  Agents: ${agentList}`;
2825
2983
  }
@@ -2867,13 +3025,13 @@ var ChannelAdapter = class {
2867
3025
  function nodeToWebWritable(nodeStream) {
2868
3026
  return new WritableStream({
2869
3027
  write(chunk) {
2870
- return new Promise((resolve5, reject) => {
3028
+ return new Promise((resolve6, reject) => {
2871
3029
  const ok2 = nodeStream.write(chunk);
2872
3030
  if (ok2) {
2873
- resolve5();
3031
+ resolve6();
2874
3032
  return;
2875
3033
  }
2876
- nodeStream.once("drain", resolve5);
3034
+ nodeStream.once("drain", resolve6);
2877
3035
  nodeStream.once("error", reject);
2878
3036
  });
2879
3037
  },
@@ -3113,9 +3271,9 @@ var TerminalManager = class {
3113
3271
  signal: state.exitStatus.signal
3114
3272
  };
3115
3273
  }
3116
- return new Promise((resolve5) => {
3274
+ return new Promise((resolve6) => {
3117
3275
  state.process.on("exit", (code, signal) => {
3118
- resolve5({ exitCode: code, signal });
3276
+ resolve6({ exitCode: code, signal });
3119
3277
  });
3120
3278
  });
3121
3279
  }
@@ -3292,7 +3450,7 @@ var AgentInstance = class _AgentInstance extends TypedEmitter {
3292
3450
  env: { ...process.env, ...agentDef.env }
3293
3451
  }
3294
3452
  );
3295
- await new Promise((resolve5, reject) => {
3453
+ await new Promise((resolve6, reject) => {
3296
3454
  instance.child.on("error", (err) => {
3297
3455
  reject(
3298
3456
  new Error(
@@ -3300,7 +3458,7 @@ var AgentInstance = class _AgentInstance extends TypedEmitter {
3300
3458
  )
3301
3459
  );
3302
3460
  });
3303
- instance.child.on("spawn", () => resolve5());
3461
+ instance.child.on("spawn", () => resolve6());
3304
3462
  });
3305
3463
  instance.stderrCapture = new StderrCapture(50);
3306
3464
  instance.child.stderr.on("data", (chunk) => {
@@ -3726,15 +3884,15 @@ ${stderr}`
3726
3884
  this._destroying = true;
3727
3885
  this.terminalManager.destroyAll();
3728
3886
  if (this.child.exitCode !== null) return;
3729
- await new Promise((resolve5) => {
3887
+ await new Promise((resolve6) => {
3730
3888
  this.child.on("exit", () => {
3731
3889
  clearTimeout(forceKillTimer);
3732
- resolve5();
3890
+ resolve6();
3733
3891
  });
3734
3892
  this.child.kill("SIGTERM");
3735
3893
  const forceKillTimer = setTimeout(() => {
3736
3894
  if (this.child.exitCode === null) this.child.kill("SIGKILL");
3737
- resolve5();
3895
+ resolve6();
3738
3896
  }, 1e4);
3739
3897
  if (typeof forceKillTimer === "object" && forceKillTimer !== null && "unref" in forceKillTimer) {
3740
3898
  forceKillTimer.unref();
@@ -3786,8 +3944,8 @@ var PromptQueue = class {
3786
3944
  abortController = null;
3787
3945
  async enqueue(text3, attachments) {
3788
3946
  if (this.processing) {
3789
- return new Promise((resolve5) => {
3790
- this.queue.push({ text: text3, attachments, resolve: resolve5 });
3947
+ return new Promise((resolve6) => {
3948
+ this.queue.push({ text: text3, attachments, resolve: resolve6 });
3791
3949
  });
3792
3950
  }
3793
3951
  await this.process(text3, attachments);
@@ -3855,8 +4013,8 @@ var PermissionGate = class {
3855
4013
  this.request = request;
3856
4014
  this.settled = false;
3857
4015
  this.clearTimeout();
3858
- return new Promise((resolve5, reject) => {
3859
- this.resolveFn = resolve5;
4016
+ return new Promise((resolve6, reject) => {
4017
+ this.resolveFn = resolve6;
3860
4018
  this.rejectFn = reject;
3861
4019
  this.timeoutTimer = setTimeout(() => {
3862
4020
  this.reject("Permission request timed out (no response received)");
@@ -6847,13 +7005,13 @@ function createPluginContext(opts) {
6847
7005
  var SETUP_TIMEOUT_MS = 3e4;
6848
7006
  var TEARDOWN_TIMEOUT_MS = 1e4;
6849
7007
  function withTimeout(promise, ms, label) {
6850
- return new Promise((resolve5, reject) => {
7008
+ return new Promise((resolve6, reject) => {
6851
7009
  const timer = setTimeout(() => reject(new Error(`Timeout: ${label} exceeded ${ms}ms`)), ms);
6852
7010
  if (typeof timer === "object" && timer !== null && "unref" in timer) {
6853
7011
  ;
6854
7012
  timer.unref();
6855
7013
  }
6856
- promise.then(resolve5, reject).finally(() => clearTimeout(timer));
7014
+ promise.then(resolve6, reject).finally(() => clearTimeout(timer));
6857
7015
  });
6858
7016
  }
6859
7017
  function resolvePluginConfig(pluginName, configManager) {
@@ -7242,8 +7400,8 @@ var AssistantManager = class {
7242
7400
  this.registry = registry;
7243
7401
  }
7244
7402
  sessions = /* @__PURE__ */ new Map();
7245
- readyState = /* @__PURE__ */ new Map();
7246
7403
  respawning = /* @__PURE__ */ new Set();
7404
+ pendingSystemPrompts = /* @__PURE__ */ new Map();
7247
7405
  async spawn(channelId, threadId) {
7248
7406
  const session = await this.core.createSession({
7249
7407
  channelId,
@@ -7255,17 +7413,22 @@ var AssistantManager = class {
7255
7413
  session.threadId = threadId;
7256
7414
  this.sessions.set(channelId, session);
7257
7415
  const systemPrompt = this.registry.buildSystemPrompt(channelId);
7258
- const ready = session.enqueuePrompt(systemPrompt).then(() => {
7259
- log15.info({ sessionId: session.id, channelId }, "Assistant ready");
7260
- }).catch((err) => {
7261
- log15.warn({ err, channelId }, "Assistant system prompt failed");
7262
- });
7263
- this.readyState.set(channelId, ready);
7416
+ this.pendingSystemPrompts.set(channelId, systemPrompt);
7417
+ log15.info({ sessionId: session.id, channelId }, "Assistant spawned (system prompt deferred)");
7264
7418
  return session;
7265
7419
  }
7266
7420
  get(channelId) {
7267
7421
  return this.sessions.get(channelId) ?? null;
7268
7422
  }
7423
+ /**
7424
+ * Consume and return any pending system prompt for a channel.
7425
+ * Should be prepended to the first real user message.
7426
+ */
7427
+ consumePendingSystemPrompt(channelId) {
7428
+ const prompt = this.pendingSystemPrompts.get(channelId);
7429
+ if (prompt) this.pendingSystemPrompts.delete(channelId);
7430
+ return prompt;
7431
+ }
7269
7432
  isAssistant(sessionId) {
7270
7433
  for (const s of this.sessions.values()) {
7271
7434
  if (s.id === sessionId) return true;
@@ -7285,9 +7448,6 @@ var AssistantManager = class {
7285
7448
  this.respawning.delete(channelId);
7286
7449
  }
7287
7450
  }
7288
- async waitReady(channelId) {
7289
- await this.readyState.get(channelId);
7290
- }
7291
7451
  };
7292
7452
 
7293
7453
  // src/core/assistant/sections/sessions.ts
@@ -7706,7 +7866,19 @@ var OpenACPCore = class {
7706
7866
  this.sessionManager.patchRecord(session.id, {
7707
7867
  lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
7708
7868
  });
7709
- await session.enqueuePrompt(message.text, message.attachments);
7869
+ let text3 = message.text;
7870
+ if (this.assistantManager?.isAssistant(session.id)) {
7871
+ const pending = this.assistantManager.consumePendingSystemPrompt(message.channelId);
7872
+ if (pending) {
7873
+ text3 = `${pending}
7874
+
7875
+ ---
7876
+
7877
+ User message:
7878
+ ${text3}`;
7879
+ }
7880
+ }
7881
+ await session.enqueuePrompt(text3, message.attachments);
7710
7882
  }
7711
7883
  // --- Unified Session Creation Pipeline ---
7712
7884
  async createSession(params) {
@@ -7813,8 +7985,8 @@ var OpenACPCore = class {
7813
7985
  message: `Agent '${agentName}' not found`
7814
7986
  };
7815
7987
  }
7816
- const { existsSync: existsSync19 } = await import("fs");
7817
- if (!existsSync19(cwd)) {
7988
+ const { existsSync: existsSync20 } = await import("fs");
7989
+ if (!existsSync20(cwd)) {
7818
7990
  return {
7819
7991
  ok: false,
7820
7992
  error: "invalid_cwd",
@@ -8468,12 +8640,12 @@ function isProcessAlive(pid) {
8468
8640
  }
8469
8641
  }
8470
8642
  function checkPortInUse(port) {
8471
- return new Promise((resolve5) => {
8643
+ return new Promise((resolve6) => {
8472
8644
  const server = net.createServer();
8473
- server.once("error", () => resolve5(true));
8645
+ server.once("error", () => resolve6(true));
8474
8646
  server.once("listening", () => {
8475
8647
  server.close();
8476
- resolve5(false);
8648
+ resolve6(false);
8477
8649
  });
8478
8650
  server.listen(port, "127.0.0.1");
8479
8651
  });
@@ -9614,7 +9786,7 @@ function startDaemon(pidPath = getPidPath(), logDir2, instanceRoot) {
9614
9786
  return { pid: child.pid };
9615
9787
  }
9616
9788
  function sleep(ms) {
9617
- return new Promise((resolve5) => setTimeout(resolve5, ms));
9789
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
9618
9790
  }
9619
9791
  function isProcessAlive2(pid) {
9620
9792
  try {
@@ -11426,10 +11598,10 @@ var SendQueue = class {
11426
11598
  const type = opts?.type ?? "other";
11427
11599
  const key = opts?.key;
11428
11600
  const category = opts?.category;
11429
- let resolve5;
11601
+ let resolve6;
11430
11602
  let reject;
11431
11603
  const promise = new Promise((res, rej) => {
11432
- resolve5 = res;
11604
+ resolve6 = res;
11433
11605
  reject = rej;
11434
11606
  });
11435
11607
  promise.catch(() => {
@@ -11440,12 +11612,12 @@ var SendQueue = class {
11440
11612
  );
11441
11613
  if (idx !== -1) {
11442
11614
  this.items[idx].resolve(void 0);
11443
- this.items[idx] = { fn, type, key, category, resolve: resolve5, reject, promise };
11615
+ this.items[idx] = { fn, type, key, category, resolve: resolve6, reject, promise };
11444
11616
  this.scheduleProcess();
11445
11617
  return promise;
11446
11618
  }
11447
11619
  }
11448
- this.items.push({ fn, type, key, category, resolve: resolve5, reject, promise });
11620
+ this.items.push({ fn, type, key, category, resolve: resolve6, reject, promise });
11449
11621
  this.scheduleProcess();
11450
11622
  return promise;
11451
11623
  }
@@ -12495,44 +12667,11 @@ Session logs auto-cleanup: 30 days (configurable via \`logging.sessionLogRetenti
12495
12667
 
12496
12668
  // src/plugins/telegram/adapter.ts
12497
12669
  init_log();
12670
+ init_topics();
12498
12671
  import { Bot, InputFile } from "grammy";
12499
12672
 
12500
- // src/plugins/telegram/topics.ts
12501
- async function ensureTopics(bot, chatId, config, saveConfig) {
12502
- let notificationTopicId = config.notificationTopicId;
12503
- let assistantTopicId = config.assistantTopicId;
12504
- if (notificationTopicId === null) {
12505
- const topic = await bot.api.createForumTopic(chatId, "\u{1F4CB} Notifications");
12506
- notificationTopicId = topic.message_thread_id;
12507
- await saveConfig({ notificationTopicId });
12508
- }
12509
- if (assistantTopicId === null) {
12510
- const topic = await bot.api.createForumTopic(chatId, "\u{1F916} Assistant");
12511
- assistantTopicId = topic.message_thread_id;
12512
- await saveConfig({ assistantTopicId });
12513
- }
12514
- return { notificationTopicId, assistantTopicId };
12515
- }
12516
- async function createSessionTopic(bot, chatId, name) {
12517
- const topic = await bot.api.createForumTopic(chatId, name);
12518
- return topic.message_thread_id;
12519
- }
12520
- async function renameSessionTopic(bot, chatId, threadId, name) {
12521
- try {
12522
- await bot.api.editForumTopic(chatId, threadId, { name });
12523
- } catch {
12524
- }
12525
- }
12526
- async function deleteSessionTopic(bot, chatId, threadId) {
12527
- await bot.api.deleteForumTopic(chatId, threadId);
12528
- }
12529
- function buildDeepLink(chatId, threadId, messageId) {
12530
- const cleanId = String(chatId).replace("-100", "");
12531
- if (messageId && messageId !== threadId) {
12532
- return `https://t.me/c/${cleanId}/${threadId}/${messageId}`;
12533
- }
12534
- return `https://t.me/c/${cleanId}/${threadId}`;
12535
- }
12673
+ // src/plugins/telegram/commands/new-session.ts
12674
+ import { InlineKeyboard as InlineKeyboard2 } from "grammy";
12536
12675
 
12537
12676
  // src/plugins/telegram/formatting.ts
12538
12677
  function escapeHtml(text3) {
@@ -12747,6 +12886,7 @@ ${safeSection}` : safeSection;
12747
12886
  }
12748
12887
 
12749
12888
  // src/plugins/telegram/commands/new-session.ts
12889
+ init_topics();
12750
12890
  init_log();
12751
12891
 
12752
12892
  // src/plugins/telegram/commands/admin.ts
@@ -12920,6 +13060,79 @@ function setupVerbosityCallbacks(bot, core) {
12920
13060
  }
12921
13061
  });
12922
13062
  }
13063
+ async function handleUpdate(ctx, core) {
13064
+ if (!core.requestRestart) {
13065
+ await ctx.reply(
13066
+ "\u26A0\uFE0F Update is not available (no restart handler registered).",
13067
+ { parse_mode: "HTML" }
13068
+ );
13069
+ return;
13070
+ }
13071
+ const { getCurrentVersion: getCurrentVersion2, getLatestVersion: getLatestVersion2, compareVersions: compareVersions2, runUpdate: runUpdate2 } = await Promise.resolve().then(() => (init_version(), version_exports));
13072
+ const current = getCurrentVersion2();
13073
+ const statusMsg = await ctx.reply(
13074
+ `\u{1F50D} Checking for updates... (current: v${escapeHtml(current)})`,
13075
+ { parse_mode: "HTML" }
13076
+ );
13077
+ const latest = await getLatestVersion2();
13078
+ if (!latest) {
13079
+ await ctx.api.editMessageText(
13080
+ ctx.chat.id,
13081
+ statusMsg.message_id,
13082
+ "\u274C Could not check for updates.",
13083
+ { parse_mode: "HTML" }
13084
+ );
13085
+ return;
13086
+ }
13087
+ if (compareVersions2(current, latest) >= 0) {
13088
+ await ctx.api.editMessageText(
13089
+ ctx.chat.id,
13090
+ statusMsg.message_id,
13091
+ `\u2705 Already up to date (v${escapeHtml(current)}).`,
13092
+ { parse_mode: "HTML" }
13093
+ );
13094
+ return;
13095
+ }
13096
+ await ctx.api.editMessageText(
13097
+ ctx.chat.id,
13098
+ statusMsg.message_id,
13099
+ `\u2B07\uFE0F Updating v${escapeHtml(current)} \u2192 v${escapeHtml(latest)}...`,
13100
+ { parse_mode: "HTML" }
13101
+ );
13102
+ const ok2 = await runUpdate2();
13103
+ if (!ok2) {
13104
+ await ctx.api.editMessageText(
13105
+ ctx.chat.id,
13106
+ statusMsg.message_id,
13107
+ "\u274C Update failed. Try manually: <code>npm install -g @openacp/cli@latest</code>",
13108
+ { parse_mode: "HTML" }
13109
+ );
13110
+ return;
13111
+ }
13112
+ await ctx.api.editMessageText(
13113
+ ctx.chat.id,
13114
+ statusMsg.message_id,
13115
+ `\u2705 Updated to v${escapeHtml(latest)}. Restarting...`,
13116
+ { parse_mode: "HTML" }
13117
+ );
13118
+ await new Promise((r) => setTimeout(r, 500));
13119
+ await core.requestRestart();
13120
+ }
13121
+ async function handleRestart(ctx, core) {
13122
+ if (!core.requestRestart) {
13123
+ await ctx.reply(
13124
+ "\u26A0\uFE0F Restart is not available (no restart handler registered).",
13125
+ { parse_mode: "HTML" }
13126
+ );
13127
+ return;
13128
+ }
13129
+ await ctx.reply(
13130
+ "\u{1F504} <b>Restarting OpenACP...</b>\nRebuilding and restarting. Be back shortly.",
13131
+ { parse_mode: "HTML" }
13132
+ );
13133
+ await new Promise((r) => setTimeout(r, 500));
13134
+ await core.requestRestart();
13135
+ }
12923
13136
 
12924
13137
  // src/plugins/telegram/commands/new-session.ts
12925
13138
  var log22 = createChildLogger({ module: "telegram-cmd-new-session" });
@@ -12968,11 +13181,252 @@ async function createSessionDirect(ctx, core, chatId, agentName, workspace, onCo
12968
13181
  return null;
12969
13182
  }
12970
13183
  }
13184
+ var WS_CACHE_MAX = 50;
13185
+ var workspaceCache = /* @__PURE__ */ new Map();
13186
+ var nextWsId = 0;
13187
+ function cacheWorkspace(agentKey, workspace) {
13188
+ const now = Date.now();
13189
+ if (workspaceCache.size > WS_CACHE_MAX) {
13190
+ for (const [id2, entry] of workspaceCache) {
13191
+ if (now - entry.ts > 5 * 6e4 || workspaceCache.size > WS_CACHE_MAX) {
13192
+ workspaceCache.delete(id2);
13193
+ }
13194
+ }
13195
+ }
13196
+ const id = nextWsId++;
13197
+ workspaceCache.set(id, { agentKey, workspace, ts: now });
13198
+ return id;
13199
+ }
13200
+ function shortenPath2(ws) {
13201
+ const home = process.env.HOME || "";
13202
+ return home && ws.startsWith(home) ? "~" + ws.slice(home.length) : ws;
13203
+ }
13204
+ async function showAgentPicker(ctx, core, chatId) {
13205
+ const catalog = core.agentCatalog;
13206
+ const installed = catalog.getAvailable().filter((i) => i.installed);
13207
+ if (installed.length === 0) {
13208
+ await ctx.reply("No agents installed. Use /install to add one.", { parse_mode: "HTML" }).catch(() => {
13209
+ });
13210
+ return;
13211
+ }
13212
+ if (installed.length === 1) {
13213
+ await showWorkspacePicker(ctx, core, chatId, installed[0].key, true);
13214
+ return;
13215
+ }
13216
+ const kb = new InlineKeyboard2();
13217
+ for (let i = 0; i < installed.length; i += 2) {
13218
+ const row = installed.slice(i, i + 2);
13219
+ for (const agent of row) {
13220
+ kb.text(agent.name, `ns:agent:${agent.key}`);
13221
+ }
13222
+ kb.row();
13223
+ }
13224
+ await ctx.reply("<b>\u{1F195} New Session</b>\nSelect an agent:", {
13225
+ parse_mode: "HTML",
13226
+ reply_markup: kb
13227
+ }).catch(() => {
13228
+ });
13229
+ }
13230
+ async function showWorkspacePicker(ctx, core, chatId, agentKey, newMessage = false) {
13231
+ const records = core.sessionManager.listRecords();
13232
+ const recentWorkspaces = [...new Set(records.map((r) => r.workingDir).filter(Boolean))].slice(0, 5);
13233
+ const config = core.configManager.get();
13234
+ const baseDir = config.workspace.baseDir;
13235
+ const resolvedBaseDir = core.configManager.resolveWorkspace(baseDir);
13236
+ const hasBaseDir = recentWorkspaces.some((ws) => ws === baseDir || ws === resolvedBaseDir);
13237
+ const workspaces = hasBaseDir ? recentWorkspaces : [resolvedBaseDir, ...recentWorkspaces].slice(0, 5);
13238
+ const kb = new InlineKeyboard2();
13239
+ for (const ws of workspaces) {
13240
+ const id = cacheWorkspace(agentKey, ws);
13241
+ kb.text(`\u{1F4C1} ${shortenPath2(ws)}`, `ns:ws:${id}`).row();
13242
+ }
13243
+ kb.text("\u{1F4C1} Custom path...", `ns:custom:${agentKey}`).row();
13244
+ const agentLabel = escapeHtml(agentKey);
13245
+ const text3 = `<b>\u{1F195} New Session</b>
13246
+ Agent: <code>${agentLabel}</code>
13247
+
13248
+ Select workspace:`;
13249
+ const opts = { parse_mode: "HTML", reply_markup: kb };
13250
+ if (newMessage) {
13251
+ await ctx.reply(text3, opts).catch(() => {
13252
+ });
13253
+ } else {
13254
+ try {
13255
+ await ctx.editMessageText(text3, opts);
13256
+ } catch {
13257
+ await ctx.reply(text3, opts).catch(() => {
13258
+ });
13259
+ }
13260
+ }
13261
+ }
13262
+ function setupNewSessionCallbacks(bot, core, chatId, getAssistantSession) {
13263
+ bot.callbackQuery("ns:start", async (ctx) => {
13264
+ try {
13265
+ await ctx.answerCallbackQuery();
13266
+ } catch {
13267
+ }
13268
+ await showAgentPicker(ctx, core, chatId);
13269
+ });
13270
+ bot.callbackQuery(/^ns:agent:/, async (ctx) => {
13271
+ const agentKey = ctx.callbackQuery.data.replace("ns:agent:", "");
13272
+ try {
13273
+ await ctx.answerCallbackQuery();
13274
+ } catch {
13275
+ }
13276
+ await showWorkspacePicker(ctx, core, chatId, agentKey);
13277
+ });
13278
+ bot.callbackQuery(/^ns:ws:/, async (ctx) => {
13279
+ const id = parseInt(ctx.callbackQuery.data.replace("ns:ws:", ""), 10);
13280
+ try {
13281
+ await ctx.answerCallbackQuery();
13282
+ } catch {
13283
+ }
13284
+ const entry = workspaceCache.get(id);
13285
+ if (!entry) {
13286
+ try {
13287
+ await ctx.editMessageText("\u26A0\uFE0F Session expired. Please try again via /menu.");
13288
+ } catch {
13289
+ }
13290
+ return;
13291
+ }
13292
+ workspaceCache.delete(id);
13293
+ try {
13294
+ await ctx.editMessageText(
13295
+ `<b>\u{1F195} New Session</b>
13296
+ Agent: <code>${escapeHtml(entry.agentKey)}</code>
13297
+ Workspace: <code>${escapeHtml(shortenPath2(entry.workspace))}</code>
13298
+
13299
+ \u23F3 Creating session...`,
13300
+ { parse_mode: "HTML" }
13301
+ );
13302
+ } catch {
13303
+ }
13304
+ const threadId = await createSessionDirect(ctx, core, chatId, entry.agentKey, entry.workspace);
13305
+ if (threadId) {
13306
+ const { buildDeepLink: buildDeepLink2 } = await Promise.resolve().then(() => (init_topics(), topics_exports));
13307
+ const link = buildDeepLink2(chatId, threadId);
13308
+ try {
13309
+ await ctx.editMessageText(
13310
+ `<b>\u2705 Session created</b>
13311
+ Agent: <code>${escapeHtml(entry.agentKey)}</code>
13312
+ Workspace: <code>${escapeHtml(shortenPath2(entry.workspace))}</code>
13313
+
13314
+ <a href="${link}">Open session \u2192</a>`,
13315
+ { parse_mode: "HTML" }
13316
+ );
13317
+ } catch {
13318
+ }
13319
+ } else {
13320
+ try {
13321
+ await ctx.editMessageText(
13322
+ `<b>\u274C Session creation failed</b>
13323
+ Agent: <code>${escapeHtml(entry.agentKey)}</code>
13324
+ Workspace: <code>${escapeHtml(shortenPath2(entry.workspace))}</code>
13325
+
13326
+ Try again with /new or /menu`,
13327
+ { parse_mode: "HTML" }
13328
+ );
13329
+ } catch {
13330
+ }
13331
+ }
13332
+ });
13333
+ bot.callbackQuery(/^ns:custom:/, async (ctx) => {
13334
+ const agentKey = ctx.callbackQuery.data.replace("ns:custom:", "");
13335
+ try {
13336
+ await ctx.answerCallbackQuery();
13337
+ } catch {
13338
+ }
13339
+ const assistant = getAssistantSession?.();
13340
+ if (assistant) {
13341
+ try {
13342
+ await ctx.editMessageText(
13343
+ `<b>\u{1F195} New Session</b>
13344
+ Agent: <code>${escapeHtml(agentKey)}</code>
13345
+
13346
+ \u{1F4AC} Type your workspace path in the chat below.`,
13347
+ { parse_mode: "HTML" }
13348
+ );
13349
+ } catch {
13350
+ }
13351
+ await assistant.enqueuePrompt(
13352
+ `User wants to create a new session with agent "${agentKey}". Ask them for the workspace (project directory) path, then create the session.`
13353
+ );
13354
+ } else {
13355
+ try {
13356
+ await ctx.editMessageText(
13357
+ `Usage: <code>/new ${escapeHtml(agentKey)} &lt;workspace-path&gt;</code>`,
13358
+ { parse_mode: "HTML" }
13359
+ );
13360
+ } catch {
13361
+ }
13362
+ }
13363
+ });
13364
+ }
12971
13365
 
12972
13366
  // src/plugins/telegram/commands/session.ts
12973
- import { InlineKeyboard as InlineKeyboard2 } from "grammy";
13367
+ import { InlineKeyboard as InlineKeyboard3 } from "grammy";
12974
13368
  init_log();
12975
13369
  var log23 = createChildLogger({ module: "telegram-cmd-session" });
13370
+ async function handleTopics(ctx, core) {
13371
+ try {
13372
+ const allRecords = core.sessionManager.listRecords();
13373
+ const records = allRecords.filter((r) => {
13374
+ const platform2 = r.platform;
13375
+ return !!platform2?.topicId;
13376
+ });
13377
+ const headlessCount = allRecords.length - records.length;
13378
+ if (records.length === 0) {
13379
+ const extra = headlessCount > 0 ? ` (${headlessCount} headless hidden)` : "";
13380
+ await ctx.reply(`No sessions with topics found.${extra}`, { parse_mode: "HTML" });
13381
+ return;
13382
+ }
13383
+ const statusEmoji = {
13384
+ active: "\u{1F7E2}",
13385
+ initializing: "\u{1F7E1}",
13386
+ finished: "\u2705",
13387
+ error: "\u274C",
13388
+ cancelled: "\u26D4"
13389
+ };
13390
+ const statusOrder = { active: 0, initializing: 1, error: 2, finished: 3, cancelled: 4 };
13391
+ records.sort((a, b) => (statusOrder[a.status] ?? 5) - (statusOrder[b.status] ?? 5));
13392
+ const MAX_DISPLAY = 30;
13393
+ const displayed = records.slice(0, MAX_DISPLAY);
13394
+ const lines = displayed.map((r) => {
13395
+ const emoji = statusEmoji[r.status] || "\u26AA";
13396
+ const name = r.name?.trim();
13397
+ const label = name ? escapeHtml(name) : `<i>${escapeHtml(r.agentName)} session</i>`;
13398
+ return `${emoji} ${label} <code>[${r.status}]</code>`;
13399
+ });
13400
+ const header2 = `<b>Sessions: ${records.length}</b>` + (headlessCount > 0 ? ` (${headlessCount} headless hidden)` : "");
13401
+ const truncated = records.length > MAX_DISPLAY ? `
13402
+
13403
+ <i>...and ${records.length - MAX_DISPLAY} more</i>` : "";
13404
+ const finishedCount = allRecords.filter((r) => r.status === "finished").length;
13405
+ const errorCount = allRecords.filter((r) => r.status === "error" || r.status === "cancelled").length;
13406
+ const keyboard = new InlineKeyboard3();
13407
+ if (finishedCount > 0) {
13408
+ keyboard.text(`Cleanup finished (${finishedCount})`, "m:cleanup:finished").row();
13409
+ }
13410
+ if (errorCount > 0) {
13411
+ keyboard.text(`Cleanup errors (${errorCount})`, "m:cleanup:errors").row();
13412
+ }
13413
+ if (finishedCount + errorCount > 0) {
13414
+ keyboard.text(`Cleanup all non-active (${finishedCount + errorCount})`, "m:cleanup:all").row();
13415
+ }
13416
+ keyboard.text(`\u26A0\uFE0F Cleanup ALL (${allRecords.length})`, "m:cleanup:everything").row();
13417
+ keyboard.text("Refresh", "m:topics");
13418
+ await ctx.reply(
13419
+ `${header2}
13420
+
13421
+ ${lines.join("\n")}${truncated}`,
13422
+ { parse_mode: "HTML", reply_markup: keyboard }
13423
+ );
13424
+ } catch (err) {
13425
+ log23.error({ err }, "handleTopics error");
13426
+ await ctx.reply("\u274C Failed to list sessions.", { parse_mode: "HTML" }).catch(() => {
13427
+ });
13428
+ }
13429
+ }
12976
13430
  async function handleCleanup(ctx, core, chatId, statuses) {
12977
13431
  const allRecords = core.sessionManager.listRecords();
12978
13432
  const cleanable = allRecords.filter((r) => statuses.includes(r.status));
@@ -13031,7 +13485,7 @@ async function handleCleanupEverything(ctx, core, chatId, systemTopicIds) {
13031
13485
  const activeWarning = activeCount > 0 ? `
13032
13486
 
13033
13487
  \u26A0\uFE0F <b>${activeCount} active session(s) will be cancelled and their agents stopped!</b>` : "";
13034
- const keyboard = new InlineKeyboard2().text("Yes, delete all", "m:cleanup:everything:confirm").text("Cancel", "m:topics");
13488
+ const keyboard = new InlineKeyboard3().text("Yes, delete all", "m:cleanup:everything:confirm").text("Cancel", "m:topics");
13035
13489
  await ctx.reply(
13036
13490
  `<b>Delete ${cleanable.length} topics?</b>
13037
13491
 
@@ -13114,6 +13568,13 @@ function setupSessionCallbacks(bot, core, chatId, systemTopicIds) {
13114
13568
  break;
13115
13569
  }
13116
13570
  });
13571
+ bot.callbackQuery("m:topics", async (ctx) => {
13572
+ try {
13573
+ await ctx.answerCallbackQuery();
13574
+ } catch {
13575
+ }
13576
+ await handleTopics(ctx, core);
13577
+ });
13117
13578
  }
13118
13579
  async function handleArchiveConfirm(ctx, core, chatId) {
13119
13580
  const data = ctx.callbackQuery?.data;
@@ -13143,8 +13604,74 @@ async function handleArchiveConfirm(ctx, core, chatId) {
13143
13604
  }
13144
13605
 
13145
13606
  // src/plugins/telegram/commands/agents.ts
13146
- import { InlineKeyboard as InlineKeyboard3 } from "grammy";
13607
+ import { InlineKeyboard as InlineKeyboard4 } from "grammy";
13147
13608
  var AGENTS_PER_PAGE = 6;
13609
+ async function handleAgents(ctx, core, page = 0) {
13610
+ const catalog = core.agentCatalog;
13611
+ const items = catalog.getAvailable();
13612
+ const installed = items.filter((i) => i.installed);
13613
+ const available = items.filter((i) => !i.installed);
13614
+ let text3 = "<b>\u{1F916} Agents</b>\n\n";
13615
+ if (installed.length > 0) {
13616
+ text3 += "<b>Installed:</b>\n";
13617
+ for (const item of installed) {
13618
+ text3 += `\u2705 <b>${escapeHtml(item.name)}</b>`;
13619
+ if (item.description) {
13620
+ text3 += ` \u2014 <i>${escapeHtml(truncate(item.description, 50))}</i>`;
13621
+ }
13622
+ text3 += "\n";
13623
+ }
13624
+ text3 += "\n";
13625
+ }
13626
+ if (available.length > 0) {
13627
+ const totalPages = Math.ceil(available.length / AGENTS_PER_PAGE);
13628
+ const safePage = Math.max(0, Math.min(page, totalPages - 1));
13629
+ const pageItems = available.slice(safePage * AGENTS_PER_PAGE, (safePage + 1) * AGENTS_PER_PAGE);
13630
+ text3 += `<b>Available to install:</b>`;
13631
+ if (totalPages > 1) {
13632
+ text3 += ` (${safePage + 1}/${totalPages})`;
13633
+ }
13634
+ text3 += "\n";
13635
+ for (const item of pageItems) {
13636
+ if (item.available) {
13637
+ text3 += `\u2B07\uFE0F <b>${escapeHtml(item.name)}</b>`;
13638
+ } else {
13639
+ const deps = item.missingDeps?.join(", ") ?? "requirements not met";
13640
+ text3 += `\u26A0\uFE0F <b>${escapeHtml(item.name)}</b> <i>(needs: ${escapeHtml(deps)})</i>`;
13641
+ }
13642
+ if (item.description) {
13643
+ text3 += `
13644
+ <i>${escapeHtml(truncate(item.description, 60))}</i>`;
13645
+ }
13646
+ text3 += "\n";
13647
+ }
13648
+ const keyboard = new InlineKeyboard4();
13649
+ const installable = pageItems.filter((i) => i.available);
13650
+ for (let i = 0; i < installable.length; i += 2) {
13651
+ const row = installable.slice(i, i + 2);
13652
+ for (const item of row) {
13653
+ keyboard.text(`\u2B07\uFE0F ${item.name}`, `ag:install:${item.key}`);
13654
+ }
13655
+ keyboard.row();
13656
+ }
13657
+ if (totalPages > 1) {
13658
+ if (safePage > 0) {
13659
+ keyboard.text("\u25C0\uFE0F Prev", `ag:page:${safePage - 1}`);
13660
+ }
13661
+ if (safePage < totalPages - 1) {
13662
+ keyboard.text("Next \u25B6\uFE0F", `ag:page:${safePage + 1}`);
13663
+ }
13664
+ keyboard.row();
13665
+ }
13666
+ if (available.some((i) => !i.available)) {
13667
+ text3 += "\n\u{1F4A1} <i>Agents marked \u26A0\uFE0F need additional setup. Use</i> <code>openacp agents info &lt;name&gt;</code> <i>for details.</i>\n";
13668
+ }
13669
+ await ctx.reply(text3, { parse_mode: "HTML", reply_markup: keyboard });
13670
+ } else {
13671
+ text3 += "<i>All agents are already installed!</i>";
13672
+ await ctx.reply(text3, { parse_mode: "HTML" });
13673
+ }
13674
+ }
13148
13675
  async function handleAgentCallback(ctx, core) {
13149
13676
  const data = ctx.callbackQuery?.data ?? "";
13150
13677
  await ctx.answerCallbackQuery();
@@ -13193,7 +13720,7 @@ async function handleAgentCallback(ctx, core) {
13193
13720
  }
13194
13721
  text3 += "\n";
13195
13722
  }
13196
- const keyboard = new InlineKeyboard3();
13723
+ const keyboard = new InlineKeyboard4();
13197
13724
  const installable = pageItems.filter((i) => i.available);
13198
13725
  for (let i = 0; i < installable.length; i += 2) {
13199
13726
  const row = installable.slice(i, i + 2);
@@ -13248,7 +13775,7 @@ Downloading... ${bar} ${percent}%`, { parse_mode: "HTML" });
13248
13775
  },
13249
13776
  async onSuccess(name) {
13250
13777
  try {
13251
- const keyboard = new InlineKeyboard3().text(`\u{1F680} Start session with ${name}`, `na:${nameOrId}`);
13778
+ const keyboard = new InlineKeyboard4().text(`\u{1F680} Start session with ${name}`, `na:${nameOrId}`);
13252
13779
  await ctx.api.editMessageText(msg.chat.id, msg.message_id, `\u2705 <b>${escapeHtml(name)}</b> installed!`, { parse_mode: "HTML", reply_markup: keyboard });
13253
13780
  } catch {
13254
13781
  }
@@ -13333,6 +13860,7 @@ function buildProgressBar(percent) {
13333
13860
  }
13334
13861
 
13335
13862
  // src/plugins/telegram/commands/resume.ts
13863
+ init_topics();
13336
13864
  init_log();
13337
13865
  var log24 = createChildLogger({ module: "telegram-cmd-resume" });
13338
13866
  function setupResumeCallbacks(_bot, _core, _chatId, _onControlMessage) {
@@ -13341,12 +13869,12 @@ function setupResumeCallbacks(_bot, _core, _chatId, _onControlMessage) {
13341
13869
  // src/plugins/telegram/commands/settings.ts
13342
13870
  init_config_registry();
13343
13871
  init_log();
13344
- import { InlineKeyboard as InlineKeyboard5 } from "grammy";
13872
+ import { InlineKeyboard as InlineKeyboard6 } from "grammy";
13345
13873
  var log25 = createChildLogger({ module: "telegram-settings" });
13346
13874
  function buildSettingsKeyboard(core) {
13347
13875
  const config = core.configManager.get();
13348
13876
  const fields = getSafeFields();
13349
- const kb = new InlineKeyboard5();
13877
+ const kb = new InlineKeyboard6();
13350
13878
  for (const field of fields) {
13351
13879
  const value = getConfigValue(config, field.path);
13352
13880
  const label = formatFieldLabel(field, value);
@@ -13378,6 +13906,14 @@ function formatFieldLabel(field, value) {
13378
13906
  const displayValue = value === null || value === void 0 ? "Not set" : String(value);
13379
13907
  return `${icon} ${field.displayName}: ${displayValue}`;
13380
13908
  }
13909
+ async function handleSettings(ctx, core) {
13910
+ const kb = buildSettingsKeyboard(core);
13911
+ await ctx.reply(`<b>\u2699\uFE0F Settings</b>
13912
+ Tap to change:`, {
13913
+ parse_mode: "HTML",
13914
+ reply_markup: kb
13915
+ });
13916
+ }
13381
13917
  function setupSettingsCallbacks(bot, core, getAssistantSession) {
13382
13918
  bot.callbackQuery(/^s:toggle:/, async (ctx) => {
13383
13919
  const fieldPath = ctx.callbackQuery.data.replace("s:toggle:", "");
@@ -13411,7 +13947,7 @@ function setupSettingsCallbacks(bot, core, getAssistantSession) {
13411
13947
  if (!fieldDef) return;
13412
13948
  const options = resolveOptions(fieldDef, config) ?? [];
13413
13949
  const currentValue = getConfigValue(config, fieldPath);
13414
- const kb = new InlineKeyboard5();
13950
+ const kb = new InlineKeyboard6();
13415
13951
  for (const opt of options) {
13416
13952
  const marker = opt === String(currentValue) ? " \u2713" : "";
13417
13953
  kb.text(`${opt}${marker}`, `s:pick:${fieldPath}:${opt}`).row();
@@ -13505,11 +14041,12 @@ Tap to change:`, {
13505
14041
  } catch {
13506
14042
  }
13507
14043
  const { buildMenuKeyboard: buildMenuKeyboard2 } = await Promise.resolve().then(() => (init_menu(), menu_exports));
14044
+ const menuRegistry = core.lifecycleManager?.serviceRegistry?.get("menu-registry");
13508
14045
  try {
13509
14046
  await ctx.editMessageText(`<b>OpenACP Menu</b>
13510
14047
  Choose an action:`, {
13511
14048
  parse_mode: "HTML",
13512
- reply_markup: buildMenuKeyboard2()
14049
+ reply_markup: buildMenuKeyboard2(menuRegistry)
13513
14050
  });
13514
14051
  } catch {
13515
14052
  }
@@ -13542,7 +14079,7 @@ function buildNestedUpdate(dotPath, value) {
13542
14079
  }
13543
14080
 
13544
14081
  // src/plugins/telegram/commands/doctor.ts
13545
- import { InlineKeyboard as InlineKeyboard6 } from "grammy";
14082
+ import { InlineKeyboard as InlineKeyboard7 } from "grammy";
13546
14083
  init_log();
13547
14084
  var log26 = createChildLogger({ module: "telegram-cmd-doctor" });
13548
14085
  var pendingFixesStore = /* @__PURE__ */ new Map();
@@ -13561,7 +14098,7 @@ function renderReport(report) {
13561
14098
  lines.push(`<b>Result:</b> ${passed} passed, ${warnings} warnings, ${failed} failed${fixedStr}`);
13562
14099
  let keyboard;
13563
14100
  if (report.pendingFixes.length > 0) {
13564
- keyboard = new InlineKeyboard6();
14101
+ keyboard = new InlineKeyboard7();
13565
14102
  for (let i = 0; i < report.pendingFixes.length; i++) {
13566
14103
  const label = `\u{1F527} Fix: ${report.pendingFixes[i].message.slice(0, 30)}`;
13567
14104
  keyboard.text(label, `m:doctor:fix:${i}`).row();
@@ -13649,7 +14186,7 @@ function setupDoctorCallbacks(bot) {
13649
14186
  }
13650
14187
 
13651
14188
  // src/plugins/telegram/commands/tunnel.ts
13652
- import { InlineKeyboard as InlineKeyboard7 } from "grammy";
14189
+ import { InlineKeyboard as InlineKeyboard8 } from "grammy";
13653
14190
  init_log();
13654
14191
  var log27 = createChildLogger({ module: "telegram-cmd-tunnel" });
13655
14192
  function setupTunnelCallbacks(bot, core) {
@@ -13678,7 +14215,7 @@ function setupTunnelCallbacks(bot, core) {
13678
14215
  if (remaining.length === 0) {
13679
14216
  await ctx.editMessageText("\u{1F50C} All tunnels stopped.", { parse_mode: "HTML" });
13680
14217
  } else {
13681
- const kb = new InlineKeyboard7();
14218
+ const kb = new InlineKeyboard8();
13682
14219
  for (const e of remaining) {
13683
14220
  kb.text(`\u{1F50C} Stop ${e.port}${e.label ? ` (${e.label})` : ""}`, `tn:stop:${e.port}`).row();
13684
14221
  }
@@ -13706,7 +14243,7 @@ function setupTunnelCallbacks(bot, core) {
13706
14243
  }
13707
14244
 
13708
14245
  // src/plugins/telegram/commands/switch.ts
13709
- import { InlineKeyboard as InlineKeyboard8 } from "grammy";
14246
+ import { InlineKeyboard as InlineKeyboard9 } from "grammy";
13710
14247
  init_log();
13711
14248
  var log28 = createChildLogger({ module: "telegram-cmd-switch" });
13712
14249
  async function executeSwitchAgent(ctx, core, sessionId, agentName) {
@@ -13735,7 +14272,7 @@ function setupSwitchCallbacks(bot, core) {
13735
14272
  return;
13736
14273
  }
13737
14274
  if (session.promptRunning) {
13738
- const keyboard = new InlineKeyboard8();
14275
+ const keyboard = new InlineKeyboard9();
13739
14276
  keyboard.text("Yes, switch now", `swc:${agentName}`).text("Cancel", "swc:cancel");
13740
14277
  await ctx.reply(
13741
14278
  `A prompt is currently running. Switching will interrupt it.
@@ -13766,14 +14303,29 @@ Switch to <b>${escapeHtml(agentName)}</b> anyway?`,
13766
14303
  });
13767
14304
  }
13768
14305
 
14306
+ // src/plugins/telegram/commands/telegram-overrides.ts
14307
+ init_menu();
14308
+ var TELEGRAM_OVERRIDES = {
14309
+ agents: (ctx, core) => handleAgents(ctx, core),
14310
+ sessions: (ctx, core) => handleTopics(ctx, core),
14311
+ doctor: (ctx) => handleDoctor(ctx),
14312
+ update: (ctx, core) => handleUpdate(ctx, core),
14313
+ restart: (ctx, core) => handleRestart(ctx, core),
14314
+ help: (ctx) => handleHelp(ctx),
14315
+ menu: (ctx, core) => {
14316
+ const menuRegistry = core.lifecycleManager?.serviceRegistry?.get("menu-registry");
14317
+ return handleMenu(ctx, menuRegistry);
14318
+ }
14319
+ };
14320
+
13769
14321
  // src/plugins/telegram/commands/index.ts
13770
14322
  init_menu();
13771
14323
  init_menu();
13772
14324
 
13773
14325
  // src/plugins/telegram/commands/integrate.ts
13774
- import { InlineKeyboard as InlineKeyboard9 } from "grammy";
14326
+ import { InlineKeyboard as InlineKeyboard10 } from "grammy";
13775
14327
  function buildAgentItemsKeyboard(agentName, items) {
13776
- const keyboard = new InlineKeyboard9();
14328
+ const keyboard = new InlineKeyboard10();
13777
14329
  for (const item of items) {
13778
14330
  const installed = item.isInstalled();
13779
14331
  keyboard.text(
@@ -13794,7 +14346,7 @@ function setupIntegrateCallbacks(bot, core) {
13794
14346
  if (data === "i:back") {
13795
14347
  const { listIntegrations: listIntegrations2 } = await Promise.resolve().then(() => (init_integrate(), integrate_exports));
13796
14348
  const agents = listIntegrations2();
13797
- const keyboard2 = new InlineKeyboard9();
14349
+ const keyboard2 = new InlineKeyboard10();
13798
14350
  for (const agent of agents) {
13799
14351
  keyboard2.text(`\u{1F916} ${agent}`, `i:agent:${agent}`).row();
13800
14352
  }
@@ -13894,6 +14446,7 @@ function setupAllCallbacks(bot, core, chatId, systemTopicIds, getAssistantSessio
13894
14446
  core.configManager.get().workspace.baseDir
13895
14447
  );
13896
14448
  });
14449
+ setupNewSessionCallbacks(bot, core, chatId, getAssistantSession);
13897
14450
  bot.callbackQuery(/^ar:/, (ctx) => handleArchiveConfirm(ctx, core, chatId));
13898
14451
  bot.callbackQuery(/^m:/, async (ctx) => {
13899
14452
  const itemId = ctx.callbackQuery.data.replace("m:", "");
@@ -13909,6 +14462,17 @@ function setupAllCallbacks(bot, core, chatId, systemTopicIds, getAssistantSessio
13909
14462
  const registry = core.lifecycleManager?.serviceRegistry?.get("command-registry");
13910
14463
  switch (item.action.type) {
13911
14464
  case "command": {
14465
+ const cmdName = item.action.command.replace(/^\//, "").split(" ")[0];
14466
+ const telegramOverride = TELEGRAM_OVERRIDES[cmdName];
14467
+ if (telegramOverride) {
14468
+ try {
14469
+ await telegramOverride(ctx, core);
14470
+ } catch (err) {
14471
+ await ctx.reply(`\u26A0\uFE0F Command failed: ${String(err)}`).catch(() => {
14472
+ });
14473
+ }
14474
+ break;
14475
+ }
13912
14476
  if (!registry) return;
13913
14477
  const response = await registry.execute(item.action.command, {
13914
14478
  raw: "",
@@ -13931,16 +14495,16 @@ function setupAllCallbacks(bot, core, chatId, systemTopicIds, getAssistantSessio
13931
14495
  ${lines}`, { parse_mode: "HTML" }).catch(() => {
13932
14496
  });
13933
14497
  } else if (response.type === "menu") {
13934
- const { InlineKeyboard: InlineKeyboard11 } = await import("grammy");
13935
- const kb = new InlineKeyboard11();
14498
+ const { InlineKeyboard: InlineKeyboard12 } = await import("grammy");
14499
+ const kb = new InlineKeyboard12();
13936
14500
  for (const opt of response.options) {
13937
14501
  kb.text(opt.label, `c/${opt.command}`).row();
13938
14502
  }
13939
14503
  await ctx.reply(response.title, { parse_mode: "HTML", reply_markup: kb }).catch(() => {
13940
14504
  });
13941
14505
  } else if (response.type === "confirm") {
13942
- const { InlineKeyboard: InlineKeyboard11 } = await import("grammy");
13943
- const kb = new InlineKeyboard11().text("\u2705 Yes", `c/${response.onYes}`).text("\u274C No", `c/${response.onNo}`);
14506
+ const { InlineKeyboard: InlineKeyboard12 } = await import("grammy");
14507
+ const kb = new InlineKeyboard12().text("\u2705 Yes", `c/${response.onYes}`).text("\u274C No", `c/${response.onNo}`);
13944
14508
  await ctx.reply(response.question, { reply_markup: kb }).catch(() => {
13945
14509
  });
13946
14510
  }
@@ -13948,6 +14512,10 @@ ${lines}`, { parse_mode: "HTML" }).catch(() => {
13948
14512
  break;
13949
14513
  }
13950
14514
  case "delegate": {
14515
+ if (itemId === "core:new") {
14516
+ await showAgentPicker(ctx, core, chatId);
14517
+ break;
14518
+ }
13951
14519
  const assistant = core.assistantManager?.get("telegram");
13952
14520
  if (assistant) {
13953
14521
  if (topicId && systemTopicIds && topicId !== systemTopicIds.assistantTopicId) {
@@ -13963,8 +14531,13 @@ ${lines}`, { parse_mode: "HTML" }).catch(() => {
13963
14531
  }
13964
14532
  break;
13965
14533
  }
13966
- case "callback":
14534
+ case "callback": {
14535
+ const cbData = item.action.callbackData;
14536
+ if (cbData === "s:settings") {
14537
+ await handleSettings(ctx, core);
14538
+ }
13967
14539
  break;
14540
+ }
13968
14541
  }
13969
14542
  });
13970
14543
  }
@@ -13999,8 +14572,9 @@ var STATIC_COMMANDS = [
13999
14572
  ];
14000
14573
 
14001
14574
  // src/plugins/telegram/permissions.ts
14002
- import { InlineKeyboard as InlineKeyboard10 } from "grammy";
14575
+ import { InlineKeyboard as InlineKeyboard11 } from "grammy";
14003
14576
  import { nanoid as nanoid2 } from "nanoid";
14577
+ init_topics();
14004
14578
  init_log();
14005
14579
  var log29 = createChildLogger({ module: "telegram-permissions" });
14006
14580
  var PermissionHandler = class {
@@ -14019,7 +14593,7 @@ var PermissionHandler = class {
14019
14593
  requestId: request.id,
14020
14594
  options: request.options.map((o) => ({ id: o.id, isAllow: o.isAllow }))
14021
14595
  });
14022
- const keyboard = new InlineKeyboard10();
14596
+ const keyboard = new InlineKeyboard11();
14023
14597
  for (const option of request.options) {
14024
14598
  const emoji = option.isAllow ? "\u2705" : "\u274C";
14025
14599
  keyboard.text(`${emoji} ${option.label}`, `p:${callbackKey}:${option.id}`);
@@ -15078,6 +15652,15 @@ var TelegramAdapter = class extends MessagingAdapter {
15078
15652
  const commandName = atIdx === -1 ? rawCommand : rawCommand.slice(0, atIdx);
15079
15653
  const def = registry.get(commandName);
15080
15654
  if (!def) return next();
15655
+ const telegramOverride = TELEGRAM_OVERRIDES[commandName];
15656
+ if (telegramOverride) {
15657
+ try {
15658
+ await telegramOverride(ctx, this.core);
15659
+ } catch (err) {
15660
+ await ctx.reply(`\u26A0\uFE0F Command failed: ${String(err)}`);
15661
+ }
15662
+ return;
15663
+ }
15081
15664
  const chatId = ctx.chat.id;
15082
15665
  const topicId = ctx.message.message_thread_id;
15083
15666
  try {
@@ -15183,7 +15766,16 @@ var TelegramAdapter = class extends MessagingAdapter {
15183
15766
  if (!assistant) return void 0;
15184
15767
  return {
15185
15768
  topicId: this.assistantTopicId,
15186
- enqueuePrompt: (p) => assistant.enqueuePrompt(p)
15769
+ enqueuePrompt: (p) => {
15770
+ const pending = this.core.assistantManager?.consumePendingSystemPrompt("telegram");
15771
+ const text3 = pending ? `${pending}
15772
+
15773
+ ---
15774
+
15775
+ User message:
15776
+ ${p}` : p;
15777
+ return assistant.enqueuePrompt(text3);
15778
+ }
15187
15779
  };
15188
15780
  },
15189
15781
  (sessionId, msgId) => {
@@ -15244,7 +15836,8 @@ var TelegramAdapter = class extends MessagingAdapter {
15244
15836
  errorCount: allRecords.filter((r) => r.status === "error").length,
15245
15837
  totalCount: allRecords.length,
15246
15838
  agents: agents.map((a) => a.name),
15247
- defaultAgent: config.defaultAgent
15839
+ defaultAgent: config.defaultAgent,
15840
+ workspace: this.core.configManager.resolveWorkspace()
15248
15841
  });
15249
15842
  await this.bot.api.sendMessage(this.telegramConfig.chatId, welcomeText, {
15250
15843
  message_thread_id: this.assistantTopicId,