@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.
- package/README.md +25 -25
- package/dist/chunk-5KBEVENA.js +412 -0
- package/dist/chunk-5KBEVENA.js.map +1 -0
- package/dist/{chunk-I6KXISAR.js → chunk-TZGP3JSE.js} +377 -40
- package/dist/chunk-TZGP3JSE.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +124 -45
- package/dist/index.js +12 -2
- package/dist/{main-L5JD5STD.js → main-ZQ5RNL3X.js} +28 -18
- package/dist/main-ZQ5RNL3X.js.map +1 -0
- package/dist/{setup-2UVU4YYA.js → setup-4EBTX2NJ.js} +3 -32
- package/dist/{setup-2UVU4YYA.js.map → setup-4EBTX2NJ.js.map} +1 -1
- package/package.json +4 -1
- package/dist/chunk-I6KXISAR.js.map +0 -1
- package/dist/chunk-KADEDKIM.js +0 -220
- package/dist/chunk-KADEDKIM.js.map +0 -1
- package/dist/main-L5JD5STD.js.map +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
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(
|
|
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
|
|
123
|
-
|
|
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
|
|
171
|
+
log.debug({ sessionId: instance.sessionId }, "ACP connection closed");
|
|
153
172
|
});
|
|
154
|
-
log.info(
|
|
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(
|
|
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(
|
|
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))
|
|
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
|
-
|
|
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
|
|
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 =
|
|
1134
|
-
this.pending.set(callbackKey, {
|
|
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
|
-
|
|
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 ?? [
|
|
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: () =>
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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) =>
|
|
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(
|
|
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(
|
|
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
|
-
{
|
|
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, {
|
|
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 = {
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
1818
|
+
//# sourceMappingURL=chunk-TZGP3JSE.js.map
|