@openacp/cli 0.4.5 → 0.4.8
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 +81 -15
- package/dist/agent-registry-7HC6D4CH.js +7 -0
- package/dist/{chunk-WHKLPZGK.js → chunk-6MJLVZXV.js} +8 -8
- package/dist/{chunk-V5P3K4A5.js → chunk-BBPWAWE3.js} +1137 -119
- package/dist/chunk-BBPWAWE3.js.map +1 -0
- package/dist/{chunk-3QACY5E3.js → chunk-C6YIUTGR.js} +2 -2
- package/dist/{chunk-2SY7Y2VB.js → chunk-HZD3CGPK.js} +2 -2
- package/dist/{chunk-BLVZFCKN.js → chunk-UAUTLC4E.js} +27 -3
- package/dist/{chunk-BLVZFCKN.js.map → chunk-UAUTLC4E.js.map} +1 -1
- package/dist/chunk-VA2M52CM.js +15 -0
- package/dist/chunk-VA2M52CM.js.map +1 -0
- package/dist/{chunk-WF5XDN4D.js → chunk-ZRFBLD3W.js} +6 -2
- package/dist/chunk-ZRFBLD3W.js.map +1 -0
- package/dist/cli.js +388 -38
- package/dist/cli.js.map +1 -1
- package/dist/{config-J5YQOMDU.js → config-H2DSEHNW.js} +2 -2
- package/dist/config-editor-SKS4LJLT.js +11 -0
- package/dist/{daemon-SLGQGRKO.js → daemon-VF6HJQXD.js} +3 -3
- package/dist/index.d.ts +111 -10
- package/dist/index.js +13 -10
- package/dist/integrate-WUPLRJD3.js +145 -0
- package/dist/integrate-WUPLRJD3.js.map +1 -0
- package/dist/{main-3CDOICYN.js → main-NV7YN3VY.js} +27 -14
- package/dist/main-NV7YN3VY.js.map +1 -0
- package/dist/{setup-JQZBPXWS.js → setup-FCVL75K6.js} +3 -3
- package/dist/setup-FCVL75K6.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-V5P3K4A5.js.map +0 -1
- package/dist/chunk-WF5XDN4D.js.map +0 -1
- package/dist/config-editor-IXL4BFG3.js +0 -11
- package/dist/main-3CDOICYN.js.map +0 -1
- /package/dist/{config-J5YQOMDU.js.map → agent-registry-7HC6D4CH.js.map} +0 -0
- /package/dist/{chunk-WHKLPZGK.js.map → chunk-6MJLVZXV.js.map} +0 -0
- /package/dist/{chunk-3QACY5E3.js.map → chunk-C6YIUTGR.js.map} +0 -0
- /package/dist/{chunk-2SY7Y2VB.js.map → chunk-HZD3CGPK.js.map} +0 -0
- /package/dist/{config-editor-IXL4BFG3.js.map → config-H2DSEHNW.js.map} +0 -0
- /package/dist/{daemon-SLGQGRKO.js.map → config-editor-SKS4LJLT.js.map} +0 -0
- /package/dist/{setup-JQZBPXWS.js.map → daemon-VF6HJQXD.js.map} +0 -0
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getAgentCapabilities
|
|
3
|
+
} from "./chunk-VA2M52CM.js";
|
|
1
4
|
import {
|
|
2
5
|
createChildLogger,
|
|
3
6
|
createSessionLogger
|
|
@@ -7,10 +10,10 @@ import {
|
|
|
7
10
|
function nodeToWebWritable(nodeStream) {
|
|
8
11
|
return new WritableStream({
|
|
9
12
|
write(chunk) {
|
|
10
|
-
return new Promise((
|
|
13
|
+
return new Promise((resolve2, reject) => {
|
|
11
14
|
nodeStream.write(Buffer.from(chunk), (err) => {
|
|
12
15
|
if (err) reject(err);
|
|
13
|
-
else
|
|
16
|
+
else resolve2();
|
|
14
17
|
});
|
|
15
18
|
});
|
|
16
19
|
}
|
|
@@ -142,7 +145,7 @@ var AgentInstance = class _AgentInstance {
|
|
|
142
145
|
env: { ...process.env, ...agentDef.env }
|
|
143
146
|
}
|
|
144
147
|
);
|
|
145
|
-
await new Promise((
|
|
148
|
+
await new Promise((resolve2, reject) => {
|
|
146
149
|
instance.child.on("error", (err) => {
|
|
147
150
|
reject(
|
|
148
151
|
new Error(
|
|
@@ -150,7 +153,7 @@ var AgentInstance = class _AgentInstance {
|
|
|
150
153
|
)
|
|
151
154
|
);
|
|
152
155
|
});
|
|
153
|
-
instance.child.on("spawn", () =>
|
|
156
|
+
instance.child.on("spawn", () => resolve2());
|
|
154
157
|
});
|
|
155
158
|
instance.stderrCapture = new StderrCapture(50);
|
|
156
159
|
instance.child.stderr.on("data", (chunk) => {
|
|
@@ -428,9 +431,9 @@ ${stderr}`
|
|
|
428
431
|
signal: state.exitStatus.signal
|
|
429
432
|
};
|
|
430
433
|
}
|
|
431
|
-
return new Promise((
|
|
434
|
+
return new Promise((resolve2) => {
|
|
432
435
|
state.process.on("exit", (code, signal) => {
|
|
433
|
-
|
|
436
|
+
resolve2({ exitCode: code, signal });
|
|
434
437
|
});
|
|
435
438
|
});
|
|
436
439
|
},
|
|
@@ -588,8 +591,8 @@ var PromptQueue = class {
|
|
|
588
591
|
processing = false;
|
|
589
592
|
async enqueue(text) {
|
|
590
593
|
if (this.processing) {
|
|
591
|
-
return new Promise((
|
|
592
|
-
this.queue.push({ text, resolve });
|
|
594
|
+
return new Promise((resolve2) => {
|
|
595
|
+
this.queue.push({ text, resolve: resolve2 });
|
|
593
596
|
});
|
|
594
597
|
}
|
|
595
598
|
await this.process(text);
|
|
@@ -634,8 +637,8 @@ var PermissionGate = class {
|
|
|
634
637
|
setPending(request) {
|
|
635
638
|
this.request = request;
|
|
636
639
|
this.settled = false;
|
|
637
|
-
return new Promise((
|
|
638
|
-
this.resolveFn =
|
|
640
|
+
return new Promise((resolve2, reject) => {
|
|
641
|
+
this.resolveFn = resolve2;
|
|
639
642
|
this.rejectFn = reject;
|
|
640
643
|
});
|
|
641
644
|
}
|
|
@@ -827,6 +830,7 @@ var SessionManager = class {
|
|
|
827
830
|
createdAt: session.createdAt.toISOString(),
|
|
828
831
|
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
829
832
|
name: session.name,
|
|
833
|
+
dangerousMode: false,
|
|
830
834
|
platform: {}
|
|
831
835
|
});
|
|
832
836
|
}
|
|
@@ -843,6 +847,17 @@ var SessionManager = class {
|
|
|
843
847
|
}
|
|
844
848
|
return void 0;
|
|
845
849
|
}
|
|
850
|
+
getSessionByAgentSessionId(agentSessionId) {
|
|
851
|
+
for (const session of this.sessions.values()) {
|
|
852
|
+
if (session.agentSessionId === agentSessionId) {
|
|
853
|
+
return session;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
return void 0;
|
|
857
|
+
}
|
|
858
|
+
getRecordByAgentSessionId(agentSessionId) {
|
|
859
|
+
return this.store?.findByAgentSessionId(agentSessionId);
|
|
860
|
+
}
|
|
846
861
|
getRecordByThread(channelId, threadId) {
|
|
847
862
|
return this.store?.findByPlatform(
|
|
848
863
|
channelId,
|
|
@@ -910,6 +925,18 @@ var SessionManager = class {
|
|
|
910
925
|
if (channelId) return all.filter((s) => s.channelId === channelId);
|
|
911
926
|
return all;
|
|
912
927
|
}
|
|
928
|
+
listRecords(filter) {
|
|
929
|
+
if (!this.store) return [];
|
|
930
|
+
let records = this.store.list();
|
|
931
|
+
if (filter?.statuses?.length) {
|
|
932
|
+
records = records.filter((r) => filter.statuses.includes(r.status));
|
|
933
|
+
}
|
|
934
|
+
return records;
|
|
935
|
+
}
|
|
936
|
+
async removeRecord(sessionId) {
|
|
937
|
+
if (!this.store) return;
|
|
938
|
+
await this.store.remove(sessionId);
|
|
939
|
+
}
|
|
913
940
|
async destroyAll() {
|
|
914
941
|
if (this.store) {
|
|
915
942
|
for (const session of this.sessions.values()) {
|
|
@@ -1189,6 +1216,11 @@ var JsonFileSessionStore = class {
|
|
|
1189
1216
|
}
|
|
1190
1217
|
return void 0;
|
|
1191
1218
|
}
|
|
1219
|
+
findByAgentSessionId(agentSessionId) {
|
|
1220
|
+
return [...this.records.values()].find(
|
|
1221
|
+
(r) => r.agentSessionId === agentSessionId || r.originalAgentSessionId === agentSessionId
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1192
1224
|
list(channelId) {
|
|
1193
1225
|
const all = [...this.records.values()];
|
|
1194
1226
|
if (channelId) return all.filter((r) => r.channelId === channelId);
|
|
@@ -1392,6 +1424,95 @@ var OpenACPCore = class {
|
|
|
1392
1424
|
}
|
|
1393
1425
|
return session;
|
|
1394
1426
|
}
|
|
1427
|
+
async adoptSession(agentName, agentSessionId, cwd) {
|
|
1428
|
+
const caps = getAgentCapabilities(agentName);
|
|
1429
|
+
if (!caps.supportsResume) {
|
|
1430
|
+
return { ok: false, error: "agent_not_supported", message: `Agent '${agentName}' does not support session resume` };
|
|
1431
|
+
}
|
|
1432
|
+
const agentDef = this.agentManager.getAgent(agentName);
|
|
1433
|
+
if (!agentDef) {
|
|
1434
|
+
return { ok: false, error: "agent_not_supported", message: `Agent '${agentName}' not found` };
|
|
1435
|
+
}
|
|
1436
|
+
const { existsSync } = await import("fs");
|
|
1437
|
+
if (!existsSync(cwd)) {
|
|
1438
|
+
return { ok: false, error: "invalid_cwd", message: `Directory does not exist: ${cwd}` };
|
|
1439
|
+
}
|
|
1440
|
+
const maxSessions = this.configManager.get().security.maxConcurrentSessions;
|
|
1441
|
+
if (this.sessionManager.listSessions().length >= maxSessions) {
|
|
1442
|
+
return { ok: false, error: "session_limit", message: "Maximum concurrent sessions reached" };
|
|
1443
|
+
}
|
|
1444
|
+
const existingRecord = this.sessionManager.getRecordByAgentSessionId(agentSessionId);
|
|
1445
|
+
if (existingRecord) {
|
|
1446
|
+
const platform = existingRecord.platform;
|
|
1447
|
+
if (platform?.topicId) {
|
|
1448
|
+
const adapter2 = this.adapters.values().next().value;
|
|
1449
|
+
if (adapter2) {
|
|
1450
|
+
try {
|
|
1451
|
+
await adapter2.sendMessage(existingRecord.sessionId, {
|
|
1452
|
+
type: "text",
|
|
1453
|
+
text: "Session resumed from CLI."
|
|
1454
|
+
});
|
|
1455
|
+
} catch {
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
return {
|
|
1459
|
+
ok: true,
|
|
1460
|
+
sessionId: existingRecord.sessionId,
|
|
1461
|
+
threadId: String(platform.topicId),
|
|
1462
|
+
status: "existing"
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
let agentInstance;
|
|
1467
|
+
try {
|
|
1468
|
+
agentInstance = await this.agentManager.resume(agentName, cwd, agentSessionId);
|
|
1469
|
+
} catch (err) {
|
|
1470
|
+
return {
|
|
1471
|
+
ok: false,
|
|
1472
|
+
error: "resume_failed",
|
|
1473
|
+
message: `Failed to resume session: ${err instanceof Error ? err.message : String(err)}`
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
const session = new Session({
|
|
1477
|
+
channelId: "api",
|
|
1478
|
+
agentName,
|
|
1479
|
+
workingDirectory: cwd,
|
|
1480
|
+
agentInstance
|
|
1481
|
+
});
|
|
1482
|
+
session.agentSessionId = agentInstance.sessionId;
|
|
1483
|
+
this.sessionManager.registerSession(session);
|
|
1484
|
+
const firstEntry = this.adapters.entries().next().value;
|
|
1485
|
+
if (!firstEntry) {
|
|
1486
|
+
await session.destroy();
|
|
1487
|
+
return { ok: false, error: "no_adapter", message: "No channel adapter registered" };
|
|
1488
|
+
}
|
|
1489
|
+
const [adapterChannelId, adapter] = firstEntry;
|
|
1490
|
+
const threadId = await adapter.createSessionThread(session.id, session.name ?? "Adopted session");
|
|
1491
|
+
session.channelId = adapterChannelId;
|
|
1492
|
+
session.threadId = threadId;
|
|
1493
|
+
this.wireSessionEvents(session, adapter);
|
|
1494
|
+
if (this.sessionStore) {
|
|
1495
|
+
await this.sessionStore.save({
|
|
1496
|
+
sessionId: session.id,
|
|
1497
|
+
agentSessionId: agentInstance.sessionId,
|
|
1498
|
+
originalAgentSessionId: agentSessionId,
|
|
1499
|
+
agentName,
|
|
1500
|
+
workingDir: cwd,
|
|
1501
|
+
channelId: adapterChannelId,
|
|
1502
|
+
status: "active",
|
|
1503
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1504
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1505
|
+
name: session.name,
|
|
1506
|
+
platform: { topicId: Number(threadId) }
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
return {
|
|
1510
|
+
ok: true,
|
|
1511
|
+
sessionId: session.id,
|
|
1512
|
+
threadId,
|
|
1513
|
+
status: "adopted"
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1395
1516
|
async handleNewChat(channelId, currentThreadId) {
|
|
1396
1517
|
const currentSession = this.sessionManager.getSessionByThread(
|
|
1397
1518
|
channelId,
|
|
@@ -1548,6 +1669,8 @@ var ChannelAdapter = class {
|
|
|
1548
1669
|
this.core = core;
|
|
1549
1670
|
this.config = config;
|
|
1550
1671
|
}
|
|
1672
|
+
async deleteSessionThread(_sessionId) {
|
|
1673
|
+
}
|
|
1551
1674
|
// Skill commands — override in adapters that support dynamic commands
|
|
1552
1675
|
async sendSkillCommands(_sessionId, _commands) {
|
|
1553
1676
|
}
|
|
@@ -1560,25 +1683,56 @@ import * as http from "http";
|
|
|
1560
1683
|
import * as fs3 from "fs";
|
|
1561
1684
|
import * as path4 from "path";
|
|
1562
1685
|
import * as os2 from "os";
|
|
1686
|
+
import { fileURLToPath } from "url";
|
|
1563
1687
|
var log5 = createChildLogger({ module: "api-server" });
|
|
1564
1688
|
var DEFAULT_PORT_FILE = path4.join(os2.homedir(), ".openacp", "api.port");
|
|
1689
|
+
var cachedVersion;
|
|
1690
|
+
function getVersion() {
|
|
1691
|
+
if (cachedVersion) return cachedVersion;
|
|
1692
|
+
try {
|
|
1693
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
1694
|
+
const pkgPath = path4.resolve(path4.dirname(__filename), "../../package.json");
|
|
1695
|
+
const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
|
|
1696
|
+
cachedVersion = pkg.version ?? "0.0.0-dev";
|
|
1697
|
+
} catch {
|
|
1698
|
+
cachedVersion = "0.0.0-dev";
|
|
1699
|
+
}
|
|
1700
|
+
return cachedVersion;
|
|
1701
|
+
}
|
|
1702
|
+
var SENSITIVE_KEYS = ["botToken", "token", "apiKey", "secret", "password", "webhookSecret"];
|
|
1703
|
+
function redactConfig(config) {
|
|
1704
|
+
const redacted = structuredClone(config);
|
|
1705
|
+
redactDeep(redacted);
|
|
1706
|
+
return redacted;
|
|
1707
|
+
}
|
|
1708
|
+
function redactDeep(obj) {
|
|
1709
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1710
|
+
if (SENSITIVE_KEYS.includes(key) && typeof value === "string") {
|
|
1711
|
+
obj[key] = "***";
|
|
1712
|
+
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
1713
|
+
redactDeep(value);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1565
1717
|
var ApiServer = class {
|
|
1566
|
-
constructor(core, config, portFilePath) {
|
|
1718
|
+
constructor(core, config, portFilePath, topicManager) {
|
|
1567
1719
|
this.core = core;
|
|
1568
1720
|
this.config = config;
|
|
1721
|
+
this.topicManager = topicManager;
|
|
1569
1722
|
this.portFilePath = portFilePath ?? DEFAULT_PORT_FILE;
|
|
1570
1723
|
}
|
|
1571
1724
|
server = null;
|
|
1572
1725
|
actualPort = 0;
|
|
1573
1726
|
portFilePath;
|
|
1727
|
+
startedAt = Date.now();
|
|
1574
1728
|
async start() {
|
|
1575
1729
|
this.server = http.createServer((req, res) => this.handleRequest(req, res));
|
|
1576
|
-
await new Promise((
|
|
1730
|
+
await new Promise((resolve2, reject) => {
|
|
1577
1731
|
this.server.on("error", (err) => {
|
|
1578
1732
|
if (err.code === "EADDRINUSE") {
|
|
1579
1733
|
log5.warn({ port: this.config.port }, "API port in use, continuing without API server");
|
|
1580
1734
|
this.server = null;
|
|
1581
|
-
|
|
1735
|
+
resolve2();
|
|
1582
1736
|
} else {
|
|
1583
1737
|
reject(err);
|
|
1584
1738
|
}
|
|
@@ -1590,15 +1744,15 @@ var ApiServer = class {
|
|
|
1590
1744
|
}
|
|
1591
1745
|
this.writePortFile();
|
|
1592
1746
|
log5.info({ host: this.config.host, port: this.actualPort }, "API server listening");
|
|
1593
|
-
|
|
1747
|
+
resolve2();
|
|
1594
1748
|
});
|
|
1595
1749
|
});
|
|
1596
1750
|
}
|
|
1597
1751
|
async stop() {
|
|
1598
1752
|
this.removePortFile();
|
|
1599
1753
|
if (this.server) {
|
|
1600
|
-
await new Promise((
|
|
1601
|
-
this.server.close(() =>
|
|
1754
|
+
await new Promise((resolve2) => {
|
|
1755
|
+
this.server.close(() => resolve2());
|
|
1602
1756
|
});
|
|
1603
1757
|
this.server = null;
|
|
1604
1758
|
}
|
|
@@ -1621,15 +1775,49 @@ var ApiServer = class {
|
|
|
1621
1775
|
const method = req.method?.toUpperCase();
|
|
1622
1776
|
const url = req.url || "";
|
|
1623
1777
|
try {
|
|
1624
|
-
if (method === "POST" && url === "/api/sessions") {
|
|
1778
|
+
if (method === "POST" && url === "/api/sessions/adopt") {
|
|
1779
|
+
await this.handleAdoptSession(req, res);
|
|
1780
|
+
} else if (method === "POST" && url === "/api/sessions") {
|
|
1625
1781
|
await this.handleCreateSession(req, res);
|
|
1626
|
-
} else if (method === "
|
|
1627
|
-
const sessionId = url.match(/^\/api\/sessions\/(
|
|
1782
|
+
} else if (method === "POST" && url.match(/^\/api\/sessions\/([^/]+)\/prompt$/)) {
|
|
1783
|
+
const sessionId = decodeURIComponent(url.match(/^\/api\/sessions\/([^/]+)\/prompt$/)[1]);
|
|
1784
|
+
await this.handleSendPrompt(sessionId, req, res);
|
|
1785
|
+
} else if (method === "PATCH" && url.match(/^\/api\/sessions\/([^/]+)\/dangerous$/)) {
|
|
1786
|
+
const sessionId = decodeURIComponent(url.match(/^\/api\/sessions\/([^/]+)\/dangerous$/)[1]);
|
|
1787
|
+
await this.handleToggleDangerous(sessionId, req, res);
|
|
1788
|
+
} else if (method === "GET" && url.match(/^\/api\/sessions\/([^/]+)$/)) {
|
|
1789
|
+
const sessionId = decodeURIComponent(url.match(/^\/api\/sessions\/([^/]+)$/)[1]);
|
|
1790
|
+
await this.handleGetSession(sessionId, res);
|
|
1791
|
+
} else if (method === "DELETE" && url.match(/^\/api\/sessions\/([^/]+)$/)) {
|
|
1792
|
+
const sessionId = decodeURIComponent(url.match(/^\/api\/sessions\/([^/]+)$/)[1]);
|
|
1628
1793
|
await this.handleCancelSession(sessionId, res);
|
|
1629
1794
|
} else if (method === "GET" && url === "/api/sessions") {
|
|
1630
1795
|
await this.handleListSessions(res);
|
|
1631
1796
|
} else if (method === "GET" && url === "/api/agents") {
|
|
1632
1797
|
await this.handleListAgents(res);
|
|
1798
|
+
} else if (method === "GET" && url === "/api/health") {
|
|
1799
|
+
await this.handleHealth(res);
|
|
1800
|
+
} else if (method === "GET" && url === "/api/version") {
|
|
1801
|
+
await this.handleVersion(res);
|
|
1802
|
+
} else if (method === "GET" && url === "/api/config") {
|
|
1803
|
+
await this.handleGetConfig(res);
|
|
1804
|
+
} else if (method === "PATCH" && url === "/api/config") {
|
|
1805
|
+
await this.handleUpdateConfig(req, res);
|
|
1806
|
+
} else if (method === "GET" && url === "/api/adapters") {
|
|
1807
|
+
await this.handleListAdapters(res);
|
|
1808
|
+
} else if (method === "GET" && url === "/api/tunnel") {
|
|
1809
|
+
await this.handleTunnelStatus(res);
|
|
1810
|
+
} else if (method === "POST" && url === "/api/notify") {
|
|
1811
|
+
await this.handleNotify(req, res);
|
|
1812
|
+
} else if (method === "POST" && url === "/api/restart") {
|
|
1813
|
+
await this.handleRestart(res);
|
|
1814
|
+
} else if (method === "GET" && url.match(/^\/api\/topics(\?.*)?$/)) {
|
|
1815
|
+
await this.handleListTopics(url, res);
|
|
1816
|
+
} else if (method === "POST" && url === "/api/topics/cleanup") {
|
|
1817
|
+
await this.handleCleanupTopics(req, res);
|
|
1818
|
+
} else if (method === "DELETE" && url.match(/^\/api\/topics\/([^/?]+)/)) {
|
|
1819
|
+
const match = url.match(/^\/api\/topics\/([^/?]+)/);
|
|
1820
|
+
await this.handleDeleteTopic(decodeURIComponent(match[1]), url, res);
|
|
1633
1821
|
} else {
|
|
1634
1822
|
this.sendJson(res, 404, { error: "Not found" });
|
|
1635
1823
|
}
|
|
@@ -1687,6 +1875,222 @@ var ApiServer = class {
|
|
|
1687
1875
|
workspace: session.workingDirectory
|
|
1688
1876
|
});
|
|
1689
1877
|
}
|
|
1878
|
+
async handleSendPrompt(sessionId, req, res) {
|
|
1879
|
+
const session = this.core.sessionManager.getSession(sessionId);
|
|
1880
|
+
if (!session) {
|
|
1881
|
+
this.sendJson(res, 404, { error: `Session "${sessionId}" not found` });
|
|
1882
|
+
return;
|
|
1883
|
+
}
|
|
1884
|
+
if (session.status === "cancelled" || session.status === "finished" || session.status === "error") {
|
|
1885
|
+
this.sendJson(res, 400, { error: `Session is ${session.status}` });
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1888
|
+
const body = await this.readBody(req);
|
|
1889
|
+
let prompt;
|
|
1890
|
+
if (body) {
|
|
1891
|
+
try {
|
|
1892
|
+
const parsed = JSON.parse(body);
|
|
1893
|
+
prompt = parsed.prompt;
|
|
1894
|
+
} catch {
|
|
1895
|
+
this.sendJson(res, 400, { error: "Invalid JSON body" });
|
|
1896
|
+
return;
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
if (!prompt) {
|
|
1900
|
+
this.sendJson(res, 400, { error: "Missing prompt" });
|
|
1901
|
+
return;
|
|
1902
|
+
}
|
|
1903
|
+
session.enqueuePrompt(prompt).catch(() => {
|
|
1904
|
+
});
|
|
1905
|
+
this.sendJson(res, 200, { ok: true, sessionId, queueDepth: session.queueDepth });
|
|
1906
|
+
}
|
|
1907
|
+
async handleGetSession(sessionId, res) {
|
|
1908
|
+
const session = this.core.sessionManager.getSession(sessionId);
|
|
1909
|
+
if (!session) {
|
|
1910
|
+
this.sendJson(res, 404, { error: `Session "${sessionId}" not found` });
|
|
1911
|
+
return;
|
|
1912
|
+
}
|
|
1913
|
+
this.sendJson(res, 200, {
|
|
1914
|
+
session: {
|
|
1915
|
+
id: session.id,
|
|
1916
|
+
agent: session.agentName,
|
|
1917
|
+
status: session.status,
|
|
1918
|
+
name: session.name ?? null,
|
|
1919
|
+
workspace: session.workingDirectory,
|
|
1920
|
+
createdAt: session.createdAt.toISOString(),
|
|
1921
|
+
dangerousMode: session.dangerousMode,
|
|
1922
|
+
queueDepth: session.queueDepth,
|
|
1923
|
+
promptRunning: session.promptRunning,
|
|
1924
|
+
threadId: session.threadId,
|
|
1925
|
+
channelId: session.channelId,
|
|
1926
|
+
agentSessionId: session.agentSessionId
|
|
1927
|
+
}
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
async handleToggleDangerous(sessionId, req, res) {
|
|
1931
|
+
const session = this.core.sessionManager.getSession(sessionId);
|
|
1932
|
+
if (!session) {
|
|
1933
|
+
this.sendJson(res, 404, { error: `Session "${sessionId}" not found` });
|
|
1934
|
+
return;
|
|
1935
|
+
}
|
|
1936
|
+
const body = await this.readBody(req);
|
|
1937
|
+
let enabled;
|
|
1938
|
+
if (body) {
|
|
1939
|
+
try {
|
|
1940
|
+
const parsed = JSON.parse(body);
|
|
1941
|
+
enabled = parsed.enabled;
|
|
1942
|
+
} catch {
|
|
1943
|
+
this.sendJson(res, 400, { error: "Invalid JSON body" });
|
|
1944
|
+
return;
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
if (typeof enabled !== "boolean") {
|
|
1948
|
+
this.sendJson(res, 400, { error: "Missing enabled boolean" });
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
session.dangerousMode = enabled;
|
|
1952
|
+
await this.core.sessionManager.updateSessionDangerousMode(sessionId, enabled);
|
|
1953
|
+
this.sendJson(res, 200, { ok: true, dangerousMode: enabled });
|
|
1954
|
+
}
|
|
1955
|
+
async handleHealth(res) {
|
|
1956
|
+
const activeSessions = this.core.sessionManager.listSessions();
|
|
1957
|
+
const allRecords = this.core.sessionManager.listRecords();
|
|
1958
|
+
const mem = process.memoryUsage();
|
|
1959
|
+
const tunnel = this.core.tunnelService;
|
|
1960
|
+
this.sendJson(res, 200, {
|
|
1961
|
+
status: "ok",
|
|
1962
|
+
uptime: Date.now() - this.startedAt,
|
|
1963
|
+
version: getVersion(),
|
|
1964
|
+
memory: {
|
|
1965
|
+
rss: mem.rss,
|
|
1966
|
+
heapUsed: mem.heapUsed,
|
|
1967
|
+
heapTotal: mem.heapTotal
|
|
1968
|
+
},
|
|
1969
|
+
sessions: {
|
|
1970
|
+
active: activeSessions.filter((s) => s.status === "active" || s.status === "initializing").length,
|
|
1971
|
+
total: allRecords.length
|
|
1972
|
+
},
|
|
1973
|
+
adapters: Array.from(this.core.adapters.keys()),
|
|
1974
|
+
tunnel: tunnel ? { enabled: true, url: tunnel.getPublicUrl() } : { enabled: false }
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
async handleVersion(res) {
|
|
1978
|
+
this.sendJson(res, 200, { version: getVersion() });
|
|
1979
|
+
}
|
|
1980
|
+
async handleGetConfig(res) {
|
|
1981
|
+
const config = this.core.configManager.get();
|
|
1982
|
+
this.sendJson(res, 200, { config: redactConfig(config) });
|
|
1983
|
+
}
|
|
1984
|
+
async handleUpdateConfig(req, res) {
|
|
1985
|
+
const body = await this.readBody(req);
|
|
1986
|
+
let configPath;
|
|
1987
|
+
let value;
|
|
1988
|
+
if (body) {
|
|
1989
|
+
try {
|
|
1990
|
+
const parsed = JSON.parse(body);
|
|
1991
|
+
configPath = parsed.path;
|
|
1992
|
+
value = parsed.value;
|
|
1993
|
+
} catch {
|
|
1994
|
+
this.sendJson(res, 400, { error: "Invalid JSON body" });
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
if (!configPath) {
|
|
1999
|
+
this.sendJson(res, 400, { error: "Missing path" });
|
|
2000
|
+
return;
|
|
2001
|
+
}
|
|
2002
|
+
const currentConfig = this.core.configManager.get();
|
|
2003
|
+
const cloned = structuredClone(currentConfig);
|
|
2004
|
+
const parts = configPath.split(".");
|
|
2005
|
+
let target = cloned;
|
|
2006
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
2007
|
+
if (target[parts[i]] && typeof target[parts[i]] === "object" && !Array.isArray(target[parts[i]])) {
|
|
2008
|
+
target = target[parts[i]];
|
|
2009
|
+
} else {
|
|
2010
|
+
this.sendJson(res, 400, { error: "Invalid config path" });
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
const lastKey = parts[parts.length - 1];
|
|
2015
|
+
if (!(lastKey in target)) {
|
|
2016
|
+
this.sendJson(res, 400, { error: "Invalid config path" });
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
target[lastKey] = value;
|
|
2020
|
+
const { ConfigSchema } = await import("./config-H2DSEHNW.js");
|
|
2021
|
+
const result = ConfigSchema.safeParse(cloned);
|
|
2022
|
+
if (!result.success) {
|
|
2023
|
+
this.sendJson(res, 400, {
|
|
2024
|
+
error: "Validation failed",
|
|
2025
|
+
details: result.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
|
|
2026
|
+
});
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
2029
|
+
const updates = {};
|
|
2030
|
+
let updateTarget = updates;
|
|
2031
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
2032
|
+
updateTarget[parts[i]] = {};
|
|
2033
|
+
updateTarget = updateTarget[parts[i]];
|
|
2034
|
+
}
|
|
2035
|
+
updateTarget[lastKey] = value;
|
|
2036
|
+
await this.core.configManager.save(updates);
|
|
2037
|
+
const RESTART_PREFIXES = ["api.port", "api.host", "runMode", "channels.", "tunnel.", "agents."];
|
|
2038
|
+
const needsRestart = RESTART_PREFIXES.some(
|
|
2039
|
+
(prefix) => configPath.startsWith(prefix) || configPath === prefix.replace(/\.$/, "")
|
|
2040
|
+
// exact match for non-wildcard
|
|
2041
|
+
);
|
|
2042
|
+
this.sendJson(res, 200, {
|
|
2043
|
+
ok: true,
|
|
2044
|
+
needsRestart,
|
|
2045
|
+
config: redactConfig(this.core.configManager.get())
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
async handleListAdapters(res) {
|
|
2049
|
+
const adapters = Array.from(this.core.adapters.entries()).map(([name]) => ({
|
|
2050
|
+
name,
|
|
2051
|
+
type: "built-in"
|
|
2052
|
+
}));
|
|
2053
|
+
this.sendJson(res, 200, { adapters });
|
|
2054
|
+
}
|
|
2055
|
+
async handleTunnelStatus(res) {
|
|
2056
|
+
const tunnel = this.core.tunnelService;
|
|
2057
|
+
if (tunnel) {
|
|
2058
|
+
this.sendJson(res, 200, { enabled: true, url: tunnel.getPublicUrl(), provider: this.core.configManager.get().tunnel.provider });
|
|
2059
|
+
} else {
|
|
2060
|
+
this.sendJson(res, 200, { enabled: false });
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
async handleNotify(req, res) {
|
|
2064
|
+
const body = await this.readBody(req);
|
|
2065
|
+
let message;
|
|
2066
|
+
if (body) {
|
|
2067
|
+
try {
|
|
2068
|
+
const parsed = JSON.parse(body);
|
|
2069
|
+
message = parsed.message;
|
|
2070
|
+
} catch {
|
|
2071
|
+
this.sendJson(res, 400, { error: "Invalid JSON body" });
|
|
2072
|
+
return;
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
if (!message) {
|
|
2076
|
+
this.sendJson(res, 400, { error: "Missing message" });
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
await this.core.notificationManager.notifyAll({
|
|
2080
|
+
sessionId: "system",
|
|
2081
|
+
type: "completed",
|
|
2082
|
+
summary: message
|
|
2083
|
+
});
|
|
2084
|
+
this.sendJson(res, 200, { ok: true });
|
|
2085
|
+
}
|
|
2086
|
+
async handleRestart(res) {
|
|
2087
|
+
if (!this.core.requestRestart) {
|
|
2088
|
+
this.sendJson(res, 501, { error: "Restart not available" });
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2091
|
+
this.sendJson(res, 200, { ok: true, message: "Restarting..." });
|
|
2092
|
+
setImmediate(() => this.core.requestRestart());
|
|
2093
|
+
}
|
|
1690
2094
|
async handleCancelSession(sessionId, res) {
|
|
1691
2095
|
const session = this.core.sessionManager.getSession(sessionId);
|
|
1692
2096
|
if (!session) {
|
|
@@ -1708,34 +2112,180 @@ var ApiServer = class {
|
|
|
1708
2112
|
}))
|
|
1709
2113
|
});
|
|
1710
2114
|
}
|
|
2115
|
+
async handleAdoptSession(req, res) {
|
|
2116
|
+
const body = await this.readBody(req);
|
|
2117
|
+
if (!body) {
|
|
2118
|
+
return this.sendJson(res, 400, { error: "bad_request", message: "Empty request body" });
|
|
2119
|
+
}
|
|
2120
|
+
let parsed;
|
|
2121
|
+
try {
|
|
2122
|
+
parsed = JSON.parse(body);
|
|
2123
|
+
} catch {
|
|
2124
|
+
return this.sendJson(res, 400, { error: "bad_request", message: "Invalid JSON" });
|
|
2125
|
+
}
|
|
2126
|
+
const { agent, agentSessionId, cwd } = parsed;
|
|
2127
|
+
if (!agent || !agentSessionId) {
|
|
2128
|
+
return this.sendJson(res, 400, { error: "bad_request", message: "Missing required fields: agent, agentSessionId" });
|
|
2129
|
+
}
|
|
2130
|
+
const result = await this.core.adoptSession(agent, agentSessionId, cwd ?? process.cwd());
|
|
2131
|
+
if (result.ok) {
|
|
2132
|
+
return this.sendJson(res, 200, result);
|
|
2133
|
+
} else {
|
|
2134
|
+
const status = result.error === "session_limit" ? 429 : result.error === "agent_not_supported" ? 400 : 500;
|
|
2135
|
+
return this.sendJson(res, status, result);
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
1711
2138
|
async handleListAgents(res) {
|
|
1712
2139
|
const agents = this.core.agentManager.getAvailableAgents();
|
|
1713
2140
|
const defaultAgent = this.core.configManager.get().defaultAgent;
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
})),
|
|
1720
|
-
default: defaultAgent
|
|
1721
|
-
});
|
|
2141
|
+
const agentsWithCaps = agents.map((a) => ({
|
|
2142
|
+
...a,
|
|
2143
|
+
capabilities: getAgentCapabilities(a.name)
|
|
2144
|
+
}));
|
|
2145
|
+
this.sendJson(res, 200, { agents: agentsWithCaps, default: defaultAgent });
|
|
1722
2146
|
}
|
|
1723
2147
|
sendJson(res, status, data) {
|
|
1724
2148
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
1725
2149
|
res.end(JSON.stringify(data));
|
|
1726
2150
|
}
|
|
2151
|
+
async handleListTopics(url, res) {
|
|
2152
|
+
if (!this.topicManager) {
|
|
2153
|
+
this.sendJson(res, 501, { error: "Topic management not available" });
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2156
|
+
const params = new URL(url, "http://localhost").searchParams;
|
|
2157
|
+
const statusParam = params.get("status");
|
|
2158
|
+
const filter = statusParam ? { statuses: statusParam.split(",") } : void 0;
|
|
2159
|
+
const topics = this.topicManager.listTopics(filter);
|
|
2160
|
+
this.sendJson(res, 200, { topics });
|
|
2161
|
+
}
|
|
2162
|
+
async handleDeleteTopic(sessionId, url, res) {
|
|
2163
|
+
if (!this.topicManager) {
|
|
2164
|
+
this.sendJson(res, 501, { error: "Topic management not available" });
|
|
2165
|
+
return;
|
|
2166
|
+
}
|
|
2167
|
+
const params = new URL(url, "http://localhost").searchParams;
|
|
2168
|
+
const force = params.get("force") === "true";
|
|
2169
|
+
const result = await this.topicManager.deleteTopic(sessionId, force ? { confirmed: true } : void 0);
|
|
2170
|
+
if (result.ok) {
|
|
2171
|
+
this.sendJson(res, 200, result);
|
|
2172
|
+
} else if (result.needsConfirmation) {
|
|
2173
|
+
this.sendJson(res, 409, { error: "Session is active", needsConfirmation: true, session: result.session });
|
|
2174
|
+
} else if (result.error === "Cannot delete system topic") {
|
|
2175
|
+
this.sendJson(res, 403, { error: result.error });
|
|
2176
|
+
} else {
|
|
2177
|
+
this.sendJson(res, 404, { error: result.error ?? "Not found" });
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
async handleCleanupTopics(req, res) {
|
|
2181
|
+
if (!this.topicManager) {
|
|
2182
|
+
this.sendJson(res, 501, { error: "Topic management not available" });
|
|
2183
|
+
return;
|
|
2184
|
+
}
|
|
2185
|
+
const body = await this.readBody(req);
|
|
2186
|
+
let statuses;
|
|
2187
|
+
if (body) {
|
|
2188
|
+
try {
|
|
2189
|
+
statuses = JSON.parse(body).statuses;
|
|
2190
|
+
} catch {
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
const result = await this.topicManager.cleanup(statuses);
|
|
2194
|
+
this.sendJson(res, 200, result);
|
|
2195
|
+
}
|
|
1727
2196
|
readBody(req) {
|
|
1728
|
-
return new Promise((
|
|
2197
|
+
return new Promise((resolve2) => {
|
|
1729
2198
|
let data = "";
|
|
1730
2199
|
req.on("data", (chunk) => {
|
|
1731
2200
|
data += chunk;
|
|
1732
2201
|
});
|
|
1733
|
-
req.on("end", () =>
|
|
1734
|
-
req.on("error", () =>
|
|
2202
|
+
req.on("end", () => resolve2(data));
|
|
2203
|
+
req.on("error", () => resolve2(""));
|
|
1735
2204
|
});
|
|
1736
2205
|
}
|
|
1737
2206
|
};
|
|
1738
2207
|
|
|
2208
|
+
// src/core/topic-manager.ts
|
|
2209
|
+
var log6 = createChildLogger({ module: "topic-manager" });
|
|
2210
|
+
var TopicManager = class {
|
|
2211
|
+
constructor(sessionManager, adapter, systemTopicIds) {
|
|
2212
|
+
this.sessionManager = sessionManager;
|
|
2213
|
+
this.adapter = adapter;
|
|
2214
|
+
this.systemTopicIds = systemTopicIds;
|
|
2215
|
+
}
|
|
2216
|
+
listTopics(filter) {
|
|
2217
|
+
const records = this.sessionManager.listRecords(filter);
|
|
2218
|
+
return records.filter((r) => !this.isSystemTopic(r)).filter((r) => !filter?.statuses?.length || filter.statuses.includes(r.status)).map((r) => ({
|
|
2219
|
+
sessionId: r.sessionId,
|
|
2220
|
+
topicId: r.platform?.topicId ?? null,
|
|
2221
|
+
name: r.name ?? null,
|
|
2222
|
+
status: r.status,
|
|
2223
|
+
agentName: r.agentName,
|
|
2224
|
+
lastActiveAt: r.lastActiveAt
|
|
2225
|
+
}));
|
|
2226
|
+
}
|
|
2227
|
+
async deleteTopic(sessionId, options) {
|
|
2228
|
+
const records = this.sessionManager.listRecords();
|
|
2229
|
+
const record = records.find((r) => r.sessionId === sessionId);
|
|
2230
|
+
if (!record) return { ok: false, error: "Session not found" };
|
|
2231
|
+
if (this.isSystemTopic(record)) return { ok: false, error: "Cannot delete system topic" };
|
|
2232
|
+
const isActive = record.status === "active" || record.status === "initializing";
|
|
2233
|
+
if (isActive && !options?.confirmed) {
|
|
2234
|
+
return {
|
|
2235
|
+
ok: false,
|
|
2236
|
+
needsConfirmation: true,
|
|
2237
|
+
session: { id: record.sessionId, name: record.name ?? null, status: record.status }
|
|
2238
|
+
};
|
|
2239
|
+
}
|
|
2240
|
+
if (isActive) {
|
|
2241
|
+
await this.sessionManager.cancelSession(sessionId);
|
|
2242
|
+
}
|
|
2243
|
+
const topicId = record.platform?.topicId ?? null;
|
|
2244
|
+
if (this.adapter && topicId) {
|
|
2245
|
+
try {
|
|
2246
|
+
await this.adapter.deleteSessionThread(sessionId);
|
|
2247
|
+
} catch (err) {
|
|
2248
|
+
log6.warn({ err, sessionId, topicId }, "Failed to delete platform thread, removing record anyway");
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
await this.sessionManager.removeRecord(sessionId);
|
|
2252
|
+
return { ok: true, topicId };
|
|
2253
|
+
}
|
|
2254
|
+
async cleanup(statuses) {
|
|
2255
|
+
const targetStatuses = statuses?.length ? statuses : ["finished", "error", "cancelled"];
|
|
2256
|
+
const records = this.sessionManager.listRecords({ statuses: targetStatuses });
|
|
2257
|
+
const targets = records.filter((r) => !this.isSystemTopic(r)).filter((r) => targetStatuses.includes(r.status));
|
|
2258
|
+
const deleted = [];
|
|
2259
|
+
const failed = [];
|
|
2260
|
+
for (const record of targets) {
|
|
2261
|
+
try {
|
|
2262
|
+
const isActive = record.status === "active" || record.status === "initializing";
|
|
2263
|
+
if (isActive) {
|
|
2264
|
+
await this.sessionManager.cancelSession(record.sessionId);
|
|
2265
|
+
}
|
|
2266
|
+
const topicId = record.platform?.topicId;
|
|
2267
|
+
if (this.adapter && topicId) {
|
|
2268
|
+
try {
|
|
2269
|
+
await this.adapter.deleteSessionThread(record.sessionId);
|
|
2270
|
+
} catch (err) {
|
|
2271
|
+
log6.warn({ err, sessionId: record.sessionId }, "Failed to delete platform thread during cleanup");
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
await this.sessionManager.removeRecord(record.sessionId);
|
|
2275
|
+
deleted.push(record.sessionId);
|
|
2276
|
+
} catch (err) {
|
|
2277
|
+
failed.push({ sessionId: record.sessionId, error: err instanceof Error ? err.message : String(err) });
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
return { deleted, failed };
|
|
2281
|
+
}
|
|
2282
|
+
isSystemTopic(record) {
|
|
2283
|
+
const topicId = record.platform?.topicId;
|
|
2284
|
+
if (!topicId) return false;
|
|
2285
|
+
return topicId === this.systemTopicIds.notificationTopicId || topicId === this.systemTopicIds.assistantTopicId;
|
|
2286
|
+
}
|
|
2287
|
+
};
|
|
2288
|
+
|
|
1739
2289
|
// src/adapters/telegram/adapter.ts
|
|
1740
2290
|
import { Bot } from "grammy";
|
|
1741
2291
|
|
|
@@ -1869,7 +2419,7 @@ function formatUsage(usage) {
|
|
|
1869
2419
|
return `${emoji} ${formatTokens(tokensUsed)} / ${formatTokens(contextSize)} tokens
|
|
1870
2420
|
${bar} ${pct}%`;
|
|
1871
2421
|
}
|
|
1872
|
-
function splitMessage(text, maxLength =
|
|
2422
|
+
function splitMessage(text, maxLength = 3800) {
|
|
1873
2423
|
if (text.length <= maxLength) return [text];
|
|
1874
2424
|
const chunks = [];
|
|
1875
2425
|
let remaining = text;
|
|
@@ -1879,14 +2429,23 @@ function splitMessage(text, maxLength = 4096) {
|
|
|
1879
2429
|
break;
|
|
1880
2430
|
}
|
|
1881
2431
|
let splitAt = remaining.lastIndexOf("\n\n", maxLength);
|
|
1882
|
-
if (splitAt === -1 || splitAt < maxLength * 0.
|
|
2432
|
+
if (splitAt === -1 || splitAt < maxLength * 0.2) {
|
|
1883
2433
|
splitAt = remaining.lastIndexOf("\n", maxLength);
|
|
1884
2434
|
}
|
|
1885
|
-
if (splitAt === -1 || splitAt < maxLength * 0.
|
|
2435
|
+
if (splitAt === -1 || splitAt < maxLength * 0.2) {
|
|
1886
2436
|
splitAt = maxLength;
|
|
1887
2437
|
}
|
|
2438
|
+
const candidate = remaining.slice(0, splitAt);
|
|
2439
|
+
const fences = candidate.match(/```/g);
|
|
2440
|
+
if (fences && fences.length % 2 !== 0) {
|
|
2441
|
+
const closingFence = remaining.indexOf("```", splitAt);
|
|
2442
|
+
if (closingFence !== -1) {
|
|
2443
|
+
const afterFence = remaining.indexOf("\n", closingFence + 3);
|
|
2444
|
+
splitAt = afterFence !== -1 ? afterFence + 1 : closingFence + 3;
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
1888
2447
|
chunks.push(remaining.slice(0, splitAt));
|
|
1889
|
-
remaining = remaining.slice(splitAt).
|
|
2448
|
+
remaining = remaining.slice(splitAt).replace(/^\n+/, "");
|
|
1890
2449
|
}
|
|
1891
2450
|
return chunks;
|
|
1892
2451
|
}
|
|
@@ -1923,14 +2482,22 @@ var MessageDraft = class {
|
|
|
1923
2482
|
async flush() {
|
|
1924
2483
|
if (!this.buffer) return;
|
|
1925
2484
|
if (this.firstFlushPending) return;
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
2485
|
+
let displayBuffer = this.buffer;
|
|
2486
|
+
if (displayBuffer.length > 3800) {
|
|
2487
|
+
let cutAt = displayBuffer.lastIndexOf("\n", 3800);
|
|
2488
|
+
if (cutAt < 800) cutAt = 3800;
|
|
2489
|
+
displayBuffer = displayBuffer.slice(0, cutAt) + "\n\u2026";
|
|
2490
|
+
}
|
|
2491
|
+
let html = markdownToTelegramHtml(displayBuffer);
|
|
2492
|
+
if (!html) return;
|
|
2493
|
+
if (html.length > 4096) {
|
|
2494
|
+
html = html.slice(0, 4090) + "\n\u2026";
|
|
2495
|
+
}
|
|
1929
2496
|
if (!this.messageId) {
|
|
1930
2497
|
this.firstFlushPending = true;
|
|
1931
2498
|
try {
|
|
1932
2499
|
const result = await this.sendQueue.enqueue(
|
|
1933
|
-
() => this.bot.api.sendMessage(this.chatId,
|
|
2500
|
+
() => this.bot.api.sendMessage(this.chatId, html, {
|
|
1934
2501
|
message_thread_id: this.threadId,
|
|
1935
2502
|
parse_mode: "HTML",
|
|
1936
2503
|
disable_notification: true
|
|
@@ -1948,7 +2515,7 @@ var MessageDraft = class {
|
|
|
1948
2515
|
} else {
|
|
1949
2516
|
try {
|
|
1950
2517
|
await this.sendQueue.enqueue(
|
|
1951
|
-
() => this.bot.api.editMessageText(this.chatId, this.messageId,
|
|
2518
|
+
() => this.bot.api.editMessageText(this.chatId, this.messageId, html, {
|
|
1952
2519
|
parse_mode: "HTML"
|
|
1953
2520
|
}),
|
|
1954
2521
|
{ type: "text", key: this.sessionId }
|
|
@@ -1968,21 +2535,20 @@ var MessageDraft = class {
|
|
|
1968
2535
|
if (this.messageId && this.buffer === this.lastSentBuffer) {
|
|
1969
2536
|
return this.messageId;
|
|
1970
2537
|
}
|
|
1971
|
-
const
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
const chunk = chunks[i];
|
|
2538
|
+
const mdChunks = splitMessage(this.buffer);
|
|
2539
|
+
for (let i = 0; i < mdChunks.length; i++) {
|
|
2540
|
+
const html = markdownToTelegramHtml(mdChunks[i]);
|
|
2541
|
+
try {
|
|
1976
2542
|
if (i === 0 && this.messageId) {
|
|
1977
2543
|
await this.sendQueue.enqueue(
|
|
1978
|
-
() => this.bot.api.editMessageText(this.chatId, this.messageId,
|
|
2544
|
+
() => this.bot.api.editMessageText(this.chatId, this.messageId, html, {
|
|
1979
2545
|
parse_mode: "HTML"
|
|
1980
2546
|
}),
|
|
1981
2547
|
{ type: "other" }
|
|
1982
2548
|
);
|
|
1983
2549
|
} else {
|
|
1984
2550
|
const msg = await this.sendQueue.enqueue(
|
|
1985
|
-
() => this.bot.api.sendMessage(this.chatId,
|
|
2551
|
+
() => this.bot.api.sendMessage(this.chatId, html, {
|
|
1986
2552
|
message_thread_id: this.threadId,
|
|
1987
2553
|
parse_mode: "HTML",
|
|
1988
2554
|
disable_notification: true
|
|
@@ -1993,17 +2559,25 @@ var MessageDraft = class {
|
|
|
1993
2559
|
this.messageId = msg.message_id;
|
|
1994
2560
|
}
|
|
1995
2561
|
}
|
|
1996
|
-
}
|
|
1997
|
-
} catch {
|
|
1998
|
-
if (this.buffer !== this.lastSentBuffer) {
|
|
2562
|
+
} catch {
|
|
1999
2563
|
try {
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2564
|
+
if (i === 0 && this.messageId) {
|
|
2565
|
+
await this.sendQueue.enqueue(
|
|
2566
|
+
() => this.bot.api.editMessageText(this.chatId, this.messageId, mdChunks[i].slice(0, 4096)),
|
|
2567
|
+
{ type: "other" }
|
|
2568
|
+
);
|
|
2569
|
+
} else {
|
|
2570
|
+
const msg = await this.sendQueue.enqueue(
|
|
2571
|
+
() => this.bot.api.sendMessage(this.chatId, mdChunks[i].slice(0, 4096), {
|
|
2572
|
+
message_thread_id: this.threadId,
|
|
2573
|
+
disable_notification: true
|
|
2574
|
+
}),
|
|
2575
|
+
{ type: "other" }
|
|
2576
|
+
);
|
|
2577
|
+
if (msg) {
|
|
2578
|
+
this.messageId = msg.message_id;
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2007
2581
|
} catch {
|
|
2008
2582
|
}
|
|
2009
2583
|
}
|
|
@@ -2048,12 +2622,13 @@ function buildDeepLink(chatId, messageId) {
|
|
|
2048
2622
|
|
|
2049
2623
|
// src/adapters/telegram/commands.ts
|
|
2050
2624
|
import { InlineKeyboard } from "grammy";
|
|
2051
|
-
var
|
|
2625
|
+
var log8 = createChildLogger({ module: "telegram-commands" });
|
|
2052
2626
|
function setupCommands(bot, core, chatId, assistant) {
|
|
2053
2627
|
bot.command("new", (ctx) => handleNew(ctx, core, chatId, assistant));
|
|
2054
2628
|
bot.command("newchat", (ctx) => handleNewChat(ctx, core, chatId));
|
|
2055
2629
|
bot.command("cancel", (ctx) => handleCancel(ctx, core, assistant));
|
|
2056
2630
|
bot.command("status", (ctx) => handleStatus(ctx, core));
|
|
2631
|
+
bot.command("sessions", (ctx) => handleTopics(ctx, core));
|
|
2057
2632
|
bot.command("agents", (ctx) => handleAgents(ctx, core));
|
|
2058
2633
|
bot.command("help", (ctx) => handleHelp(ctx));
|
|
2059
2634
|
bot.command("menu", (ctx) => handleMenu(ctx));
|
|
@@ -2061,11 +2636,12 @@ function setupCommands(bot, core, chatId, assistant) {
|
|
|
2061
2636
|
bot.command("disable_dangerous", (ctx) => handleDisableDangerous(ctx, core));
|
|
2062
2637
|
bot.command("restart", (ctx) => handleRestart(ctx, core));
|
|
2063
2638
|
bot.command("update", (ctx) => handleUpdate(ctx, core));
|
|
2639
|
+
bot.command("integrate", (ctx) => handleIntegrate(ctx, core));
|
|
2064
2640
|
}
|
|
2065
2641
|
function buildMenuKeyboard() {
|
|
2066
|
-
return new InlineKeyboard().text("\u{1F195} New Session", "m:new").text("\u{1F4AC} New Chat", "m:newchat").row().text("\u26D4 Cancel", "m:cancel").text("\u{1F4CA} Status", "m:status").row().text("\u{1F916} Agents", "m:agents").text("\u2753 Help", "m:help").row().text("\u{1F504} Restart", "m:restart").text("\u2B06\uFE0F Update", "m:update");
|
|
2642
|
+
return new InlineKeyboard().text("\u{1F195} New Session", "m:new").text("\u{1F4AC} New Chat", "m:newchat").row().text("\u26D4 Cancel", "m:cancel").text("\u{1F4CA} Status", "m:status").row().text("\u{1F4CB} Sessions", "m:topics").text("\u{1F916} Agents", "m:agents").row().text("\u{1F517} Integrate", "m:integrate").text("\u2753 Help", "m:help").row().text("\u{1F504} Restart", "m:restart").text("\u2B06\uFE0F Update", "m:update");
|
|
2067
2643
|
}
|
|
2068
|
-
function setupMenuCallbacks(bot, core, chatId) {
|
|
2644
|
+
function setupMenuCallbacks(bot, core, chatId, systemTopicIds) {
|
|
2069
2645
|
bot.callbackQuery(/^m:/, async (ctx) => {
|
|
2070
2646
|
const data = ctx.callbackQuery.data;
|
|
2071
2647
|
try {
|
|
@@ -2097,6 +2673,27 @@ function setupMenuCallbacks(bot, core, chatId) {
|
|
|
2097
2673
|
case "m:update":
|
|
2098
2674
|
await handleUpdate(ctx, core);
|
|
2099
2675
|
break;
|
|
2676
|
+
case "m:integrate":
|
|
2677
|
+
await handleIntegrate(ctx, core);
|
|
2678
|
+
break;
|
|
2679
|
+
case "m:topics":
|
|
2680
|
+
await handleTopics(ctx, core);
|
|
2681
|
+
break;
|
|
2682
|
+
case "m:cleanup:finished":
|
|
2683
|
+
await handleCleanup(ctx, core, chatId, ["finished"]);
|
|
2684
|
+
break;
|
|
2685
|
+
case "m:cleanup:errors":
|
|
2686
|
+
await handleCleanup(ctx, core, chatId, ["error", "cancelled"]);
|
|
2687
|
+
break;
|
|
2688
|
+
case "m:cleanup:all":
|
|
2689
|
+
await handleCleanup(ctx, core, chatId, ["finished", "error", "cancelled"]);
|
|
2690
|
+
break;
|
|
2691
|
+
case "m:cleanup:everything":
|
|
2692
|
+
await handleCleanupEverything(ctx, core, chatId, systemTopicIds);
|
|
2693
|
+
break;
|
|
2694
|
+
case "m:cleanup:everything:confirm":
|
|
2695
|
+
await handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicIds);
|
|
2696
|
+
break;
|
|
2100
2697
|
}
|
|
2101
2698
|
});
|
|
2102
2699
|
}
|
|
@@ -2122,7 +2719,7 @@ async function handleNew(ctx, core, chatId, assistant) {
|
|
|
2122
2719
|
return;
|
|
2123
2720
|
}
|
|
2124
2721
|
}
|
|
2125
|
-
|
|
2722
|
+
log8.info({ userId: ctx.from?.id, agentName }, "New session command");
|
|
2126
2723
|
let threadId;
|
|
2127
2724
|
try {
|
|
2128
2725
|
const topicName = `\u{1F504} New Session`;
|
|
@@ -2156,9 +2753,9 @@ async function handleNew(ctx, core, chatId, assistant) {
|
|
|
2156
2753
|
reply_markup: buildDangerousModeKeyboard(session.id, false)
|
|
2157
2754
|
}
|
|
2158
2755
|
);
|
|
2159
|
-
session.warmup().catch((err) =>
|
|
2756
|
+
session.warmup().catch((err) => log8.error({ err }, "Warm-up error"));
|
|
2160
2757
|
} catch (err) {
|
|
2161
|
-
|
|
2758
|
+
log8.error({ err }, "Session creation failed");
|
|
2162
2759
|
if (threadId) {
|
|
2163
2760
|
try {
|
|
2164
2761
|
await ctx.api.deleteForumTopic(chatId, threadId);
|
|
@@ -2235,7 +2832,7 @@ async function handleNewChat(ctx, core, chatId) {
|
|
|
2235
2832
|
reply_markup: buildDangerousModeKeyboard(session.id, false)
|
|
2236
2833
|
}
|
|
2237
2834
|
);
|
|
2238
|
-
session.warmup().catch((err) =>
|
|
2835
|
+
session.warmup().catch((err) => log8.error({ err }, "Warm-up error"));
|
|
2239
2836
|
} catch (err) {
|
|
2240
2837
|
if (newThreadId) {
|
|
2241
2838
|
try {
|
|
@@ -2264,14 +2861,14 @@ async function handleCancel(ctx, core, assistant) {
|
|
|
2264
2861
|
String(threadId)
|
|
2265
2862
|
);
|
|
2266
2863
|
if (session) {
|
|
2267
|
-
|
|
2864
|
+
log8.info({ sessionId: session.id }, "Cancel session command");
|
|
2268
2865
|
await session.cancel();
|
|
2269
2866
|
await ctx.reply("\u26D4 Session cancelled.", { parse_mode: "HTML" });
|
|
2270
2867
|
return;
|
|
2271
2868
|
}
|
|
2272
2869
|
const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
|
|
2273
2870
|
if (record && record.status !== "cancelled" && record.status !== "error") {
|
|
2274
|
-
|
|
2871
|
+
log8.info({ sessionId: record.sessionId }, "Cancel session command (from store)");
|
|
2275
2872
|
await core.sessionManager.cancelSession(record.sessionId);
|
|
2276
2873
|
await ctx.reply("\u26D4 Session cancelled.", { parse_mode: "HTML" });
|
|
2277
2874
|
}
|
|
@@ -2321,6 +2918,188 @@ Total sessions: ${sessions.length}`,
|
|
|
2321
2918
|
);
|
|
2322
2919
|
}
|
|
2323
2920
|
}
|
|
2921
|
+
async function handleTopics(ctx, core) {
|
|
2922
|
+
try {
|
|
2923
|
+
const allRecords = core.sessionManager.listRecords();
|
|
2924
|
+
const records = allRecords.filter((r) => {
|
|
2925
|
+
const platform = r.platform;
|
|
2926
|
+
return !!platform?.topicId;
|
|
2927
|
+
});
|
|
2928
|
+
const headlessCount = allRecords.length - records.length;
|
|
2929
|
+
if (records.length === 0) {
|
|
2930
|
+
const extra = headlessCount > 0 ? ` (${headlessCount} headless hidden)` : "";
|
|
2931
|
+
await ctx.reply(`No sessions with topics found.${extra}`, { parse_mode: "HTML" });
|
|
2932
|
+
return;
|
|
2933
|
+
}
|
|
2934
|
+
const statusEmoji = {
|
|
2935
|
+
active: "\u{1F7E2}",
|
|
2936
|
+
initializing: "\u{1F7E1}",
|
|
2937
|
+
finished: "\u2705",
|
|
2938
|
+
error: "\u274C",
|
|
2939
|
+
cancelled: "\u26D4"
|
|
2940
|
+
};
|
|
2941
|
+
const statusOrder = { active: 0, initializing: 1, error: 2, finished: 3, cancelled: 4 };
|
|
2942
|
+
records.sort((a, b) => (statusOrder[a.status] ?? 5) - (statusOrder[b.status] ?? 5));
|
|
2943
|
+
const MAX_DISPLAY = 30;
|
|
2944
|
+
const displayed = records.slice(0, MAX_DISPLAY);
|
|
2945
|
+
const lines = displayed.map((r) => {
|
|
2946
|
+
const emoji = statusEmoji[r.status] || "\u26AA";
|
|
2947
|
+
const name = r.name?.trim();
|
|
2948
|
+
const label = name ? escapeHtml(name) : `<i>${escapeHtml(r.agentName)} session</i>`;
|
|
2949
|
+
return `${emoji} ${label} <code>[${r.status}]</code>`;
|
|
2950
|
+
});
|
|
2951
|
+
const header = `<b>Sessions: ${records.length}</b>` + (headlessCount > 0 ? ` (${headlessCount} headless hidden)` : "");
|
|
2952
|
+
const truncated = records.length > MAX_DISPLAY ? `
|
|
2953
|
+
|
|
2954
|
+
<i>...and ${records.length - MAX_DISPLAY} more</i>` : "";
|
|
2955
|
+
const finishedCount = records.filter((r) => r.status === "finished").length;
|
|
2956
|
+
const errorCount = records.filter((r) => r.status === "error" || r.status === "cancelled").length;
|
|
2957
|
+
const activeCount = records.filter((r) => r.status === "active" || r.status === "initializing").length;
|
|
2958
|
+
const keyboard = new InlineKeyboard();
|
|
2959
|
+
if (finishedCount > 0) {
|
|
2960
|
+
keyboard.text(`Cleanup finished (${finishedCount})`, "m:cleanup:finished").row();
|
|
2961
|
+
}
|
|
2962
|
+
if (errorCount > 0) {
|
|
2963
|
+
keyboard.text(`Cleanup errors (${errorCount})`, "m:cleanup:errors").row();
|
|
2964
|
+
}
|
|
2965
|
+
if (finishedCount + errorCount > 0) {
|
|
2966
|
+
keyboard.text(`Cleanup all non-active (${finishedCount + errorCount})`, "m:cleanup:all").row();
|
|
2967
|
+
}
|
|
2968
|
+
keyboard.text(`\u26A0\uFE0F Cleanup ALL (${records.length})`, "m:cleanup:everything").row();
|
|
2969
|
+
keyboard.text("Refresh", "m:topics");
|
|
2970
|
+
await ctx.reply(
|
|
2971
|
+
`${header}
|
|
2972
|
+
|
|
2973
|
+
${lines.join("\n")}${truncated}`,
|
|
2974
|
+
{ parse_mode: "HTML", reply_markup: keyboard }
|
|
2975
|
+
);
|
|
2976
|
+
} catch (err) {
|
|
2977
|
+
log8.error({ err }, "handleTopics error");
|
|
2978
|
+
await ctx.reply("\u274C Failed to list sessions.", { parse_mode: "HTML" }).catch(() => {
|
|
2979
|
+
});
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
async function handleCleanup(ctx, core, chatId, statuses) {
|
|
2983
|
+
const allRecords = core.sessionManager.listRecords();
|
|
2984
|
+
const cleanable = allRecords.filter((r) => {
|
|
2985
|
+
const platform = r.platform;
|
|
2986
|
+
return !!platform?.topicId && statuses.includes(r.status);
|
|
2987
|
+
});
|
|
2988
|
+
if (cleanable.length === 0) {
|
|
2989
|
+
await ctx.reply("Nothing to clean up.", { parse_mode: "HTML" });
|
|
2990
|
+
return;
|
|
2991
|
+
}
|
|
2992
|
+
let deleted = 0;
|
|
2993
|
+
let failed = 0;
|
|
2994
|
+
for (const record of cleanable) {
|
|
2995
|
+
try {
|
|
2996
|
+
const topicId = record.platform?.topicId;
|
|
2997
|
+
if (topicId) {
|
|
2998
|
+
try {
|
|
2999
|
+
await ctx.api.deleteForumTopic(chatId, topicId);
|
|
3000
|
+
} catch (err) {
|
|
3001
|
+
log8.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
3004
|
+
await core.sessionManager.removeRecord(record.sessionId);
|
|
3005
|
+
deleted++;
|
|
3006
|
+
} catch (err) {
|
|
3007
|
+
log8.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
|
|
3008
|
+
failed++;
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
await ctx.reply(
|
|
3012
|
+
`\u{1F5D1} Cleaned up <b>${deleted}</b> sessions${failed > 0 ? ` (${failed} failed)` : ""}.`,
|
|
3013
|
+
{ parse_mode: "HTML" }
|
|
3014
|
+
);
|
|
3015
|
+
}
|
|
3016
|
+
async function handleCleanupEverything(ctx, core, chatId, systemTopicIds) {
|
|
3017
|
+
const allRecords = core.sessionManager.listRecords();
|
|
3018
|
+
const cleanable = allRecords.filter((r) => {
|
|
3019
|
+
const platform = r.platform;
|
|
3020
|
+
if (!platform?.topicId) return false;
|
|
3021
|
+
if (systemTopicIds && (platform.topicId === systemTopicIds.notificationTopicId || platform.topicId === systemTopicIds.assistantTopicId)) return false;
|
|
3022
|
+
return true;
|
|
3023
|
+
});
|
|
3024
|
+
if (cleanable.length === 0) {
|
|
3025
|
+
await ctx.reply("Nothing to clean up.", { parse_mode: "HTML" });
|
|
3026
|
+
return;
|
|
3027
|
+
}
|
|
3028
|
+
const statusCounts = /* @__PURE__ */ new Map();
|
|
3029
|
+
for (const r of cleanable) {
|
|
3030
|
+
statusCounts.set(r.status, (statusCounts.get(r.status) ?? 0) + 1);
|
|
3031
|
+
}
|
|
3032
|
+
const statusEmoji = {
|
|
3033
|
+
active: "\u{1F7E2}",
|
|
3034
|
+
initializing: "\u{1F7E1}",
|
|
3035
|
+
finished: "\u2705",
|
|
3036
|
+
error: "\u274C",
|
|
3037
|
+
cancelled: "\u26D4"
|
|
3038
|
+
};
|
|
3039
|
+
const breakdown = Array.from(statusCounts.entries()).map(([status, count]) => `${statusEmoji[status] ?? "\u26AA"} ${status}: ${count}`).join("\n");
|
|
3040
|
+
const activeCount = (statusCounts.get("active") ?? 0) + (statusCounts.get("initializing") ?? 0);
|
|
3041
|
+
const activeWarning = activeCount > 0 ? `
|
|
3042
|
+
|
|
3043
|
+
\u26A0\uFE0F <b>${activeCount} active session(s) will be cancelled and their agents stopped!</b>` : "";
|
|
3044
|
+
const keyboard = new InlineKeyboard().text("Yes, delete all", "m:cleanup:everything:confirm").text("Cancel", "m:topics");
|
|
3045
|
+
await ctx.reply(
|
|
3046
|
+
`<b>Delete ${cleanable.length} topics?</b>
|
|
3047
|
+
|
|
3048
|
+
This will:
|
|
3049
|
+
\u2022 Delete all session topics from this group
|
|
3050
|
+
\u2022 Cancel any running agent sessions
|
|
3051
|
+
\u2022 Remove all session records
|
|
3052
|
+
|
|
3053
|
+
<b>Breakdown:</b>
|
|
3054
|
+
${breakdown}${activeWarning}
|
|
3055
|
+
|
|
3056
|
+
<i>Notifications and Assistant topics will NOT be deleted.</i>`,
|
|
3057
|
+
{ parse_mode: "HTML", reply_markup: keyboard }
|
|
3058
|
+
);
|
|
3059
|
+
}
|
|
3060
|
+
async function handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicIds) {
|
|
3061
|
+
const allRecords = core.sessionManager.listRecords();
|
|
3062
|
+
const cleanable = allRecords.filter((r) => {
|
|
3063
|
+
const platform = r.platform;
|
|
3064
|
+
if (!platform?.topicId) return false;
|
|
3065
|
+
if (systemTopicIds && (platform.topicId === systemTopicIds.notificationTopicId || platform.topicId === systemTopicIds.assistantTopicId)) return false;
|
|
3066
|
+
return true;
|
|
3067
|
+
});
|
|
3068
|
+
if (cleanable.length === 0) {
|
|
3069
|
+
await ctx.reply("Nothing to clean up.", { parse_mode: "HTML" });
|
|
3070
|
+
return;
|
|
3071
|
+
}
|
|
3072
|
+
let deleted = 0;
|
|
3073
|
+
let failed = 0;
|
|
3074
|
+
for (const record of cleanable) {
|
|
3075
|
+
try {
|
|
3076
|
+
if (record.status === "active" || record.status === "initializing") {
|
|
3077
|
+
try {
|
|
3078
|
+
await core.sessionManager.cancelSession(record.sessionId);
|
|
3079
|
+
} catch (err) {
|
|
3080
|
+
log8.warn({ err, sessionId: record.sessionId }, "Failed to cancel session during cleanup");
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
const topicId = record.platform?.topicId;
|
|
3084
|
+
if (topicId) {
|
|
3085
|
+
try {
|
|
3086
|
+
await ctx.api.deleteForumTopic(chatId, topicId);
|
|
3087
|
+
} catch (err) {
|
|
3088
|
+
log8.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
await core.sessionManager.removeRecord(record.sessionId);
|
|
3092
|
+
deleted++;
|
|
3093
|
+
} catch (err) {
|
|
3094
|
+
log8.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
|
|
3095
|
+
failed++;
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
await ctx.reply(
|
|
3099
|
+
`\u{1F5D1} Cleaned up <b>${deleted}</b> sessions${failed > 0 ? ` (${failed} failed)` : ""}.`,
|
|
3100
|
+
{ parse_mode: "HTML" }
|
|
3101
|
+
);
|
|
3102
|
+
}
|
|
2324
3103
|
async function handleAgents(ctx, core) {
|
|
2325
3104
|
const agents = core.agentManager.getAvailableAgents();
|
|
2326
3105
|
const defaultAgent = core.configManager.get().defaultAgent;
|
|
@@ -2365,7 +3144,7 @@ function setupDangerousModeCallbacks(bot, core) {
|
|
|
2365
3144
|
const session = core.sessionManager.getSession(sessionId);
|
|
2366
3145
|
if (session) {
|
|
2367
3146
|
session.dangerousMode = !session.dangerousMode;
|
|
2368
|
-
|
|
3147
|
+
log8.info({ sessionId, dangerousMode: session.dangerousMode }, "Dangerous mode toggled via button");
|
|
2369
3148
|
core.sessionManager.updateSessionDangerousMode(sessionId, session.dangerousMode).catch(() => {
|
|
2370
3149
|
});
|
|
2371
3150
|
const toastText2 = session.dangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
|
|
@@ -2392,7 +3171,7 @@ function setupDangerousModeCallbacks(bot, core) {
|
|
|
2392
3171
|
const newDangerousMode = !(record.dangerousMode ?? false);
|
|
2393
3172
|
core.sessionManager.updateSessionDangerousMode(sessionId, newDangerousMode).catch(() => {
|
|
2394
3173
|
});
|
|
2395
|
-
|
|
3174
|
+
log8.info({ sessionId, dangerousMode: newDangerousMode }, "Dangerous mode toggled via button (store-only, session not in memory)");
|
|
2396
3175
|
const toastText = newDangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
|
|
2397
3176
|
try {
|
|
2398
3177
|
await ctx.answerCallbackQuery({ text: toastText });
|
|
@@ -2560,7 +3339,7 @@ async function executeNewSession(bot, core, chatId, agentName, workspace) {
|
|
|
2560
3339
|
});
|
|
2561
3340
|
const finalName = `\u{1F504} ${session.agentName} \u2014 New Session`;
|
|
2562
3341
|
await renameSessionTopic(bot, chatId, threadId, finalName);
|
|
2563
|
-
session.warmup().catch((err) =>
|
|
3342
|
+
session.warmup().catch((err) => log8.error({ err }, "Warm-up error"));
|
|
2564
3343
|
return { session, threadId, firstMsgId };
|
|
2565
3344
|
} catch (err) {
|
|
2566
3345
|
try {
|
|
@@ -2577,16 +3356,134 @@ async function executeCancelSession(core, excludeSessionId) {
|
|
|
2577
3356
|
await session.cancel();
|
|
2578
3357
|
return session;
|
|
2579
3358
|
}
|
|
3359
|
+
async function handleIntegrate(ctx, _core) {
|
|
3360
|
+
const { listIntegrations } = await import("./integrate-WUPLRJD3.js");
|
|
3361
|
+
const agents = listIntegrations();
|
|
3362
|
+
const keyboard = new InlineKeyboard();
|
|
3363
|
+
for (const agent of agents) {
|
|
3364
|
+
keyboard.text(`\u{1F916} ${agent}`, `i:agent:${agent}`).row();
|
|
3365
|
+
}
|
|
3366
|
+
await ctx.reply(
|
|
3367
|
+
`<b>\u{1F517} Integrations</b>
|
|
3368
|
+
|
|
3369
|
+
Select an agent to manage its integrations.`,
|
|
3370
|
+
{ parse_mode: "HTML", reply_markup: keyboard }
|
|
3371
|
+
);
|
|
3372
|
+
}
|
|
3373
|
+
function buildAgentItemsKeyboard(agentName, items) {
|
|
3374
|
+
const keyboard = new InlineKeyboard();
|
|
3375
|
+
for (const item of items) {
|
|
3376
|
+
const installed = item.isInstalled();
|
|
3377
|
+
keyboard.text(
|
|
3378
|
+
installed ? `\u2705 ${item.name} \u2014 Uninstall` : `\u{1F4E6} ${item.name} \u2014 Install`,
|
|
3379
|
+
installed ? `i:uninstall:${agentName}:${item.id}` : `i:install:${agentName}:${item.id}`
|
|
3380
|
+
).row();
|
|
3381
|
+
}
|
|
3382
|
+
keyboard.text("\u2190 Back", "i:back").row();
|
|
3383
|
+
return keyboard;
|
|
3384
|
+
}
|
|
3385
|
+
function setupIntegrateCallbacks(bot, core) {
|
|
3386
|
+
bot.callbackQuery(/^i:/, async (ctx) => {
|
|
3387
|
+
const data = ctx.callbackQuery.data;
|
|
3388
|
+
try {
|
|
3389
|
+
await ctx.answerCallbackQuery();
|
|
3390
|
+
} catch {
|
|
3391
|
+
}
|
|
3392
|
+
if (data === "i:back") {
|
|
3393
|
+
const { listIntegrations } = await import("./integrate-WUPLRJD3.js");
|
|
3394
|
+
const agents = listIntegrations();
|
|
3395
|
+
const keyboard2 = new InlineKeyboard();
|
|
3396
|
+
for (const agent of agents) {
|
|
3397
|
+
keyboard2.text(`\u{1F916} ${agent}`, `i:agent:${agent}`).row();
|
|
3398
|
+
}
|
|
3399
|
+
try {
|
|
3400
|
+
await ctx.editMessageText(
|
|
3401
|
+
`<b>\u{1F517} Integrations</b>
|
|
3402
|
+
|
|
3403
|
+
Select an agent to manage its integrations.`,
|
|
3404
|
+
{ parse_mode: "HTML", reply_markup: keyboard2 }
|
|
3405
|
+
);
|
|
3406
|
+
} catch {
|
|
3407
|
+
}
|
|
3408
|
+
return;
|
|
3409
|
+
}
|
|
3410
|
+
const agentMatch = data.match(/^i:agent:(.+)$/);
|
|
3411
|
+
if (agentMatch) {
|
|
3412
|
+
const agentName2 = agentMatch[1];
|
|
3413
|
+
const { getIntegration: getIntegration2 } = await import("./integrate-WUPLRJD3.js");
|
|
3414
|
+
const integration2 = getIntegration2(agentName2);
|
|
3415
|
+
if (!integration2) {
|
|
3416
|
+
await ctx.reply(`\u274C No integration available for '${escapeHtml(agentName2)}'.`, { parse_mode: "HTML" });
|
|
3417
|
+
return;
|
|
3418
|
+
}
|
|
3419
|
+
const keyboard2 = buildAgentItemsKeyboard(agentName2, integration2.items);
|
|
3420
|
+
try {
|
|
3421
|
+
await ctx.editMessageText(
|
|
3422
|
+
`<b>\u{1F517} ${escapeHtml(agentName2)} Integrations</b>
|
|
3423
|
+
|
|
3424
|
+
${integration2.items.map((i) => `\u2022 <b>${escapeHtml(i.name)}</b> \u2014 ${escapeHtml(i.description)}`).join("\n")}`,
|
|
3425
|
+
{ parse_mode: "HTML", reply_markup: keyboard2 }
|
|
3426
|
+
);
|
|
3427
|
+
} catch {
|
|
3428
|
+
await ctx.reply(
|
|
3429
|
+
`<b>\u{1F517} ${escapeHtml(agentName2)} Integrations</b>`,
|
|
3430
|
+
{ parse_mode: "HTML", reply_markup: keyboard2 }
|
|
3431
|
+
);
|
|
3432
|
+
}
|
|
3433
|
+
return;
|
|
3434
|
+
}
|
|
3435
|
+
const actionMatch = data.match(/^i:(install|uninstall):([^:]+):(.+)$/);
|
|
3436
|
+
if (!actionMatch) return;
|
|
3437
|
+
const action = actionMatch[1];
|
|
3438
|
+
const agentName = actionMatch[2];
|
|
3439
|
+
const itemId = actionMatch[3];
|
|
3440
|
+
const { getIntegration } = await import("./integrate-WUPLRJD3.js");
|
|
3441
|
+
const integration = getIntegration(agentName);
|
|
3442
|
+
if (!integration) return;
|
|
3443
|
+
const item = integration.items.find((i) => i.id === itemId);
|
|
3444
|
+
if (!item) return;
|
|
3445
|
+
const result = action === "install" ? await item.install() : await item.uninstall();
|
|
3446
|
+
const installed = action === "install" && result.success;
|
|
3447
|
+
await core.configManager.save({
|
|
3448
|
+
integrations: {
|
|
3449
|
+
[agentName]: {
|
|
3450
|
+
installed,
|
|
3451
|
+
installedAt: installed ? (/* @__PURE__ */ new Date()).toISOString() : void 0
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3454
|
+
});
|
|
3455
|
+
const statusEmoji = result.success ? "\u2705" : "\u274C";
|
|
3456
|
+
const actionLabel = action === "install" ? "installed" : "uninstalled";
|
|
3457
|
+
const logsText = result.logs.map((l) => `<code>${escapeHtml(l)}</code>`).join("\n");
|
|
3458
|
+
const resultText = `${statusEmoji} <b>${escapeHtml(item.name)}</b> ${actionLabel}.
|
|
3459
|
+
|
|
3460
|
+
${logsText}`;
|
|
3461
|
+
const keyboard = buildAgentItemsKeyboard(agentName, integration.items);
|
|
3462
|
+
try {
|
|
3463
|
+
await ctx.editMessageText(
|
|
3464
|
+
`<b>\u{1F517} ${escapeHtml(agentName)} Integrations</b>
|
|
3465
|
+
|
|
3466
|
+
${resultText}`,
|
|
3467
|
+
{ parse_mode: "HTML", reply_markup: keyboard }
|
|
3468
|
+
);
|
|
3469
|
+
} catch {
|
|
3470
|
+
await ctx.reply(resultText, { parse_mode: "HTML" });
|
|
3471
|
+
}
|
|
3472
|
+
});
|
|
3473
|
+
}
|
|
2580
3474
|
var STATIC_COMMANDS = [
|
|
2581
3475
|
{ command: "new", description: "Create new session" },
|
|
2582
3476
|
{ command: "newchat", description: "New chat, same agent & workspace" },
|
|
2583
3477
|
{ command: "cancel", description: "Cancel current session" },
|
|
2584
3478
|
{ command: "status", description: "Show status" },
|
|
3479
|
+
{ command: "sessions", description: "List all sessions" },
|
|
2585
3480
|
{ command: "agents", description: "List available agents" },
|
|
2586
3481
|
{ command: "help", description: "Help" },
|
|
2587
3482
|
{ command: "menu", description: "Show menu" },
|
|
2588
3483
|
{ command: "enable_dangerous", description: "Auto-approve all permission requests (session only)" },
|
|
2589
3484
|
{ command: "disable_dangerous", description: "Restore normal permission prompts (session only)" },
|
|
3485
|
+
{ command: "integrate", description: "Manage agent integrations" },
|
|
3486
|
+
{ command: "handoff", description: "Continue this session in your terminal" },
|
|
2590
3487
|
{ command: "restart", description: "Restart OpenACP" },
|
|
2591
3488
|
{ command: "update", description: "Update to latest version and restart" }
|
|
2592
3489
|
];
|
|
@@ -2594,7 +3491,7 @@ var STATIC_COMMANDS = [
|
|
|
2594
3491
|
// src/adapters/telegram/permissions.ts
|
|
2595
3492
|
import { InlineKeyboard as InlineKeyboard2 } from "grammy";
|
|
2596
3493
|
import { nanoid as nanoid2 } from "nanoid";
|
|
2597
|
-
var
|
|
3494
|
+
var log9 = createChildLogger({ module: "telegram-permissions" });
|
|
2598
3495
|
var PermissionHandler = class {
|
|
2599
3496
|
constructor(bot, chatId, getSession, sendNotification) {
|
|
2600
3497
|
this.bot = bot;
|
|
@@ -2654,7 +3551,7 @@ ${escapeHtml(request.description)}`,
|
|
|
2654
3551
|
}
|
|
2655
3552
|
const session = this.getSession(pending.sessionId);
|
|
2656
3553
|
const isAllow = pending.options.find((o) => o.id === optionId)?.isAllow ?? false;
|
|
2657
|
-
|
|
3554
|
+
log9.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
|
|
2658
3555
|
if (session?.permissionGate.requestId === pending.requestId) {
|
|
2659
3556
|
session.permissionGate.resolve(optionId);
|
|
2660
3557
|
}
|
|
@@ -2672,10 +3569,10 @@ ${escapeHtml(request.description)}`,
|
|
|
2672
3569
|
};
|
|
2673
3570
|
|
|
2674
3571
|
// src/adapters/telegram/assistant.ts
|
|
2675
|
-
var
|
|
3572
|
+
var log10 = createChildLogger({ module: "telegram-assistant" });
|
|
2676
3573
|
async function spawnAssistant(core, adapter, assistantTopicId) {
|
|
2677
3574
|
const config = core.configManager.get();
|
|
2678
|
-
|
|
3575
|
+
log10.info({ agent: config.defaultAgent }, "Creating assistant session...");
|
|
2679
3576
|
const session = await core.sessionManager.createSession(
|
|
2680
3577
|
"telegram",
|
|
2681
3578
|
config.defaultAgent,
|
|
@@ -2684,30 +3581,44 @@ async function spawnAssistant(core, adapter, assistantTopicId) {
|
|
|
2684
3581
|
);
|
|
2685
3582
|
session.threadId = String(assistantTopicId);
|
|
2686
3583
|
session.name = "Assistant";
|
|
2687
|
-
|
|
3584
|
+
log10.info({ sessionId: session.id }, "Assistant agent spawned");
|
|
2688
3585
|
core.wireSessionEvents(session, adapter);
|
|
2689
|
-
const
|
|
3586
|
+
const allRecords = core.sessionManager.listRecords();
|
|
3587
|
+
const activeCount = allRecords.filter((r) => r.status === "active" || r.status === "initializing").length;
|
|
3588
|
+
const statusCounts = /* @__PURE__ */ new Map();
|
|
3589
|
+
for (const r of allRecords) {
|
|
3590
|
+
statusCounts.set(r.status, (statusCounts.get(r.status) ?? 0) + 1);
|
|
3591
|
+
}
|
|
3592
|
+
const topicSummary = Array.from(statusCounts.entries()).map(([status, count]) => ({ status, count }));
|
|
3593
|
+
const ctx = {
|
|
3594
|
+
config,
|
|
3595
|
+
activeSessionCount: activeCount,
|
|
3596
|
+
totalSessionCount: allRecords.length,
|
|
3597
|
+
topicSummary
|
|
3598
|
+
};
|
|
3599
|
+
const systemPrompt = buildAssistantSystemPrompt(ctx);
|
|
2690
3600
|
const ready = session.enqueuePrompt(systemPrompt).then(() => {
|
|
2691
|
-
|
|
3601
|
+
log10.info({ sessionId: session.id }, "Assistant system prompt completed");
|
|
2692
3602
|
}).catch((err) => {
|
|
2693
|
-
|
|
3603
|
+
log10.warn({ err }, "Assistant system prompt failed");
|
|
2694
3604
|
});
|
|
2695
3605
|
return { session, ready };
|
|
2696
3606
|
}
|
|
2697
|
-
function buildAssistantSystemPrompt(
|
|
3607
|
+
function buildAssistantSystemPrompt(ctx) {
|
|
3608
|
+
const { config, activeSessionCount, totalSessionCount, topicSummary } = ctx;
|
|
2698
3609
|
const agentNames = Object.keys(config.agents).join(", ");
|
|
2699
|
-
|
|
3610
|
+
const topicBreakdown = topicSummary.map((s) => `${s.status}: ${s.count}`).join(", ") || "none";
|
|
3611
|
+
return `You are the OpenACP Assistant. Help users manage their AI coding sessions and topics.
|
|
2700
3612
|
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
3613
|
+
## Current State
|
|
3614
|
+
- Active sessions: ${activeSessionCount} / ${totalSessionCount} total
|
|
3615
|
+
- Topics by status: ${topicBreakdown}
|
|
3616
|
+
- Available agents: ${agentNames}
|
|
3617
|
+
- Default agent: ${config.defaultAgent}
|
|
3618
|
+
- Workspace base: ${config.workspace.baseDir}
|
|
2704
3619
|
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
2. Which workspace/project
|
|
2708
|
-
3. Confirm and create
|
|
2709
|
-
|
|
2710
|
-
Commands reference:
|
|
3620
|
+
## Session Management Commands
|
|
3621
|
+
These are Telegram bot commands (type directly in chat):
|
|
2711
3622
|
- /new [agent] [workspace] \u2014 Create new session
|
|
2712
3623
|
- /newchat \u2014 New chat with same agent & workspace
|
|
2713
3624
|
- /cancel \u2014 Cancel current session
|
|
@@ -2715,7 +3626,48 @@ Commands reference:
|
|
|
2715
3626
|
- /agents \u2014 List agents
|
|
2716
3627
|
- /help \u2014 Show help
|
|
2717
3628
|
|
|
2718
|
-
|
|
3629
|
+
## Management Commands (via CLI)
|
|
3630
|
+
You have access to bash. Use these commands to manage OpenACP:
|
|
3631
|
+
|
|
3632
|
+
### Session management
|
|
3633
|
+
\`\`\`bash
|
|
3634
|
+
openacp api status # List active sessions
|
|
3635
|
+
openacp api session <id> # Session detail
|
|
3636
|
+
openacp api send <id> "prompt text" # Send prompt to session
|
|
3637
|
+
openacp api cancel <id> # Cancel session
|
|
3638
|
+
openacp api dangerous <id> on|off # Toggle dangerous mode
|
|
3639
|
+
\`\`\`
|
|
3640
|
+
|
|
3641
|
+
### Topic management
|
|
3642
|
+
\`\`\`bash
|
|
3643
|
+
openacp api topics # List topics
|
|
3644
|
+
openacp api topics --status finished,error
|
|
3645
|
+
openacp api delete-topic <id> # Delete topic
|
|
3646
|
+
openacp api delete-topic <id> --force # Force delete active
|
|
3647
|
+
openacp api cleanup # Cleanup finished topics
|
|
3648
|
+
openacp api cleanup --status finished,error
|
|
3649
|
+
\`\`\`
|
|
3650
|
+
|
|
3651
|
+
### System
|
|
3652
|
+
\`\`\`bash
|
|
3653
|
+
openacp api health # System health
|
|
3654
|
+
openacp api config # Show config
|
|
3655
|
+
openacp api config set <key> <value> # Update config
|
|
3656
|
+
openacp api adapters # List adapters
|
|
3657
|
+
openacp api tunnel # Tunnel status
|
|
3658
|
+
openacp api notify "message" # Send notification
|
|
3659
|
+
openacp api version # Daemon version
|
|
3660
|
+
openacp api restart # Restart daemon
|
|
3661
|
+
\`\`\`
|
|
3662
|
+
|
|
3663
|
+
## Guidelines
|
|
3664
|
+
- When a user asks about sessions or topics, run \`openacp api topics\` or \`openacp api status\` to get current data.
|
|
3665
|
+
- When deleting: if the session is active/initializing, warn the user first. Only use --force if they confirm.
|
|
3666
|
+
- Use \`openacp api health\` to check system status.
|
|
3667
|
+
- Use \`openacp api config\` to check configuration, \`openacp api config set\` to update values.
|
|
3668
|
+
- Format responses nicely for Telegram (use bold, code blocks).
|
|
3669
|
+
- Be concise and helpful. Respond in the same language the user uses.
|
|
3670
|
+
- When creating sessions, guide through: agent selection \u2192 workspace \u2192 confirm.`;
|
|
2719
3671
|
}
|
|
2720
3672
|
async function handleAssistantMessage(session, text) {
|
|
2721
3673
|
if (!session) return;
|
|
@@ -2728,7 +3680,7 @@ function redirectToAssistant(chatId, assistantTopicId) {
|
|
|
2728
3680
|
}
|
|
2729
3681
|
|
|
2730
3682
|
// src/adapters/telegram/activity.ts
|
|
2731
|
-
var
|
|
3683
|
+
var log11 = createChildLogger({ module: "telegram:activity" });
|
|
2732
3684
|
var THINKING_REFRESH_MS = 15e3;
|
|
2733
3685
|
var THINKING_MAX_MS = 3 * 60 * 1e3;
|
|
2734
3686
|
var ThinkingIndicator = class {
|
|
@@ -2760,7 +3712,7 @@ var ThinkingIndicator = class {
|
|
|
2760
3712
|
this.startRefreshTimer();
|
|
2761
3713
|
}
|
|
2762
3714
|
} catch (err) {
|
|
2763
|
-
|
|
3715
|
+
log11.warn({ err }, "ThinkingIndicator.show() failed");
|
|
2764
3716
|
} finally {
|
|
2765
3717
|
this.sending = false;
|
|
2766
3718
|
}
|
|
@@ -2833,7 +3785,7 @@ var UsageMessage = class {
|
|
|
2833
3785
|
if (result) this.msgId = result.message_id;
|
|
2834
3786
|
}
|
|
2835
3787
|
} catch (err) {
|
|
2836
|
-
|
|
3788
|
+
log11.warn({ err }, "UsageMessage.send() failed");
|
|
2837
3789
|
}
|
|
2838
3790
|
}
|
|
2839
3791
|
getMsgId() {
|
|
@@ -2846,7 +3798,7 @@ var UsageMessage = class {
|
|
|
2846
3798
|
try {
|
|
2847
3799
|
await this.sendQueue.enqueue(() => this.api.deleteMessage(this.chatId, id));
|
|
2848
3800
|
} catch (err) {
|
|
2849
|
-
|
|
3801
|
+
log11.warn({ err }, "UsageMessage.delete() failed");
|
|
2850
3802
|
}
|
|
2851
3803
|
}
|
|
2852
3804
|
};
|
|
@@ -2881,6 +3833,7 @@ var PlanCard = class {
|
|
|
2881
3833
|
msgId;
|
|
2882
3834
|
flushPromise = Promise.resolve();
|
|
2883
3835
|
latestEntries;
|
|
3836
|
+
lastSentText;
|
|
2884
3837
|
flushTimer;
|
|
2885
3838
|
update(entries) {
|
|
2886
3839
|
this.latestEntries = entries;
|
|
@@ -2911,6 +3864,8 @@ var PlanCard = class {
|
|
|
2911
3864
|
async _flush() {
|
|
2912
3865
|
if (!this.latestEntries) return;
|
|
2913
3866
|
const text = formatPlanCard(this.latestEntries);
|
|
3867
|
+
if (this.msgId && text === this.lastSentText) return;
|
|
3868
|
+
this.lastSentText = text;
|
|
2914
3869
|
try {
|
|
2915
3870
|
if (this.msgId) {
|
|
2916
3871
|
await this.sendQueue.enqueue(
|
|
@@ -2929,7 +3884,7 @@ var PlanCard = class {
|
|
|
2929
3884
|
if (result) this.msgId = result.message_id;
|
|
2930
3885
|
}
|
|
2931
3886
|
} catch (err) {
|
|
2932
|
-
|
|
3887
|
+
log11.warn({ err }, "PlanCard flush failed");
|
|
2933
3888
|
}
|
|
2934
3889
|
}
|
|
2935
3890
|
};
|
|
@@ -2992,7 +3947,7 @@ var ActivityTracker = class {
|
|
|
2992
3947
|
})
|
|
2993
3948
|
);
|
|
2994
3949
|
} catch (err) {
|
|
2995
|
-
|
|
3950
|
+
log11.warn({ err }, "ActivityTracker.onComplete() Done send failed");
|
|
2996
3951
|
}
|
|
2997
3952
|
}
|
|
2998
3953
|
}
|
|
@@ -3019,19 +3974,19 @@ var TelegramSendQueue = class {
|
|
|
3019
3974
|
enqueue(fn, opts) {
|
|
3020
3975
|
const type = opts?.type ?? "other";
|
|
3021
3976
|
const key = opts?.key;
|
|
3022
|
-
return new Promise((
|
|
3977
|
+
return new Promise((resolve2, reject) => {
|
|
3023
3978
|
if (type === "text" && key) {
|
|
3024
3979
|
const idx = this.items.findIndex(
|
|
3025
3980
|
(item) => item.type === "text" && item.key === key
|
|
3026
3981
|
);
|
|
3027
3982
|
if (idx !== -1) {
|
|
3028
3983
|
this.items[idx].resolve(void 0);
|
|
3029
|
-
this.items[idx] = { fn, type, key, resolve, reject };
|
|
3984
|
+
this.items[idx] = { fn, type, key, resolve: resolve2, reject };
|
|
3030
3985
|
this.scheduleProcess();
|
|
3031
3986
|
return;
|
|
3032
3987
|
}
|
|
3033
3988
|
}
|
|
3034
|
-
this.items.push({ fn, type, key, resolve, reject });
|
|
3989
|
+
this.items.push({ fn, type, key, resolve: resolve2, reject });
|
|
3035
3990
|
this.scheduleProcess();
|
|
3036
3991
|
});
|
|
3037
3992
|
}
|
|
@@ -3219,7 +4174,7 @@ function setupActionCallbacks(bot, core, chatId, getAssistantSessionId) {
|
|
|
3219
4174
|
}
|
|
3220
4175
|
|
|
3221
4176
|
// src/adapters/telegram/adapter.ts
|
|
3222
|
-
var
|
|
4177
|
+
var log12 = createChildLogger({ module: "telegram" });
|
|
3223
4178
|
function patchedFetch(input, init) {
|
|
3224
4179
|
if (init?.signal && !(init.signal instanceof AbortSignal)) {
|
|
3225
4180
|
const nativeController = new AbortController();
|
|
@@ -3270,7 +4225,7 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
3270
4225
|
this.bot = new Bot(this.telegramConfig.botToken, { client: { fetch: patchedFetch } });
|
|
3271
4226
|
this.bot.catch((err) => {
|
|
3272
4227
|
const rootCause = err.error instanceof Error ? err.error : err;
|
|
3273
|
-
|
|
4228
|
+
log12.error({ err: rootCause }, "Telegram bot error");
|
|
3274
4229
|
});
|
|
3275
4230
|
this.bot.api.config.use(async (prev, method, payload, signal) => {
|
|
3276
4231
|
const maxRetries = 3;
|
|
@@ -3284,7 +4239,7 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
3284
4239
|
if (rateLimitedMethods.includes(method)) {
|
|
3285
4240
|
this.sendQueue.onRateLimited();
|
|
3286
4241
|
}
|
|
3287
|
-
|
|
4242
|
+
log12.warn(
|
|
3288
4243
|
{ method, retryAfter, attempt: attempt + 1 },
|
|
3289
4244
|
"Rate limited by Telegram, retrying"
|
|
3290
4245
|
);
|
|
@@ -3335,10 +4290,12 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
3335
4290
|
this.telegramConfig.chatId,
|
|
3336
4291
|
() => this.assistantSession?.id
|
|
3337
4292
|
);
|
|
4293
|
+
setupIntegrateCallbacks(this.bot, this.core);
|
|
3338
4294
|
setupMenuCallbacks(
|
|
3339
4295
|
this.bot,
|
|
3340
4296
|
this.core,
|
|
3341
|
-
this.telegramConfig.chatId
|
|
4297
|
+
this.telegramConfig.chatId,
|
|
4298
|
+
{ notificationTopicId: this.notificationTopicId, assistantTopicId: this.assistantTopicId }
|
|
3342
4299
|
);
|
|
3343
4300
|
setupCommands(
|
|
3344
4301
|
this.bot,
|
|
@@ -3350,10 +4307,48 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
3350
4307
|
}
|
|
3351
4308
|
);
|
|
3352
4309
|
this.permissionHandler.setupCallbackHandler();
|
|
4310
|
+
this.bot.command("handoff", async (ctx) => {
|
|
4311
|
+
const threadId = ctx.message?.message_thread_id;
|
|
4312
|
+
if (!threadId) return;
|
|
4313
|
+
if (threadId === this.notificationTopicId || threadId === this.assistantTopicId) {
|
|
4314
|
+
await ctx.reply("This command only works in session topics.", {
|
|
4315
|
+
message_thread_id: threadId
|
|
4316
|
+
});
|
|
4317
|
+
return;
|
|
4318
|
+
}
|
|
4319
|
+
const session = this.core.sessionManager.getSessionByThread("telegram", String(threadId));
|
|
4320
|
+
const record = session ? void 0 : this.core.sessionManager.getRecordByThread("telegram", String(threadId));
|
|
4321
|
+
const agentName = session?.agentName ?? record?.agentName;
|
|
4322
|
+
const agentSessionId = session?.agentSessionId ?? record?.agentSessionId;
|
|
4323
|
+
if (!agentName || !agentSessionId) {
|
|
4324
|
+
await ctx.reply("No session found for this topic.", {
|
|
4325
|
+
message_thread_id: threadId
|
|
4326
|
+
});
|
|
4327
|
+
return;
|
|
4328
|
+
}
|
|
4329
|
+
const { getAgentCapabilities: getAgentCapabilities2 } = await import("./agent-registry-7HC6D4CH.js");
|
|
4330
|
+
const caps = getAgentCapabilities2(agentName);
|
|
4331
|
+
if (!caps.supportsResume || !caps.resumeCommand) {
|
|
4332
|
+
await ctx.reply("This agent does not support session transfer.", {
|
|
4333
|
+
message_thread_id: threadId
|
|
4334
|
+
});
|
|
4335
|
+
return;
|
|
4336
|
+
}
|
|
4337
|
+
const command = caps.resumeCommand(agentSessionId);
|
|
4338
|
+
await ctx.reply(
|
|
4339
|
+
`Run this in your terminal to continue the session:
|
|
4340
|
+
|
|
4341
|
+
<code>${command}</code>`,
|
|
4342
|
+
{
|
|
4343
|
+
message_thread_id: threadId,
|
|
4344
|
+
parse_mode: "HTML"
|
|
4345
|
+
}
|
|
4346
|
+
);
|
|
4347
|
+
});
|
|
3353
4348
|
this.setupRoutes();
|
|
3354
4349
|
this.bot.start({
|
|
3355
4350
|
allowed_updates: ["message", "callback_query"],
|
|
3356
|
-
onStart: () =>
|
|
4351
|
+
onStart: () => log12.info(
|
|
3357
4352
|
{ chatId: this.telegramConfig.chatId },
|
|
3358
4353
|
"Telegram bot started"
|
|
3359
4354
|
)
|
|
@@ -3363,10 +4358,13 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
3363
4358
|
const agents = this.core.agentManager.getAvailableAgents();
|
|
3364
4359
|
const agentList = agents.map((a) => `${escapeHtml(a.name)}${a.name === config.defaultAgent ? " (default)" : ""}`).join(", ");
|
|
3365
4360
|
const workspace = escapeHtml(config.workspace.baseDir);
|
|
4361
|
+
const allRecords = this.core.sessionManager.listRecords();
|
|
4362
|
+
const activeCount = allRecords.filter((r) => r.status === "active" || r.status === "initializing").length;
|
|
3366
4363
|
const welcomeText = `\u{1F44B} <b>OpenACP Assistant</b> is online.
|
|
3367
4364
|
|
|
3368
4365
|
Available agents: ${agentList}
|
|
3369
4366
|
Workspace: <code>${workspace}</code>
|
|
4367
|
+
Sessions: ${activeCount} active / ${allRecords.length} total
|
|
3370
4368
|
|
|
3371
4369
|
<b>Select an action:</b>`;
|
|
3372
4370
|
await this.bot.api.sendMessage(this.telegramConfig.chatId, welcomeText, {
|
|
@@ -3375,10 +4373,10 @@ Workspace: <code>${workspace}</code>
|
|
|
3375
4373
|
reply_markup: buildMenuKeyboard()
|
|
3376
4374
|
});
|
|
3377
4375
|
} catch (err) {
|
|
3378
|
-
|
|
4376
|
+
log12.warn({ err }, "Failed to send welcome message");
|
|
3379
4377
|
}
|
|
3380
4378
|
try {
|
|
3381
|
-
|
|
4379
|
+
log12.info("Spawning assistant session...");
|
|
3382
4380
|
const { session, ready } = await spawnAssistant(
|
|
3383
4381
|
this.core,
|
|
3384
4382
|
this,
|
|
@@ -3386,13 +4384,13 @@ Workspace: <code>${workspace}</code>
|
|
|
3386
4384
|
);
|
|
3387
4385
|
this.assistantSession = session;
|
|
3388
4386
|
this.assistantInitializing = true;
|
|
3389
|
-
|
|
4387
|
+
log12.info({ sessionId: session.id }, "Assistant session ready, system prompt running in background");
|
|
3390
4388
|
ready.then(() => {
|
|
3391
4389
|
this.assistantInitializing = false;
|
|
3392
|
-
|
|
4390
|
+
log12.info({ sessionId: session.id }, "Assistant ready for user messages");
|
|
3393
4391
|
});
|
|
3394
4392
|
} catch (err) {
|
|
3395
|
-
|
|
4393
|
+
log12.error({ err }, "Failed to spawn assistant");
|
|
3396
4394
|
this.bot.api.sendMessage(
|
|
3397
4395
|
this.telegramConfig.chatId,
|
|
3398
4396
|
`\u26A0\uFE0F <b>Failed to start assistant session.</b>
|
|
@@ -3408,7 +4406,7 @@ Workspace: <code>${workspace}</code>
|
|
|
3408
4406
|
await this.assistantSession.destroy();
|
|
3409
4407
|
}
|
|
3410
4408
|
await this.bot.stop();
|
|
3411
|
-
|
|
4409
|
+
log12.info("Telegram bot stopped");
|
|
3412
4410
|
}
|
|
3413
4411
|
setupRoutes() {
|
|
3414
4412
|
this.bot.on("message:text", async (ctx) => {
|
|
@@ -3431,7 +4429,7 @@ Workspace: <code>${workspace}</code>
|
|
|
3431
4429
|
ctx.replyWithChatAction("typing").catch(() => {
|
|
3432
4430
|
});
|
|
3433
4431
|
handleAssistantMessage(this.assistantSession, ctx.message.text).catch(
|
|
3434
|
-
(err) =>
|
|
4432
|
+
(err) => log12.error({ err }, "Assistant error")
|
|
3435
4433
|
);
|
|
3436
4434
|
return;
|
|
3437
4435
|
}
|
|
@@ -3448,7 +4446,7 @@ Workspace: <code>${workspace}</code>
|
|
|
3448
4446
|
threadId: String(threadId),
|
|
3449
4447
|
userId: String(ctx.from.id),
|
|
3450
4448
|
text: ctx.message.text
|
|
3451
|
-
}).catch((err) =>
|
|
4449
|
+
}).catch((err) => log12.error({ err }, "handleMessage error"));
|
|
3452
4450
|
});
|
|
3453
4451
|
}
|
|
3454
4452
|
// --- ChannelAdapter implementations ---
|
|
@@ -3528,7 +4526,7 @@ Workspace: <code>${workspace}</code>
|
|
|
3528
4526
|
if (toolState) {
|
|
3529
4527
|
if (meta.viewerLinks) {
|
|
3530
4528
|
toolState.viewerLinks = meta.viewerLinks;
|
|
3531
|
-
|
|
4529
|
+
log12.debug({ toolId: meta.id, viewerLinks: meta.viewerLinks }, "Accumulated viewerLinks");
|
|
3532
4530
|
}
|
|
3533
4531
|
const viewerFilePath = content.metadata?.viewerFilePath;
|
|
3534
4532
|
if (viewerFilePath) toolState.viewerFilePath = viewerFilePath;
|
|
@@ -3537,7 +4535,7 @@ Workspace: <code>${workspace}</code>
|
|
|
3537
4535
|
const isTerminal = meta.status === "completed" || meta.status === "failed";
|
|
3538
4536
|
if (!isTerminal && !meta.viewerLinks) break;
|
|
3539
4537
|
await toolState.ready;
|
|
3540
|
-
|
|
4538
|
+
log12.debug(
|
|
3541
4539
|
{ toolId: meta.id, status: meta.status, hasViewerLinks: !!toolState.viewerLinks, viewerLinks: toolState.viewerLinks, name: toolState.name, msgId: toolState.msgId },
|
|
3542
4540
|
"Tool completed, preparing edit"
|
|
3543
4541
|
);
|
|
@@ -3559,7 +4557,7 @@ Workspace: <code>${workspace}</code>
|
|
|
3559
4557
|
)
|
|
3560
4558
|
);
|
|
3561
4559
|
} catch (err) {
|
|
3562
|
-
|
|
4560
|
+
log12.warn(
|
|
3563
4561
|
{ err, msgId: toolState.msgId, textLen: formattedText.length, hasViewerLinks: !!merged.viewerLinks },
|
|
3564
4562
|
"Tool update edit failed"
|
|
3565
4563
|
);
|
|
@@ -3654,15 +4652,23 @@ Task completed.
|
|
|
3654
4652
|
}
|
|
3655
4653
|
}
|
|
3656
4654
|
async sendPermissionRequest(sessionId, request) {
|
|
3657
|
-
|
|
4655
|
+
log12.info({ sessionId, requestId: request.id }, "Permission request sent");
|
|
3658
4656
|
const session = this.core.sessionManager.getSession(
|
|
3659
4657
|
sessionId
|
|
3660
4658
|
);
|
|
3661
4659
|
if (!session) return;
|
|
4660
|
+
if (request.description.includes("openacp")) {
|
|
4661
|
+
const allowOption = request.options.find((o) => o.isAllow);
|
|
4662
|
+
if (allowOption && session.permissionGate.requestId === request.id) {
|
|
4663
|
+
log12.info({ sessionId, requestId: request.id }, "Auto-approving openacp command");
|
|
4664
|
+
session.permissionGate.resolve(allowOption.id);
|
|
4665
|
+
}
|
|
4666
|
+
return;
|
|
4667
|
+
}
|
|
3662
4668
|
if (session.dangerousMode) {
|
|
3663
4669
|
const allowOption = request.options.find((o) => o.isAllow);
|
|
3664
4670
|
if (allowOption && session.permissionGate.requestId === request.id) {
|
|
3665
|
-
|
|
4671
|
+
log12.info({ sessionId, requestId: request.id, optionId: allowOption.id }, "Dangerous mode: auto-approving permission");
|
|
3666
4672
|
session.permissionGate.resolve(allowOption.id);
|
|
3667
4673
|
}
|
|
3668
4674
|
return;
|
|
@@ -3673,7 +4679,7 @@ Task completed.
|
|
|
3673
4679
|
}
|
|
3674
4680
|
async sendNotification(notification) {
|
|
3675
4681
|
if (notification.sessionId === this.assistantSession?.id) return;
|
|
3676
|
-
|
|
4682
|
+
log12.info(
|
|
3677
4683
|
{ sessionId: notification.sessionId, type: notification.type },
|
|
3678
4684
|
"Notification sent"
|
|
3679
4685
|
);
|
|
@@ -3709,7 +4715,7 @@ Task completed.
|
|
|
3709
4715
|
);
|
|
3710
4716
|
}
|
|
3711
4717
|
async createSessionThread(sessionId, name) {
|
|
3712
|
-
|
|
4718
|
+
log12.info({ sessionId, name }, "Session topic created");
|
|
3713
4719
|
return String(
|
|
3714
4720
|
await createSessionTopic(this.bot, this.telegramConfig.chatId, name)
|
|
3715
4721
|
);
|
|
@@ -3730,6 +4736,17 @@ Task completed.
|
|
|
3730
4736
|
newName
|
|
3731
4737
|
);
|
|
3732
4738
|
}
|
|
4739
|
+
async deleteSessionThread(sessionId) {
|
|
4740
|
+
const record = this.core.sessionManager.getSessionRecord(sessionId);
|
|
4741
|
+
const platform = record?.platform;
|
|
4742
|
+
const topicId = platform?.topicId;
|
|
4743
|
+
if (!topicId) return;
|
|
4744
|
+
try {
|
|
4745
|
+
await this.bot.api.deleteForumTopic(this.telegramConfig.chatId, topicId);
|
|
4746
|
+
} catch (err) {
|
|
4747
|
+
log12.warn({ err, sessionId, topicId }, "Failed to delete forum topic (may already be deleted)");
|
|
4748
|
+
}
|
|
4749
|
+
}
|
|
3733
4750
|
async sendSkillCommands(sessionId, commands) {
|
|
3734
4751
|
if (sessionId === this.assistantSession?.id) return;
|
|
3735
4752
|
const session = this.core.sessionManager.getSession(sessionId);
|
|
@@ -3800,7 +4817,7 @@ Task completed.
|
|
|
3800
4817
|
{ disable_notification: true }
|
|
3801
4818
|
);
|
|
3802
4819
|
} catch (err) {
|
|
3803
|
-
|
|
4820
|
+
log12.error({ err, sessionId }, "Failed to send skill commands");
|
|
3804
4821
|
}
|
|
3805
4822
|
}
|
|
3806
4823
|
async cleanupSkillCommands(sessionId) {
|
|
@@ -3868,6 +4885,7 @@ export {
|
|
|
3868
4885
|
OpenACPCore,
|
|
3869
4886
|
ChannelAdapter,
|
|
3870
4887
|
ApiServer,
|
|
4888
|
+
TopicManager,
|
|
3871
4889
|
TelegramAdapter
|
|
3872
4890
|
};
|
|
3873
|
-
//# sourceMappingURL=chunk-
|
|
4891
|
+
//# sourceMappingURL=chunk-BBPWAWE3.js.map
|