@openacp/cli 0.2.5 → 0.2.13

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.
@@ -0,0 +1,1820 @@
1
+ import {
2
+ createChildLogger,
3
+ createSessionLogger
4
+ } from "./chunk-5KBEVENA.js";
5
+
6
+ // src/core/streams.ts
7
+ function nodeToWebWritable(nodeStream) {
8
+ return new WritableStream({
9
+ write(chunk) {
10
+ return new Promise((resolve, reject) => {
11
+ nodeStream.write(Buffer.from(chunk), (err) => {
12
+ if (err) reject(err);
13
+ else resolve();
14
+ });
15
+ });
16
+ }
17
+ });
18
+ }
19
+ function nodeToWebReadable(nodeStream) {
20
+ return new ReadableStream({
21
+ start(controller) {
22
+ nodeStream.on("data", (chunk) => {
23
+ controller.enqueue(new Uint8Array(chunk));
24
+ });
25
+ nodeStream.on("end", () => controller.close());
26
+ nodeStream.on("error", (err) => controller.error(err));
27
+ }
28
+ });
29
+ }
30
+
31
+ // src/core/stderr-capture.ts
32
+ var StderrCapture = class {
33
+ constructor(maxLines = 50) {
34
+ this.maxLines = maxLines;
35
+ }
36
+ lines = [];
37
+ append(chunk) {
38
+ this.lines.push(...chunk.split("\n").filter(Boolean));
39
+ if (this.lines.length > this.maxLines) {
40
+ this.lines = this.lines.slice(-this.maxLines);
41
+ }
42
+ }
43
+ getLastLines() {
44
+ return this.lines.join("\n");
45
+ }
46
+ };
47
+
48
+ // src/core/agent-instance.ts
49
+ import { spawn, execSync } from "child_process";
50
+ import { Transform } from "stream";
51
+ import fs from "fs";
52
+ import path from "path";
53
+ import { randomUUID } from "crypto";
54
+ import { ClientSideConnection, ndJsonStream } from "@agentclientprotocol/sdk";
55
+ var log = createChildLogger({ module: "agent-instance" });
56
+ function resolveAgentCommand(cmd) {
57
+ const packageDirs = [
58
+ path.resolve(process.cwd(), "node_modules", "@zed-industries", cmd, "dist", "index.js"),
59
+ path.resolve(process.cwd(), "node_modules", cmd, "dist", "index.js")
60
+ ];
61
+ for (const jsPath of packageDirs) {
62
+ if (fs.existsSync(jsPath)) {
63
+ return { command: process.execPath, args: [jsPath] };
64
+ }
65
+ }
66
+ const localBin = path.resolve(process.cwd(), "node_modules", ".bin", cmd);
67
+ if (fs.existsSync(localBin)) {
68
+ const content = fs.readFileSync(localBin, "utf-8");
69
+ if (content.startsWith("#!/usr/bin/env node")) {
70
+ return { command: process.execPath, args: [localBin] };
71
+ }
72
+ const match = content.match(/"([^"]+\.js)"/);
73
+ if (match) {
74
+ const target = path.resolve(path.dirname(localBin), match[1]);
75
+ if (fs.existsSync(target)) {
76
+ return { command: process.execPath, args: [target] };
77
+ }
78
+ }
79
+ }
80
+ try {
81
+ const fullPath = execSync(`which ${cmd}`, { encoding: "utf-8" }).trim();
82
+ if (fullPath) {
83
+ const content = fs.readFileSync(fullPath, "utf-8");
84
+ if (content.startsWith("#!/usr/bin/env node")) {
85
+ return { command: process.execPath, args: [fullPath] };
86
+ }
87
+ }
88
+ } catch {
89
+ }
90
+ return { command: cmd, args: [] };
91
+ }
92
+ var AgentInstance = class _AgentInstance {
93
+ connection;
94
+ child;
95
+ stderrCapture;
96
+ terminals = /* @__PURE__ */ new Map();
97
+ sessionId;
98
+ agentName;
99
+ // Callbacks — set by core when wiring events
100
+ onSessionUpdate = () => {
101
+ };
102
+ onPermissionRequest = async () => "";
103
+ constructor(agentName) {
104
+ this.agentName = agentName;
105
+ }
106
+ static async spawn(agentDef, workingDirectory) {
107
+ const instance = new _AgentInstance(agentDef.name);
108
+ const resolved = resolveAgentCommand(agentDef.command);
109
+ log.debug({ agentName: agentDef.name, command: resolved.command, args: resolved.args }, "Spawning agent");
110
+ const spawnStart = Date.now();
111
+ instance.child = spawn(resolved.command, [...resolved.args, ...agentDef.args], {
112
+ stdio: ["pipe", "pipe", "pipe"],
113
+ cwd: workingDirectory,
114
+ env: { ...process.env, ...agentDef.env }
115
+ });
116
+ await new Promise((resolve, reject) => {
117
+ instance.child.on("error", (err) => {
118
+ reject(new Error(`Failed to spawn agent "${agentDef.name}": ${err.message}. Is "${agentDef.command}" installed?`));
119
+ });
120
+ instance.child.on("spawn", () => resolve());
121
+ });
122
+ instance.stderrCapture = new StderrCapture(50);
123
+ instance.child.stderr.on("data", (chunk) => {
124
+ instance.stderrCapture.append(chunk.toString());
125
+ });
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);
142
+ const stream = ndJsonStream(toAgent, fromAgent);
143
+ instance.connection = new ClientSideConnection(
144
+ (_agent) => instance.createClient(_agent),
145
+ stream
146
+ );
147
+ await instance.connection.initialize({
148
+ protocolVersion: 1,
149
+ clientCapabilities: {
150
+ fs: { readTextFile: true, writeTextFile: true },
151
+ terminal: true
152
+ }
153
+ });
154
+ const response = await instance.connection.newSession({
155
+ cwd: workingDirectory,
156
+ mcpServers: []
157
+ });
158
+ instance.sessionId = response.sessionId;
159
+ instance.child.on("exit", (code, signal) => {
160
+ log.info({ sessionId: instance.sessionId, exitCode: code, signal }, "Agent process exited");
161
+ if (code !== 0 && code !== null) {
162
+ const stderr = instance.stderrCapture.getLastLines();
163
+ instance.onSessionUpdate({
164
+ type: "error",
165
+ message: `Agent crashed (exit code ${code})
166
+ ${stderr}`
167
+ });
168
+ }
169
+ });
170
+ instance.connection.closed.then(() => {
171
+ log.debug({ sessionId: instance.sessionId }, "ACP connection closed");
172
+ });
173
+ log.info({ sessionId: response.sessionId, durationMs: Date.now() - spawnStart }, "Agent spawn complete");
174
+ return instance;
175
+ }
176
+ // createClient — implemented in Task 6b
177
+ createClient(_agent) {
178
+ const self = this;
179
+ const MAX_OUTPUT_BYTES = 1024 * 1024;
180
+ return {
181
+ // ── Session updates ──────────────────────────────────────────────────
182
+ async sessionUpdate(params) {
183
+ const update = params.update;
184
+ let event = null;
185
+ switch (update.sessionUpdate) {
186
+ case "agent_message_chunk":
187
+ if (update.content.type === "text") {
188
+ event = { type: "text", content: update.content.text };
189
+ }
190
+ break;
191
+ case "agent_thought_chunk":
192
+ if (update.content.type === "text") {
193
+ event = { type: "thought", content: update.content.text };
194
+ }
195
+ break;
196
+ case "tool_call":
197
+ event = {
198
+ type: "tool_call",
199
+ id: update.toolCallId,
200
+ name: update.title,
201
+ kind: update.kind ?? void 0,
202
+ status: update.status ?? "pending",
203
+ content: update.content ?? void 0
204
+ };
205
+ break;
206
+ case "tool_call_update":
207
+ event = {
208
+ type: "tool_update",
209
+ id: update.toolCallId,
210
+ status: update.status ?? "pending",
211
+ content: update.content ?? void 0
212
+ };
213
+ break;
214
+ case "plan":
215
+ event = { type: "plan", entries: update.entries };
216
+ break;
217
+ case "usage_update":
218
+ event = {
219
+ type: "usage",
220
+ tokensUsed: update.used,
221
+ contextSize: update.size,
222
+ cost: update.cost ?? void 0
223
+ };
224
+ break;
225
+ case "available_commands_update":
226
+ event = { type: "commands_update", commands: update.availableCommands };
227
+ break;
228
+ default:
229
+ return;
230
+ }
231
+ if (event !== null) {
232
+ self.onSessionUpdate(event);
233
+ }
234
+ },
235
+ // ── Permission requests ──────────────────────────────────────────────
236
+ async requestPermission(params) {
237
+ const permissionRequest = {
238
+ id: params.toolCall.toolCallId,
239
+ description: params.toolCall.title ?? params.toolCall.toolCallId,
240
+ options: params.options.map((opt) => ({
241
+ id: opt.optionId,
242
+ label: opt.name,
243
+ isAllow: opt.kind === "allow_once" || opt.kind === "allow_always"
244
+ }))
245
+ };
246
+ const selectedOptionId = await self.onPermissionRequest(permissionRequest);
247
+ return {
248
+ outcome: { outcome: "selected", optionId: selectedOptionId }
249
+ };
250
+ },
251
+ // ── File operations ──────────────────────────────────────────────────
252
+ async readTextFile(params) {
253
+ const content = await fs.promises.readFile(params.path, "utf-8");
254
+ return { content };
255
+ },
256
+ async writeTextFile(params) {
257
+ await fs.promises.mkdir(path.dirname(params.path), { recursive: true });
258
+ await fs.promises.writeFile(params.path, params.content, "utf-8");
259
+ return {};
260
+ },
261
+ // ── Terminal operations ──────────────────────────────────────────────
262
+ async createTerminal(params) {
263
+ const terminalId = randomUUID();
264
+ const args = params.args ?? [];
265
+ const env = {};
266
+ for (const ev of params.env ?? []) {
267
+ env[ev.name] = ev.value;
268
+ }
269
+ const childProcess = spawn(params.command, args, {
270
+ cwd: params.cwd ?? void 0,
271
+ env: { ...process.env, ...env },
272
+ shell: false
273
+ });
274
+ const state = {
275
+ process: childProcess,
276
+ output: "",
277
+ exitStatus: null
278
+ };
279
+ self.terminals.set(terminalId, state);
280
+ const outputByteLimit = params.outputByteLimit ?? MAX_OUTPUT_BYTES;
281
+ const appendOutput = (chunk) => {
282
+ state.output += chunk;
283
+ const bytes = Buffer.byteLength(state.output, "utf-8");
284
+ if (bytes > outputByteLimit) {
285
+ const excess = bytes - outputByteLimit;
286
+ state.output = state.output.slice(excess);
287
+ }
288
+ };
289
+ childProcess.stdout?.on("data", (chunk) => appendOutput(chunk.toString()));
290
+ childProcess.stderr?.on("data", (chunk) => appendOutput(chunk.toString()));
291
+ childProcess.on("exit", (code, signal) => {
292
+ state.exitStatus = { exitCode: code, signal };
293
+ });
294
+ return { terminalId };
295
+ },
296
+ async terminalOutput(params) {
297
+ const state = self.terminals.get(params.terminalId);
298
+ if (!state) {
299
+ throw new Error(`Terminal not found: ${params.terminalId}`);
300
+ }
301
+ return {
302
+ output: state.output,
303
+ truncated: false,
304
+ exitStatus: state.exitStatus ? { exitCode: state.exitStatus.exitCode, signal: state.exitStatus.signal } : void 0
305
+ };
306
+ },
307
+ async waitForTerminalExit(params) {
308
+ const state = self.terminals.get(params.terminalId);
309
+ if (!state) {
310
+ throw new Error(`Terminal not found: ${params.terminalId}`);
311
+ }
312
+ if (state.exitStatus !== null) {
313
+ return { exitCode: state.exitStatus.exitCode, signal: state.exitStatus.signal };
314
+ }
315
+ return new Promise((resolve) => {
316
+ state.process.on("exit", (code, signal) => {
317
+ resolve({ exitCode: code, signal });
318
+ });
319
+ });
320
+ },
321
+ async killTerminal(params) {
322
+ const state = self.terminals.get(params.terminalId);
323
+ if (!state) {
324
+ throw new Error(`Terminal not found: ${params.terminalId}`);
325
+ }
326
+ state.process.kill("SIGTERM");
327
+ return {};
328
+ },
329
+ async releaseTerminal(params) {
330
+ const state = self.terminals.get(params.terminalId);
331
+ if (!state) {
332
+ return;
333
+ }
334
+ state.process.kill("SIGKILL");
335
+ self.terminals.delete(params.terminalId);
336
+ }
337
+ };
338
+ }
339
+ async prompt(text) {
340
+ return this.connection.prompt({
341
+ sessionId: this.sessionId,
342
+ prompt: [{ type: "text", text }]
343
+ });
344
+ }
345
+ async cancel() {
346
+ await this.connection.cancel({ sessionId: this.sessionId });
347
+ }
348
+ async destroy() {
349
+ for (const [, t] of this.terminals) {
350
+ t.process.kill("SIGKILL");
351
+ }
352
+ this.terminals.clear();
353
+ this.child.kill("SIGTERM");
354
+ setTimeout(() => {
355
+ if (!this.child.killed) this.child.kill("SIGKILL");
356
+ }, 1e4);
357
+ }
358
+ };
359
+
360
+ // src/core/agent-manager.ts
361
+ var AgentManager = class {
362
+ constructor(config) {
363
+ this.config = config;
364
+ }
365
+ getAvailableAgents() {
366
+ return Object.entries(this.config.agents).map(([name, cfg]) => ({
367
+ name,
368
+ command: cfg.command,
369
+ args: cfg.args,
370
+ workingDirectory: cfg.workingDirectory,
371
+ env: cfg.env
372
+ }));
373
+ }
374
+ getAgent(name) {
375
+ const cfg = this.config.agents[name];
376
+ if (!cfg) return void 0;
377
+ return { name, ...cfg };
378
+ }
379
+ async spawn(agentName, workingDirectory) {
380
+ const agentDef = this.getAgent(agentName);
381
+ if (!agentDef) throw new Error(`Agent "${agentName}" not found in config`);
382
+ return AgentInstance.spawn(agentDef, workingDirectory);
383
+ }
384
+ };
385
+
386
+ // src/core/session.ts
387
+ import { nanoid } from "nanoid";
388
+ var moduleLog = createChildLogger({ module: "session" });
389
+ var Session = class {
390
+ id;
391
+ channelId;
392
+ threadId = "";
393
+ agentName;
394
+ workingDirectory;
395
+ agentInstance;
396
+ status = "initializing";
397
+ name;
398
+ promptQueue = [];
399
+ promptRunning = false;
400
+ createdAt = /* @__PURE__ */ new Date();
401
+ adapter;
402
+ // Set by wireSessionEvents for renaming
403
+ pendingPermission;
404
+ log;
405
+ constructor(opts) {
406
+ this.id = opts.id || nanoid(12);
407
+ this.channelId = opts.channelId;
408
+ this.agentName = opts.agentName;
409
+ this.workingDirectory = opts.workingDirectory;
410
+ this.agentInstance = opts.agentInstance;
411
+ this.log = createSessionLogger(this.id, moduleLog);
412
+ this.log.info({ agentName: this.agentName }, "Session created");
413
+ }
414
+ async enqueuePrompt(text) {
415
+ if (this.promptRunning) {
416
+ this.promptQueue.push(text);
417
+ this.log.debug({ queueDepth: this.promptQueue.length }, "Prompt queued");
418
+ return;
419
+ }
420
+ await this.runPrompt(text);
421
+ }
422
+ async runPrompt(text) {
423
+ this.promptRunning = true;
424
+ this.status = "active";
425
+ const promptStart = Date.now();
426
+ this.log.debug("Prompt execution started");
427
+ try {
428
+ await this.agentInstance.prompt(text);
429
+ this.log.info({ durationMs: Date.now() - promptStart }, "Prompt execution completed");
430
+ if (!this.name) {
431
+ await this.autoName();
432
+ }
433
+ } catch (err) {
434
+ this.status = "error";
435
+ this.log.error({ err }, "Prompt execution failed");
436
+ } finally {
437
+ this.promptRunning = false;
438
+ if (this.promptQueue.length > 0) {
439
+ const next = this.promptQueue.shift();
440
+ await this.runPrompt(next);
441
+ }
442
+ }
443
+ }
444
+ // NOTE: This injects a summary prompt into the agent's conversation history.
445
+ // Known Phase 1 limitation — the agent sees this prompt in its context.
446
+ async autoName() {
447
+ let title = "";
448
+ const prevHandler = this.agentInstance.onSessionUpdate;
449
+ this.agentInstance.onSessionUpdate = (event) => {
450
+ if (event.type === "text") title += event.content;
451
+ };
452
+ try {
453
+ await this.agentInstance.prompt(
454
+ "Summarize this conversation in max 5 words for a topic title. Reply ONLY with the title, nothing else."
455
+ );
456
+ this.name = title.trim().slice(0, 50);
457
+ this.log.info({ name: this.name }, "Session auto-named");
458
+ if (this.adapter && this.name) {
459
+ await this.adapter.renameSessionThread(this.id, this.name);
460
+ }
461
+ } catch {
462
+ this.name = `Session ${this.id.slice(0, 6)}`;
463
+ } finally {
464
+ this.agentInstance.onSessionUpdate = prevHandler;
465
+ }
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
+ }
488
+ async cancel() {
489
+ this.status = "cancelled";
490
+ this.log.info("Session cancelled");
491
+ await this.agentInstance.cancel();
492
+ }
493
+ async destroy() {
494
+ this.log.info("Session destroyed");
495
+ await this.agentInstance.destroy();
496
+ }
497
+ };
498
+
499
+ // src/core/session-manager.ts
500
+ var SessionManager = class {
501
+ sessions = /* @__PURE__ */ new Map();
502
+ async createSession(channelId, agentName, workingDirectory, agentManager) {
503
+ const agentInstance = await agentManager.spawn(agentName, workingDirectory);
504
+ const session = new Session({ channelId, agentName, workingDirectory, agentInstance });
505
+ this.sessions.set(session.id, session);
506
+ return session;
507
+ }
508
+ getSession(sessionId) {
509
+ return this.sessions.get(sessionId);
510
+ }
511
+ getSessionByThread(channelId, threadId) {
512
+ for (const session of this.sessions.values()) {
513
+ if (session.channelId === channelId && session.threadId === threadId) {
514
+ return session;
515
+ }
516
+ }
517
+ return void 0;
518
+ }
519
+ async cancelSession(sessionId) {
520
+ const session = this.sessions.get(sessionId);
521
+ if (session) await session.cancel();
522
+ }
523
+ listSessions(channelId) {
524
+ const all = Array.from(this.sessions.values());
525
+ if (channelId) return all.filter((s) => s.channelId === channelId);
526
+ return all;
527
+ }
528
+ async destroyAll() {
529
+ for (const session of this.sessions.values()) {
530
+ await session.destroy();
531
+ }
532
+ this.sessions.clear();
533
+ }
534
+ };
535
+
536
+ // src/core/notification.ts
537
+ var NotificationManager = class {
538
+ constructor(adapters) {
539
+ this.adapters = adapters;
540
+ }
541
+ async notify(channelId, notification) {
542
+ const adapter = this.adapters.get(channelId);
543
+ if (adapter) {
544
+ await adapter.sendNotification(notification);
545
+ }
546
+ }
547
+ async notifyAll(notification) {
548
+ for (const adapter of this.adapters.values()) {
549
+ await adapter.sendNotification(notification);
550
+ }
551
+ }
552
+ };
553
+
554
+ // src/core/core.ts
555
+ var log2 = createChildLogger({ module: "core" });
556
+ var OpenACPCore = class {
557
+ configManager;
558
+ agentManager;
559
+ sessionManager;
560
+ notificationManager;
561
+ adapters = /* @__PURE__ */ new Map();
562
+ constructor(configManager) {
563
+ this.configManager = configManager;
564
+ const config = configManager.get();
565
+ this.agentManager = new AgentManager(config);
566
+ this.sessionManager = new SessionManager();
567
+ this.notificationManager = new NotificationManager(this.adapters);
568
+ }
569
+ registerAdapter(name, adapter) {
570
+ this.adapters.set(name, adapter);
571
+ }
572
+ async start() {
573
+ for (const adapter of this.adapters.values()) {
574
+ await adapter.start();
575
+ }
576
+ }
577
+ async stop() {
578
+ try {
579
+ await this.notificationManager.notifyAll({
580
+ sessionId: "system",
581
+ type: "error",
582
+ summary: "OpenACP is shutting down"
583
+ });
584
+ } catch {
585
+ }
586
+ await this.sessionManager.destroyAll();
587
+ for (const adapter of this.adapters.values()) {
588
+ await adapter.stop();
589
+ }
590
+ }
591
+ // --- Message Routing ---
592
+ async handleMessage(message) {
593
+ const config = this.configManager.get();
594
+ log2.debug({ channelId: message.channelId, threadId: message.threadId, userId: message.userId }, "Incoming message");
595
+ if (config.security.allowedUserIds.length > 0) {
596
+ if (!config.security.allowedUserIds.includes(message.userId)) {
597
+ log2.warn({ userId: message.userId }, "Rejected message from unauthorized user");
598
+ return;
599
+ }
600
+ }
601
+ const activeSessions = this.sessionManager.listSessions().filter((s) => s.status === "active" || s.status === "initializing");
602
+ if (activeSessions.length >= config.security.maxConcurrentSessions) {
603
+ log2.warn({ userId: message.userId, currentCount: activeSessions.length, max: config.security.maxConcurrentSessions }, "Session limit reached");
604
+ const adapter = this.adapters.get(message.channelId);
605
+ if (adapter) {
606
+ await adapter.sendMessage("system", {
607
+ type: "error",
608
+ text: `Max concurrent sessions (${config.security.maxConcurrentSessions}) reached. Cancel a session first.`
609
+ });
610
+ }
611
+ return;
612
+ }
613
+ const session = this.sessionManager.getSessionByThread(message.channelId, message.threadId);
614
+ if (!session) return;
615
+ await session.enqueuePrompt(message.text);
616
+ }
617
+ async handleNewSession(channelId, agentName, workspacePath) {
618
+ const config = this.configManager.get();
619
+ const resolvedAgent = agentName || config.defaultAgent;
620
+ log2.info({ channelId, agentName: resolvedAgent }, "New session request");
621
+ const resolvedWorkspace = this.configManager.resolveWorkspace(
622
+ workspacePath || config.agents[resolvedAgent]?.workingDirectory
623
+ );
624
+ const session = await this.sessionManager.createSession(
625
+ channelId,
626
+ resolvedAgent,
627
+ resolvedWorkspace,
628
+ this.agentManager
629
+ );
630
+ const adapter = this.adapters.get(channelId);
631
+ if (adapter) {
632
+ this.wireSessionEvents(session, adapter);
633
+ }
634
+ return session;
635
+ }
636
+ async handleNewChat(channelId, currentThreadId) {
637
+ const currentSession = this.sessionManager.getSessionByThread(channelId, currentThreadId);
638
+ if (!currentSession) return null;
639
+ return this.handleNewSession(
640
+ channelId,
641
+ currentSession.agentName,
642
+ currentSession.workingDirectory
643
+ );
644
+ }
645
+ // --- Event Wiring ---
646
+ toOutgoingMessage(event) {
647
+ switch (event.type) {
648
+ case "text":
649
+ return { type: "text", text: event.content };
650
+ case "thought":
651
+ return { type: "thought", text: event.content };
652
+ case "tool_call":
653
+ return { type: "tool_call", text: event.name, metadata: { id: event.id, kind: event.kind, status: event.status, content: event.content, locations: event.locations } };
654
+ case "tool_update":
655
+ return { type: "tool_update", text: "", metadata: { id: event.id, status: event.status, content: event.content } };
656
+ case "plan":
657
+ return { type: "plan", text: "", metadata: { entries: event.entries } };
658
+ case "usage":
659
+ return { type: "usage", text: "", metadata: { tokensUsed: event.tokensUsed, contextSize: event.contextSize, cost: event.cost } };
660
+ default:
661
+ return { type: "text", text: "" };
662
+ }
663
+ }
664
+ // Public — adapters call this for assistant session wiring
665
+ wireSessionEvents(session, adapter) {
666
+ session.adapter = adapter;
667
+ session.agentInstance.onSessionUpdate = (event) => {
668
+ switch (event.type) {
669
+ case "text":
670
+ case "thought":
671
+ case "tool_call":
672
+ case "tool_update":
673
+ case "plan":
674
+ case "usage":
675
+ adapter.sendMessage(session.id, this.toOutgoingMessage(event));
676
+ break;
677
+ case "session_end":
678
+ session.status = "finished";
679
+ adapter.cleanupSkillCommands(session.id);
680
+ adapter.sendMessage(session.id, { type: "session_end", text: `Done (${event.reason})` });
681
+ this.notificationManager.notify(session.channelId, {
682
+ sessionId: session.id,
683
+ sessionName: session.name,
684
+ type: "completed",
685
+ summary: `Session "${session.name || session.id}" completed`
686
+ });
687
+ break;
688
+ case "error":
689
+ adapter.cleanupSkillCommands(session.id);
690
+ adapter.sendMessage(session.id, { type: "error", text: event.message });
691
+ this.notificationManager.notify(session.channelId, {
692
+ sessionId: session.id,
693
+ sessionName: session.name,
694
+ type: "error",
695
+ summary: event.message
696
+ });
697
+ break;
698
+ case "commands_update":
699
+ log2.debug({ commands: event.commands }, "Commands available");
700
+ adapter.sendSkillCommands(session.id, event.commands);
701
+ break;
702
+ }
703
+ };
704
+ session.agentInstance.onPermissionRequest = async (request) => {
705
+ const promise = new Promise((resolve) => {
706
+ session.pendingPermission = { requestId: request.id, resolve };
707
+ });
708
+ await adapter.sendPermissionRequest(session.id, request);
709
+ return promise;
710
+ };
711
+ }
712
+ };
713
+
714
+ // src/core/channel.ts
715
+ var ChannelAdapter = class {
716
+ constructor(core, config) {
717
+ this.core = core;
718
+ this.config = config;
719
+ }
720
+ // Skill commands — override in adapters that support dynamic commands
721
+ async sendSkillCommands(_sessionId, _commands) {
722
+ }
723
+ async cleanupSkillCommands(_sessionId) {
724
+ }
725
+ };
726
+
727
+ // src/adapters/telegram/adapter.ts
728
+ import { Bot } from "grammy";
729
+
730
+ // src/adapters/telegram/formatting.ts
731
+ function escapeHtml(text) {
732
+ if (!text) return "";
733
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
734
+ }
735
+ function markdownToTelegramHtml(md) {
736
+ const codeBlocks = [];
737
+ const inlineCodes = [];
738
+ let text = md.replace(/```(\w*)\n?([\s\S]*?)```/g, (_match, lang, code) => {
739
+ const index = codeBlocks.length;
740
+ const escapedCode = escapeHtml(code);
741
+ const langAttr = lang ? ` class="language-${escapeHtml(lang)}"` : "";
742
+ codeBlocks.push(`<pre><code${langAttr}>${escapedCode}</code></pre>`);
743
+ return `\0CODE_BLOCK_${index}\0`;
744
+ });
745
+ text = text.replace(/`([^`]+)`/g, (_match, code) => {
746
+ const index = inlineCodes.length;
747
+ inlineCodes.push(`<code>${escapeHtml(code)}</code>`);
748
+ return `\0INLINE_CODE_${index}\0`;
749
+ });
750
+ text = escapeHtml(text);
751
+ text = text.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
752
+ text = text.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, "<i>$1</i>");
753
+ text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
754
+ text = text.replace(/\x00CODE_BLOCK_(\d+)\x00/g, (_match, idx) => {
755
+ return codeBlocks[parseInt(idx, 10)];
756
+ });
757
+ text = text.replace(/\x00INLINE_CODE_(\d+)\x00/g, (_match, idx) => {
758
+ return inlineCodes[parseInt(idx, 10)];
759
+ });
760
+ return text;
761
+ }
762
+ var STATUS_ICON = {
763
+ pending: "\u23F3",
764
+ in_progress: "\u{1F504}",
765
+ completed: "\u2705",
766
+ failed: "\u274C"
767
+ };
768
+ var KIND_ICON = {
769
+ read: "\u{1F4D6}",
770
+ edit: "\u270F\uFE0F",
771
+ delete: "\u{1F5D1}\uFE0F",
772
+ execute: "\u25B6\uFE0F",
773
+ search: "\u{1F50D}",
774
+ fetch: "\u{1F310}",
775
+ think: "\u{1F9E0}",
776
+ move: "\u{1F4E6}",
777
+ other: "\u{1F6E0}\uFE0F"
778
+ };
779
+ function extractContentText(content, depth = 0) {
780
+ if (!content || depth > 5) return "";
781
+ if (typeof content === "string") return content;
782
+ if (Array.isArray(content)) {
783
+ return content.map((c) => extractContentText(c, depth + 1)).filter(Boolean).join("\n");
784
+ }
785
+ if (typeof content === "object" && content !== null) {
786
+ const c = content;
787
+ if (c.type === "text" && typeof c.text === "string") return c.text;
788
+ if (typeof c.text === "string") return c.text;
789
+ if (typeof c.content === "string") return c.content;
790
+ if (c.input) return extractContentText(c.input, depth + 1);
791
+ if (c.output) return extractContentText(c.output, depth + 1);
792
+ const keys = Object.keys(c).filter((k) => k !== "type");
793
+ if (keys.length === 0) return "";
794
+ return JSON.stringify(c, null, 2);
795
+ }
796
+ return String(content);
797
+ }
798
+ function truncateContent(text, maxLen = 3800) {
799
+ if (text.length <= maxLen) return text;
800
+ return text.slice(0, maxLen) + "\n\u2026 (truncated)";
801
+ }
802
+ function formatToolCall(tool) {
803
+ const si = STATUS_ICON[tool.status || ""] || "\u{1F527}";
804
+ const ki = KIND_ICON[tool.kind || ""] || "\u{1F6E0}\uFE0F";
805
+ let text = `${si} ${ki} <b>${escapeHtml(tool.name || "Tool")}</b>`;
806
+ const details = extractContentText(tool.content);
807
+ if (details) {
808
+ text += `
809
+ <pre>${escapeHtml(truncateContent(details))}</pre>`;
810
+ }
811
+ return text;
812
+ }
813
+ function formatToolUpdate(update) {
814
+ const si = STATUS_ICON[update.status] || "\u{1F527}";
815
+ const ki = KIND_ICON[update.kind || ""] || "\u{1F6E0}\uFE0F";
816
+ const name = update.name || "Tool";
817
+ let text = `${si} ${ki} <b>${escapeHtml(name)}</b>`;
818
+ const details = extractContentText(update.content);
819
+ if (details) {
820
+ text += `
821
+ <pre>${escapeHtml(truncateContent(details))}</pre>`;
822
+ }
823
+ return text;
824
+ }
825
+ function formatPlan(plan) {
826
+ const statusIcon = { pending: "\u2B1C", in_progress: "\u{1F504}", completed: "\u2705" };
827
+ const lines = plan.entries.map(
828
+ (e, i) => `${statusIcon[e.status] || "\u2B1C"} ${i + 1}. ${escapeHtml(e.content)}`
829
+ );
830
+ return `<b>Plan:</b>
831
+ ${lines.join("\n")}`;
832
+ }
833
+ function formatUsage(usage) {
834
+ const parts = [];
835
+ if (usage.tokensUsed != null) parts.push(`Tokens: ${usage.tokensUsed.toLocaleString()}`);
836
+ if (usage.contextSize != null) parts.push(`Context: ${usage.contextSize.toLocaleString()}`);
837
+ if (usage.cost) parts.push(`Cost: $${usage.cost.amount.toFixed(4)}`);
838
+ return `\u{1F4CA} ${parts.join(" | ")}`;
839
+ }
840
+ function splitMessage(text, maxLength = 4096) {
841
+ if (text.length <= maxLength) return [text];
842
+ const chunks = [];
843
+ let remaining = text;
844
+ while (remaining.length > 0) {
845
+ if (remaining.length <= maxLength) {
846
+ chunks.push(remaining);
847
+ break;
848
+ }
849
+ let splitAt = remaining.lastIndexOf("\n\n", maxLength);
850
+ if (splitAt === -1 || splitAt < maxLength * 0.5) {
851
+ splitAt = remaining.lastIndexOf("\n", maxLength);
852
+ }
853
+ if (splitAt === -1 || splitAt < maxLength * 0.5) {
854
+ splitAt = maxLength;
855
+ }
856
+ chunks.push(remaining.slice(0, splitAt));
857
+ remaining = remaining.slice(splitAt).trimStart();
858
+ }
859
+ return chunks;
860
+ }
861
+
862
+ // src/adapters/telegram/streaming.ts
863
+ var MessageDraft = class {
864
+ // 1 second throttle
865
+ constructor(bot, chatId, threadId) {
866
+ this.bot = bot;
867
+ this.chatId = chatId;
868
+ this.threadId = threadId;
869
+ }
870
+ messageId;
871
+ buffer = "";
872
+ lastFlush = 0;
873
+ flushTimer;
874
+ flushPromise = Promise.resolve();
875
+ // serialize flushes
876
+ minInterval = 1e3;
877
+ append(text) {
878
+ this.buffer += text;
879
+ this.scheduleFlush();
880
+ }
881
+ scheduleFlush() {
882
+ const now = Date.now();
883
+ const elapsed = now - this.lastFlush;
884
+ if (elapsed >= this.minInterval) {
885
+ this.flushPromise = this.flushPromise.then(() => this.flush()).catch(() => {
886
+ });
887
+ } else if (!this.flushTimer) {
888
+ this.flushTimer = setTimeout(() => {
889
+ this.flushTimer = void 0;
890
+ this.flushPromise = this.flushPromise.then(() => this.flush()).catch(() => {
891
+ });
892
+ }, this.minInterval - elapsed);
893
+ }
894
+ }
895
+ async flush() {
896
+ if (!this.buffer) return;
897
+ this.lastFlush = Date.now();
898
+ const html = markdownToTelegramHtml(this.buffer);
899
+ const truncated = html.length > 4096 ? html.slice(0, 4090) + "\n..." : html;
900
+ if (!truncated) return;
901
+ try {
902
+ if (!this.messageId) {
903
+ const msg = await this.bot.api.sendMessage(this.chatId, truncated, {
904
+ message_thread_id: this.threadId,
905
+ parse_mode: "HTML",
906
+ disable_notification: true
907
+ });
908
+ this.messageId = msg.message_id;
909
+ } else {
910
+ await this.bot.api.editMessageText(this.chatId, this.messageId, truncated, {
911
+ parse_mode: "HTML"
912
+ });
913
+ }
914
+ } catch {
915
+ try {
916
+ if (!this.messageId) {
917
+ const msg = await this.bot.api.sendMessage(this.chatId, this.buffer.slice(0, 4096), {
918
+ message_thread_id: this.threadId,
919
+ disable_notification: true
920
+ });
921
+ this.messageId = msg.message_id;
922
+ }
923
+ } catch {
924
+ }
925
+ }
926
+ }
927
+ async finalize() {
928
+ if (this.flushTimer) {
929
+ clearTimeout(this.flushTimer);
930
+ this.flushTimer = void 0;
931
+ }
932
+ await this.flushPromise;
933
+ if (!this.buffer) return this.messageId;
934
+ const html = markdownToTelegramHtml(this.buffer);
935
+ const chunks = splitMessage(html);
936
+ try {
937
+ for (let i = 0; i < chunks.length; i++) {
938
+ const chunk = chunks[i];
939
+ if (i === 0 && this.messageId) {
940
+ await this.bot.api.editMessageText(this.chatId, this.messageId, chunk, {
941
+ parse_mode: "HTML"
942
+ });
943
+ } else {
944
+ const msg = await this.bot.api.sendMessage(this.chatId, chunk, {
945
+ message_thread_id: this.threadId,
946
+ parse_mode: "HTML",
947
+ disable_notification: true
948
+ });
949
+ this.messageId = msg.message_id;
950
+ }
951
+ }
952
+ } catch {
953
+ try {
954
+ await this.bot.api.sendMessage(this.chatId, this.buffer.slice(0, 4096), {
955
+ message_thread_id: this.threadId,
956
+ disable_notification: true
957
+ });
958
+ } catch {
959
+ }
960
+ }
961
+ return this.messageId;
962
+ }
963
+ getMessageId() {
964
+ return this.messageId;
965
+ }
966
+ };
967
+
968
+ // src/adapters/telegram/topics.ts
969
+ async function ensureTopics(bot, chatId, config, saveConfig) {
970
+ let notificationTopicId = config.notificationTopicId;
971
+ let assistantTopicId = config.assistantTopicId;
972
+ if (notificationTopicId === null) {
973
+ const topic = await bot.api.createForumTopic(chatId, "\u{1F4CB} Notifications");
974
+ notificationTopicId = topic.message_thread_id;
975
+ await saveConfig({ notificationTopicId });
976
+ }
977
+ if (assistantTopicId === null) {
978
+ const topic = await bot.api.createForumTopic(chatId, "\u{1F916} Assistant");
979
+ assistantTopicId = topic.message_thread_id;
980
+ await saveConfig({ assistantTopicId });
981
+ }
982
+ return { notificationTopicId, assistantTopicId };
983
+ }
984
+ async function createSessionTopic(bot, chatId, name) {
985
+ const topic = await bot.api.createForumTopic(chatId, name);
986
+ return topic.message_thread_id;
987
+ }
988
+ async function renameSessionTopic(bot, chatId, threadId, name) {
989
+ try {
990
+ await bot.api.editForumTopic(chatId, threadId, { name });
991
+ } catch {
992
+ }
993
+ }
994
+ function buildDeepLink(chatId, messageId) {
995
+ const cleanId = String(chatId).replace("-100", "");
996
+ return `https://t.me/c/${cleanId}/${messageId}`;
997
+ }
998
+
999
+ // src/adapters/telegram/commands.ts
1000
+ import { InlineKeyboard } from "grammy";
1001
+ import { nanoid as nanoid2 } from "nanoid";
1002
+ var log4 = createChildLogger({ module: "telegram-commands" });
1003
+ function setupCommands(bot, core, chatId) {
1004
+ bot.command("new", (ctx) => handleNew(ctx, core, chatId));
1005
+ bot.command("new_chat", (ctx) => handleNewChat(ctx, core, chatId));
1006
+ bot.command("cancel", (ctx) => handleCancel(ctx, core));
1007
+ bot.command("status", (ctx) => handleStatus(ctx, core));
1008
+ bot.command("agents", (ctx) => handleAgents(ctx, core));
1009
+ bot.command("help", (ctx) => handleHelp(ctx));
1010
+ bot.command("menu", (ctx) => handleMenu(ctx));
1011
+ }
1012
+ function buildMenuKeyboard() {
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");
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
+ }
1044
+ async function handleMenu(ctx) {
1045
+ await ctx.reply(`<b>OpenACP Menu</b>
1046
+ Choose an action:`, {
1047
+ parse_mode: "HTML",
1048
+ reply_markup: buildMenuKeyboard()
1049
+ });
1050
+ }
1051
+ async function handleNew(ctx, core, chatId) {
1052
+ const rawMatch = ctx.match;
1053
+ const matchStr = typeof rawMatch === "string" ? rawMatch : "";
1054
+ const args = matchStr.split(" ").filter(Boolean);
1055
+ const agentName = args[0];
1056
+ const workspace = args[1];
1057
+ log4.info({ userId: ctx.from?.id, agentName }, "New session command");
1058
+ let threadId;
1059
+ try {
1060
+ const topicName = `\u{1F504} New Session`;
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
+ });
1066
+ const session = await core.handleNewSession(
1067
+ "telegram",
1068
+ agentName,
1069
+ workspace
1070
+ );
1071
+ session.threadId = String(threadId);
1072
+ const finalName = `\u{1F504} ${session.agentName} \u2014 New Session`;
1073
+ try {
1074
+ await ctx.api.editForumTopic(chatId, threadId, { name: finalName });
1075
+ } catch {
1076
+ }
1077
+ await ctx.api.sendMessage(
1078
+ chatId,
1079
+ `\u2705 Session started
1080
+ <b>Agent:</b> ${escapeHtml(session.agentName)}
1081
+ <b>Workspace:</b> <code>${escapeHtml(session.workingDirectory)}</code>`,
1082
+ {
1083
+ message_thread_id: threadId,
1084
+ parse_mode: "HTML"
1085
+ }
1086
+ );
1087
+ session.warmup().catch((err) => log4.error({ err }, "Warm-up error"));
1088
+ } catch (err) {
1089
+ if (threadId) {
1090
+ try {
1091
+ await ctx.api.deleteForumTopic(chatId, threadId);
1092
+ } catch {
1093
+ }
1094
+ }
1095
+ const message = err instanceof Error ? err.message : String(err);
1096
+ await ctx.reply(`\u274C ${escapeHtml(message)}`, { parse_mode: "HTML" });
1097
+ }
1098
+ }
1099
+ async function handleNewChat(ctx, core, chatId) {
1100
+ const threadId = ctx.message?.message_thread_id;
1101
+ if (!threadId) {
1102
+ await ctx.reply(
1103
+ "Use /new_chat inside a session topic to inherit its config.",
1104
+ { parse_mode: "HTML" }
1105
+ );
1106
+ return;
1107
+ }
1108
+ try {
1109
+ const session = await core.handleNewChat("telegram", String(threadId));
1110
+ if (!session) {
1111
+ await ctx.reply("No active session in this topic.", {
1112
+ parse_mode: "HTML"
1113
+ });
1114
+ return;
1115
+ }
1116
+ const topicName = `\u{1F504} ${session.agentName} \u2014 New Chat`;
1117
+ const newThreadId = await createSessionTopic(
1118
+ botFromCtx(ctx),
1119
+ chatId,
1120
+ topicName
1121
+ );
1122
+ await ctx.api.sendMessage(chatId, `\u23F3 Setting up session, please wait...`, {
1123
+ message_thread_id: newThreadId,
1124
+ parse_mode: "HTML"
1125
+ });
1126
+ session.threadId = String(newThreadId);
1127
+ await ctx.api.sendMessage(
1128
+ chatId,
1129
+ `\u2705 New chat (same agent &amp; workspace)
1130
+ <b>Agent:</b> ${escapeHtml(session.agentName)}
1131
+ <b>Workspace:</b> <code>${escapeHtml(session.workingDirectory)}</code>`,
1132
+ {
1133
+ message_thread_id: newThreadId,
1134
+ parse_mode: "HTML"
1135
+ }
1136
+ );
1137
+ session.warmup().catch((err) => log4.error({ err }, "Warm-up error"));
1138
+ } catch (err) {
1139
+ const message = err instanceof Error ? err.message : String(err);
1140
+ await ctx.reply(`\u274C ${escapeHtml(message)}`, { parse_mode: "HTML" });
1141
+ }
1142
+ }
1143
+ async function handleCancel(ctx, core) {
1144
+ const threadId = ctx.message?.message_thread_id;
1145
+ if (!threadId) return;
1146
+ const session = core.sessionManager.getSessionByThread(
1147
+ "telegram",
1148
+ String(threadId)
1149
+ );
1150
+ if (session) {
1151
+ log4.info({ sessionId: session.id }, "Cancel session command");
1152
+ await session.cancel();
1153
+ await ctx.reply("\u26D4 Session cancelled.", { parse_mode: "HTML" });
1154
+ }
1155
+ }
1156
+ async function handleStatus(ctx, core) {
1157
+ const threadId = ctx.message?.message_thread_id;
1158
+ if (threadId) {
1159
+ const session = core.sessionManager.getSessionByThread(
1160
+ "telegram",
1161
+ String(threadId)
1162
+ );
1163
+ if (session) {
1164
+ await ctx.reply(
1165
+ `<b>Session:</b> ${escapeHtml(session.name || session.id)}
1166
+ <b>Agent:</b> ${escapeHtml(session.agentName)}
1167
+ <b>Status:</b> ${escapeHtml(session.status)}
1168
+ <b>Workspace:</b> <code>${escapeHtml(session.workingDirectory)}</code>
1169
+ <b>Queue:</b> ${session.promptQueue.length} pending`,
1170
+ { parse_mode: "HTML" }
1171
+ );
1172
+ } else {
1173
+ await ctx.reply("No active session in this topic.", {
1174
+ parse_mode: "HTML"
1175
+ });
1176
+ }
1177
+ } else {
1178
+ const sessions = core.sessionManager.listSessions("telegram");
1179
+ const active = sessions.filter(
1180
+ (s) => s.status === "active" || s.status === "initializing"
1181
+ );
1182
+ await ctx.reply(
1183
+ `<b>OpenACP Status</b>
1184
+ Active sessions: ${active.length}
1185
+ Total sessions: ${sessions.length}`,
1186
+ { parse_mode: "HTML" }
1187
+ );
1188
+ }
1189
+ }
1190
+ async function handleAgents(ctx, core) {
1191
+ const agents = core.agentManager.getAvailableAgents();
1192
+ const defaultAgent = core.configManager.get().defaultAgent;
1193
+ const lines = agents.map(
1194
+ (a) => `\u2022 <b>${escapeHtml(a.name)}</b>${a.name === defaultAgent ? " (default)" : ""}
1195
+ <code>${escapeHtml(a.command)} ${a.args.map((arg) => escapeHtml(arg)).join(" ")}</code>`
1196
+ );
1197
+ const text = lines.length > 0 ? `<b>Available Agents:</b>
1198
+
1199
+ ${lines.join("\n")}` : `<b>Available Agents:</b>
1200
+
1201
+ No agents configured.`;
1202
+ await ctx.reply(text, { parse_mode: "HTML" });
1203
+ }
1204
+ async function handleHelp(ctx) {
1205
+ await ctx.reply(
1206
+ `<b>OpenACP Commands:</b>
1207
+
1208
+ /new [agent] [workspace] \u2014 Create new session
1209
+ /new_chat \u2014 New chat, same agent &amp; workspace
1210
+ /cancel \u2014 Cancel current session
1211
+ /status \u2014 Show session/system status
1212
+ /agents \u2014 List available agents
1213
+ /menu \u2014 Show interactive menu
1214
+ /help \u2014 Show this help
1215
+
1216
+ Or just chat in the \u{1F916} Assistant topic for help!`,
1217
+ { parse_mode: "HTML" }
1218
+ );
1219
+ }
1220
+ function botFromCtx(ctx) {
1221
+ return { api: ctx.api };
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
+ ];
1268
+
1269
+ // src/adapters/telegram/permissions.ts
1270
+ import { InlineKeyboard as InlineKeyboard2 } from "grammy";
1271
+ import { nanoid as nanoid3 } from "nanoid";
1272
+ var log5 = createChildLogger({ module: "telegram-permissions" });
1273
+ var PermissionHandler = class {
1274
+ constructor(bot, chatId, getSession, sendNotification) {
1275
+ this.bot = bot;
1276
+ this.chatId = chatId;
1277
+ this.getSession = getSession;
1278
+ this.sendNotification = sendNotification;
1279
+ }
1280
+ pending = /* @__PURE__ */ new Map();
1281
+ async sendPermissionRequest(session, request) {
1282
+ const threadId = Number(session.threadId);
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
+ });
1289
+ const keyboard = new InlineKeyboard2();
1290
+ for (const option of request.options) {
1291
+ const emoji = option.isAllow ? "\u2705" : "\u274C";
1292
+ keyboard.text(`${emoji} ${option.label}`, `p:${callbackKey}:${option.id}`);
1293
+ }
1294
+ const msg = await this.bot.api.sendMessage(
1295
+ this.chatId,
1296
+ `\u{1F510} <b>Permission request:</b>
1297
+
1298
+ ${escapeHtml(request.description)}`,
1299
+ {
1300
+ message_thread_id: threadId,
1301
+ parse_mode: "HTML",
1302
+ reply_markup: keyboard,
1303
+ disable_notification: false
1304
+ }
1305
+ );
1306
+ const deepLink = buildDeepLink(this.chatId, msg.message_id);
1307
+ await this.sendNotification({
1308
+ sessionId: session.id,
1309
+ sessionName: session.name,
1310
+ type: "permission",
1311
+ summary: request.description,
1312
+ deepLink
1313
+ });
1314
+ }
1315
+ setupCallbackHandler() {
1316
+ this.bot.on("callback_query:data", async (ctx) => {
1317
+ const data = ctx.callbackQuery.data;
1318
+ if (!data.startsWith("p:")) return;
1319
+ const parts = data.split(":");
1320
+ if (parts.length < 3) return;
1321
+ const [, callbackKey, optionId] = parts;
1322
+ const pending = this.pending.get(callbackKey);
1323
+ if (!pending) {
1324
+ try {
1325
+ await ctx.answerCallbackQuery({ text: "\u274C Expired" });
1326
+ } catch {
1327
+ }
1328
+ return;
1329
+ }
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");
1333
+ if (session?.pendingPermission?.requestId === pending.requestId) {
1334
+ session.pendingPermission.resolve(optionId);
1335
+ session.pendingPermission = void 0;
1336
+ }
1337
+ this.pending.delete(callbackKey);
1338
+ try {
1339
+ await ctx.answerCallbackQuery({ text: "\u2705 Responded" });
1340
+ } catch {
1341
+ }
1342
+ try {
1343
+ await ctx.editMessageReplyMarkup({ reply_markup: void 0 });
1344
+ } catch {
1345
+ }
1346
+ });
1347
+ }
1348
+ };
1349
+
1350
+ // src/adapters/telegram/assistant.ts
1351
+ async function spawnAssistant(core, adapter, assistantTopicId) {
1352
+ const config = core.configManager.get();
1353
+ const session = await core.sessionManager.createSession(
1354
+ "telegram",
1355
+ config.defaultAgent,
1356
+ core.configManager.resolveWorkspace(),
1357
+ core.agentManager
1358
+ );
1359
+ session.threadId = String(assistantTopicId);
1360
+ const systemPrompt = buildAssistantSystemPrompt(config);
1361
+ await session.enqueuePrompt(systemPrompt);
1362
+ core.wireSessionEvents(session, adapter);
1363
+ return session;
1364
+ }
1365
+ function buildAssistantSystemPrompt(config) {
1366
+ const agentNames = Object.keys(config.agents).join(", ");
1367
+ return `You are the OpenACP Assistant. Help users manage their AI coding sessions.
1368
+
1369
+ Available agents: ${agentNames}
1370
+ Default agent: ${config.defaultAgent}
1371
+ Workspace base: ${config.workspace.baseDir}
1372
+
1373
+ When a user wants to create a session, guide them through:
1374
+ 1. Which agent to use
1375
+ 2. Which workspace/project
1376
+ 3. Confirm and create
1377
+
1378
+ Commands reference:
1379
+ - /new [agent] [workspace] \u2014 Create new session
1380
+ - /new_chat \u2014 New chat with same agent & workspace
1381
+ - /cancel \u2014 Cancel current session
1382
+ - /status \u2014 Show status
1383
+ - /agents \u2014 List agents
1384
+ - /help \u2014 Show help
1385
+
1386
+ Be concise and helpful. When the user confirms session creation, tell them you'll create it now.`;
1387
+ }
1388
+ async function handleAssistantMessage(session, text) {
1389
+ if (!session) return;
1390
+ await session.enqueuePrompt(text);
1391
+ }
1392
+ function redirectToAssistant(chatId, assistantTopicId) {
1393
+ const cleanId = String(chatId).replace("-100", "");
1394
+ const link = `https://t.me/c/${cleanId}/${assistantTopicId}`;
1395
+ return `\u{1F4AC} Please use the <a href="${link}">\u{1F916} Assistant</a> topic to chat with OpenACP.`;
1396
+ }
1397
+
1398
+ // src/adapters/telegram/adapter.ts
1399
+ var log6 = createChildLogger({ module: "telegram" });
1400
+ var TelegramAdapter = class extends ChannelAdapter {
1401
+ bot;
1402
+ telegramConfig;
1403
+ sessionDrafts = /* @__PURE__ */ new Map();
1404
+ toolCallMessages = /* @__PURE__ */ new Map();
1405
+ // sessionId → (toolCallId → state)
1406
+ permissionHandler;
1407
+ assistantSession = null;
1408
+ notificationTopicId;
1409
+ assistantTopicId;
1410
+ skillMessages = /* @__PURE__ */ new Map();
1411
+ // sessionId → pinned messageId
1412
+ constructor(core, config) {
1413
+ super(core, config);
1414
+ this.telegramConfig = config;
1415
+ }
1416
+ async start() {
1417
+ this.bot = new Bot(this.telegramConfig.botToken);
1418
+ this.bot.catch((err) => {
1419
+ const rootCause = err.error instanceof Error ? err.error : err;
1420
+ log6.error({ err: rootCause }, "Telegram bot error");
1421
+ });
1422
+ this.bot.api.config.use((prev, method, payload, signal) => {
1423
+ if (method === "getUpdates") {
1424
+ const p = payload;
1425
+ p.allowed_updates = p.allowed_updates ?? [
1426
+ "message",
1427
+ "callback_query"
1428
+ ];
1429
+ }
1430
+ return prev(method, payload, signal);
1431
+ });
1432
+ await this.bot.api.setMyCommands(STATIC_COMMANDS, {
1433
+ scope: { type: "chat", chat_id: this.telegramConfig.chatId }
1434
+ });
1435
+ this.bot.use((ctx, next) => {
1436
+ const chatId = ctx.chat?.id ?? ctx.callbackQuery?.message?.chat?.id;
1437
+ if (chatId !== this.telegramConfig.chatId) return;
1438
+ return next();
1439
+ });
1440
+ const topics = await ensureTopics(
1441
+ this.bot,
1442
+ this.telegramConfig.chatId,
1443
+ this.telegramConfig,
1444
+ async (updates) => {
1445
+ await this.core.configManager.save({
1446
+ channels: { telegram: updates }
1447
+ });
1448
+ }
1449
+ );
1450
+ this.notificationTopicId = topics.notificationTopicId;
1451
+ this.assistantTopicId = topics.assistantTopicId;
1452
+ this.permissionHandler = new PermissionHandler(
1453
+ this.bot,
1454
+ this.telegramConfig.chatId,
1455
+ (sessionId) => this.core.sessionManager.getSession(sessionId),
1456
+ (notification) => this.sendNotification(notification)
1457
+ );
1458
+ setupSkillCallbacks(this.bot, this.core);
1459
+ setupMenuCallbacks(
1460
+ this.bot,
1461
+ this.core,
1462
+ this.telegramConfig.chatId
1463
+ );
1464
+ setupCommands(
1465
+ this.bot,
1466
+ this.core,
1467
+ this.telegramConfig.chatId
1468
+ );
1469
+ this.permissionHandler.setupCallbackHandler();
1470
+ this.setupRoutes();
1471
+ this.bot.start({
1472
+ allowed_updates: ["message", "callback_query"],
1473
+ onStart: () => log6.info(
1474
+ { chatId: this.telegramConfig.chatId },
1475
+ "Telegram bot started"
1476
+ )
1477
+ });
1478
+ try {
1479
+ this.assistantSession = await spawnAssistant(
1480
+ this.core,
1481
+ this,
1482
+ this.assistantTopicId
1483
+ );
1484
+ } catch (err) {
1485
+ log6.error({ err }, "Failed to spawn assistant");
1486
+ }
1487
+ try {
1488
+ const config = this.core.configManager.get();
1489
+ const agents = this.core.agentManager.getAvailableAgents();
1490
+ const agentList = agents.map(
1491
+ (a) => `${escapeHtml(a.name)}${a.name === config.defaultAgent ? " (default)" : ""}`
1492
+ ).join(", ");
1493
+ const workspace = escapeHtml(config.workspace.baseDir);
1494
+ const welcomeText = `\u{1F44B} <b>OpenACP Assistant</b> is online.
1495
+
1496
+ Available agents: ${agentList}
1497
+ Workspace: <code>${workspace}</code>
1498
+
1499
+ <b>Select an action:</b>`;
1500
+ await this.bot.api.sendMessage(this.telegramConfig.chatId, welcomeText, {
1501
+ message_thread_id: this.assistantTopicId,
1502
+ parse_mode: "HTML",
1503
+ reply_markup: buildMenuKeyboard()
1504
+ });
1505
+ } catch (err) {
1506
+ log6.warn({ err }, "Failed to send welcome message");
1507
+ }
1508
+ }
1509
+ async stop() {
1510
+ if (this.assistantSession) {
1511
+ await this.assistantSession.destroy();
1512
+ }
1513
+ await this.bot.stop();
1514
+ log6.info("Telegram bot stopped");
1515
+ }
1516
+ setupRoutes() {
1517
+ this.bot.on("message:text", async (ctx) => {
1518
+ const threadId = ctx.message.message_thread_id;
1519
+ if (!threadId) {
1520
+ const html = redirectToAssistant(
1521
+ this.telegramConfig.chatId,
1522
+ this.assistantTopicId
1523
+ );
1524
+ await ctx.reply(html, { parse_mode: "HTML" });
1525
+ return;
1526
+ }
1527
+ if (threadId === this.notificationTopicId) return;
1528
+ if (threadId === this.assistantTopicId) {
1529
+ ctx.replyWithChatAction("typing").catch(() => {
1530
+ });
1531
+ handleAssistantMessage(this.assistantSession, ctx.message.text).catch(
1532
+ (err) => log6.error({ err }, "Assistant error")
1533
+ );
1534
+ return;
1535
+ }
1536
+ ctx.replyWithChatAction("typing").catch(() => {
1537
+ });
1538
+ this.core.handleMessage({
1539
+ channelId: "telegram",
1540
+ threadId: String(threadId),
1541
+ userId: String(ctx.from.id),
1542
+ text: ctx.message.text
1543
+ }).catch((err) => log6.error({ err }, "handleMessage error"));
1544
+ });
1545
+ }
1546
+ // --- ChannelAdapter implementations ---
1547
+ async sendMessage(sessionId, content) {
1548
+ const session = this.core.sessionManager.getSession(
1549
+ sessionId
1550
+ );
1551
+ if (!session) return;
1552
+ const threadId = Number(session.threadId);
1553
+ switch (content.type) {
1554
+ case "thought": {
1555
+ break;
1556
+ }
1557
+ case "text": {
1558
+ let draft = this.sessionDrafts.get(sessionId);
1559
+ if (!draft) {
1560
+ draft = new MessageDraft(
1561
+ this.bot,
1562
+ this.telegramConfig.chatId,
1563
+ threadId
1564
+ );
1565
+ this.sessionDrafts.set(sessionId, draft);
1566
+ }
1567
+ draft.append(content.text);
1568
+ break;
1569
+ }
1570
+ case "tool_call": {
1571
+ await this.finalizeDraft(sessionId);
1572
+ const meta = content.metadata;
1573
+ const msg = await this.bot.api.sendMessage(
1574
+ this.telegramConfig.chatId,
1575
+ formatToolCall(meta),
1576
+ {
1577
+ message_thread_id: threadId,
1578
+ parse_mode: "HTML",
1579
+ disable_notification: true
1580
+ }
1581
+ );
1582
+ if (!this.toolCallMessages.has(sessionId)) {
1583
+ this.toolCallMessages.set(sessionId, /* @__PURE__ */ new Map());
1584
+ }
1585
+ this.toolCallMessages.get(sessionId).set(meta.id, {
1586
+ msgId: msg.message_id,
1587
+ name: meta.name,
1588
+ kind: meta.kind
1589
+ });
1590
+ break;
1591
+ }
1592
+ case "tool_update": {
1593
+ const meta = content.metadata;
1594
+ const toolState = this.toolCallMessages.get(sessionId)?.get(meta.id);
1595
+ if (toolState) {
1596
+ const merged = {
1597
+ ...meta,
1598
+ name: meta.name || toolState.name,
1599
+ kind: meta.kind || toolState.kind
1600
+ };
1601
+ try {
1602
+ await this.bot.api.editMessageText(
1603
+ this.telegramConfig.chatId,
1604
+ toolState.msgId,
1605
+ formatToolUpdate(merged),
1606
+ { parse_mode: "HTML" }
1607
+ );
1608
+ } catch {
1609
+ }
1610
+ }
1611
+ break;
1612
+ }
1613
+ case "plan": {
1614
+ await this.finalizeDraft(sessionId);
1615
+ await this.bot.api.sendMessage(
1616
+ this.telegramConfig.chatId,
1617
+ formatPlan(content.metadata),
1618
+ {
1619
+ message_thread_id: threadId,
1620
+ parse_mode: "HTML",
1621
+ disable_notification: true
1622
+ }
1623
+ );
1624
+ break;
1625
+ }
1626
+ case "usage": {
1627
+ await this.bot.api.sendMessage(
1628
+ this.telegramConfig.chatId,
1629
+ formatUsage(content.metadata),
1630
+ {
1631
+ message_thread_id: threadId,
1632
+ parse_mode: "HTML",
1633
+ disable_notification: true
1634
+ }
1635
+ );
1636
+ break;
1637
+ }
1638
+ case "session_end": {
1639
+ await this.finalizeDraft(sessionId);
1640
+ this.sessionDrafts.delete(sessionId);
1641
+ this.toolCallMessages.delete(sessionId);
1642
+ await this.cleanupSkillCommands(sessionId);
1643
+ await this.bot.api.sendMessage(
1644
+ this.telegramConfig.chatId,
1645
+ `\u2705 <b>Done</b>`,
1646
+ {
1647
+ message_thread_id: threadId,
1648
+ parse_mode: "HTML",
1649
+ disable_notification: true
1650
+ }
1651
+ );
1652
+ break;
1653
+ }
1654
+ case "error": {
1655
+ await this.finalizeDraft(sessionId);
1656
+ await this.bot.api.sendMessage(
1657
+ this.telegramConfig.chatId,
1658
+ `\u274C <b>Error:</b> ${escapeHtml(content.text)}`,
1659
+ {
1660
+ message_thread_id: threadId,
1661
+ parse_mode: "HTML",
1662
+ disable_notification: true
1663
+ }
1664
+ );
1665
+ break;
1666
+ }
1667
+ }
1668
+ }
1669
+ async sendPermissionRequest(sessionId, request) {
1670
+ log6.info({ sessionId, requestId: request.id }, "Permission request sent");
1671
+ const session = this.core.sessionManager.getSession(
1672
+ sessionId
1673
+ );
1674
+ if (!session) return;
1675
+ await this.permissionHandler.sendPermissionRequest(session, request);
1676
+ }
1677
+ async sendNotification(notification) {
1678
+ log6.info(
1679
+ { sessionId: notification.sessionId, type: notification.type },
1680
+ "Notification sent"
1681
+ );
1682
+ if (!this.notificationTopicId) return;
1683
+ const emoji = {
1684
+ completed: "\u2705",
1685
+ error: "\u274C",
1686
+ permission: "\u{1F510}",
1687
+ input_required: "\u{1F4AC}"
1688
+ };
1689
+ let text = `${emoji[notification.type] || "\u2139\uFE0F"} <b>${escapeHtml(notification.sessionName || notification.sessionId)}</b>
1690
+ `;
1691
+ text += escapeHtml(notification.summary);
1692
+ if (notification.deepLink) {
1693
+ text += `
1694
+
1695
+ <a href="${notification.deepLink}">\u2192 Go to message</a>`;
1696
+ }
1697
+ await this.bot.api.sendMessage(this.telegramConfig.chatId, text, {
1698
+ message_thread_id: this.notificationTopicId,
1699
+ parse_mode: "HTML",
1700
+ disable_notification: false
1701
+ });
1702
+ }
1703
+ async createSessionThread(sessionId, name) {
1704
+ log6.info({ sessionId, name }, "Session topic created");
1705
+ return String(
1706
+ await createSessionTopic(this.bot, this.telegramConfig.chatId, name)
1707
+ );
1708
+ }
1709
+ async renameSessionThread(sessionId, newName) {
1710
+ const session = this.core.sessionManager.getSession(
1711
+ sessionId
1712
+ );
1713
+ if (!session) return;
1714
+ await renameSessionTopic(
1715
+ this.bot,
1716
+ this.telegramConfig.chatId,
1717
+ Number(session.threadId),
1718
+ newName
1719
+ );
1720
+ }
1721
+ async sendSkillCommands(sessionId, commands) {
1722
+ const session = this.core.sessionManager.getSession(sessionId);
1723
+ if (!session) return;
1724
+ const threadId = Number(session.threadId);
1725
+ if (!threadId) return;
1726
+ if (commands.length === 0) {
1727
+ await this.cleanupSkillCommands(sessionId);
1728
+ return;
1729
+ }
1730
+ clearSkillCallbacks(sessionId);
1731
+ const keyboard = buildSkillKeyboard(sessionId, commands);
1732
+ const text = "\u{1F6E0} <b>Available commands:</b>";
1733
+ const existingMsgId = this.skillMessages.get(sessionId);
1734
+ if (existingMsgId) {
1735
+ try {
1736
+ await this.bot.api.editMessageText(
1737
+ this.telegramConfig.chatId,
1738
+ existingMsgId,
1739
+ text,
1740
+ { parse_mode: "HTML", reply_markup: keyboard }
1741
+ );
1742
+ return;
1743
+ } catch {
1744
+ }
1745
+ }
1746
+ try {
1747
+ const msg = await this.bot.api.sendMessage(
1748
+ this.telegramConfig.chatId,
1749
+ text,
1750
+ {
1751
+ message_thread_id: threadId,
1752
+ parse_mode: "HTML",
1753
+ reply_markup: keyboard,
1754
+ disable_notification: true
1755
+ }
1756
+ );
1757
+ this.skillMessages.set(sessionId, msg.message_id);
1758
+ await this.bot.api.pinChatMessage(this.telegramConfig.chatId, msg.message_id, {
1759
+ disable_notification: true
1760
+ });
1761
+ } catch (err) {
1762
+ log6.error({ err, sessionId }, "Failed to send skill commands");
1763
+ }
1764
+ await this.updateCommandAutocomplete(commands);
1765
+ }
1766
+ async cleanupSkillCommands(sessionId) {
1767
+ const msgId = this.skillMessages.get(sessionId);
1768
+ if (!msgId) return;
1769
+ try {
1770
+ await this.bot.api.editMessageText(
1771
+ this.telegramConfig.chatId,
1772
+ msgId,
1773
+ "\u{1F6E0} <i>Session ended</i>",
1774
+ { parse_mode: "HTML" }
1775
+ );
1776
+ await this.bot.api.unpinChatMessage(this.telegramConfig.chatId, msgId);
1777
+ } catch {
1778
+ }
1779
+ this.skillMessages.delete(sessionId);
1780
+ clearSkillCallbacks(sessionId);
1781
+ await this.updateCommandAutocomplete([]);
1782
+ }
1783
+ async updateCommandAutocomplete(skillCommands) {
1784
+ const validSkills = skillCommands.map((c) => ({
1785
+ command: c.name.toLowerCase().replace(/[^a-z0-9_]/g, "_").slice(0, 32),
1786
+ description: (c.description || c.name).replace(/\n/g, " ").slice(0, 256)
1787
+ })).filter((c) => c.command.length > 0);
1788
+ const all = [...STATIC_COMMANDS, ...validSkills];
1789
+ try {
1790
+ await this.bot.api.setMyCommands(all, {
1791
+ scope: { type: "chat", chat_id: this.telegramConfig.chatId }
1792
+ });
1793
+ log6.info({ count: all.length, skills: validSkills.length }, "Updated command autocomplete");
1794
+ } catch (err) {
1795
+ log6.error({ err, commands: all }, "Failed to update command autocomplete");
1796
+ }
1797
+ }
1798
+ async finalizeDraft(sessionId) {
1799
+ const draft = this.sessionDrafts.get(sessionId);
1800
+ if (draft) {
1801
+ await draft.finalize();
1802
+ this.sessionDrafts.delete(sessionId);
1803
+ }
1804
+ }
1805
+ };
1806
+
1807
+ export {
1808
+ nodeToWebWritable,
1809
+ nodeToWebReadable,
1810
+ StderrCapture,
1811
+ AgentInstance,
1812
+ AgentManager,
1813
+ Session,
1814
+ SessionManager,
1815
+ NotificationManager,
1816
+ OpenACPCore,
1817
+ ChannelAdapter,
1818
+ TelegramAdapter
1819
+ };
1820
+ //# sourceMappingURL=chunk-3F7TYQ4H.js.map