@openacp/cli 0.4.5 → 0.4.6
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-V5P3K4A5.js → chunk-2M4O7AFI.js} +753 -90
- package/dist/chunk-2M4O7AFI.js.map +1 -0
- package/dist/{chunk-BLVZFCKN.js → chunk-ARWC4S35.js} +19 -1
- package/dist/{chunk-BLVZFCKN.js.map → chunk-ARWC4S35.js.map} +1 -1
- package/dist/{chunk-WHKLPZGK.js → chunk-IUPHAXGA.js} +8 -8
- package/dist/chunk-VA2M52CM.js +15 -0
- package/dist/chunk-VA2M52CM.js.map +1 -0
- package/dist/cli.js +370 -26
- package/dist/cli.js.map +1 -1
- package/dist/{config-editor-IXL4BFG3.js → config-editor-EPOKAEP6.js} +4 -4
- package/dist/index.d.ts +93 -10
- package/dist/index.js +11 -8
- package/dist/integrate-HYDSHAF3.js +123 -0
- package/dist/integrate-HYDSHAF3.js.map +1 -0
- package/dist/{main-3CDOICYN.js → main-3POGUQPY.js} +21 -8
- package/dist/main-3POGUQPY.js.map +1 -0
- package/dist/{setup-JQZBPXWS.js → setup-CEDO6VWV.js} +2 -2
- package/dist/setup-CEDO6VWV.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-V5P3K4A5.js.map +0 -1
- package/dist/main-3CDOICYN.js.map +0 -1
- /package/dist/{config-editor-IXL4BFG3.js.map → agent-registry-7HC6D4CH.js.map} +0 -0
- /package/dist/{chunk-WHKLPZGK.js.map → chunk-IUPHAXGA.js.map} +0 -0
- /package/dist/{setup-JQZBPXWS.js.map → config-editor-EPOKAEP6.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
|
}
|
|
@@ -843,6 +846,17 @@ var SessionManager = class {
|
|
|
843
846
|
}
|
|
844
847
|
return void 0;
|
|
845
848
|
}
|
|
849
|
+
getSessionByAgentSessionId(agentSessionId) {
|
|
850
|
+
for (const session of this.sessions.values()) {
|
|
851
|
+
if (session.agentSessionId === agentSessionId) {
|
|
852
|
+
return session;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
return void 0;
|
|
856
|
+
}
|
|
857
|
+
getRecordByAgentSessionId(agentSessionId) {
|
|
858
|
+
return this.store?.findByAgentSessionId(agentSessionId);
|
|
859
|
+
}
|
|
846
860
|
getRecordByThread(channelId, threadId) {
|
|
847
861
|
return this.store?.findByPlatform(
|
|
848
862
|
channelId,
|
|
@@ -910,6 +924,18 @@ var SessionManager = class {
|
|
|
910
924
|
if (channelId) return all.filter((s) => s.channelId === channelId);
|
|
911
925
|
return all;
|
|
912
926
|
}
|
|
927
|
+
listRecords(filter) {
|
|
928
|
+
if (!this.store) return [];
|
|
929
|
+
let records = this.store.list();
|
|
930
|
+
if (filter?.statuses?.length) {
|
|
931
|
+
records = records.filter((r) => filter.statuses.includes(r.status));
|
|
932
|
+
}
|
|
933
|
+
return records;
|
|
934
|
+
}
|
|
935
|
+
async removeRecord(sessionId) {
|
|
936
|
+
if (!this.store) return;
|
|
937
|
+
await this.store.remove(sessionId);
|
|
938
|
+
}
|
|
913
939
|
async destroyAll() {
|
|
914
940
|
if (this.store) {
|
|
915
941
|
for (const session of this.sessions.values()) {
|
|
@@ -1189,6 +1215,11 @@ var JsonFileSessionStore = class {
|
|
|
1189
1215
|
}
|
|
1190
1216
|
return void 0;
|
|
1191
1217
|
}
|
|
1218
|
+
findByAgentSessionId(agentSessionId) {
|
|
1219
|
+
return [...this.records.values()].find(
|
|
1220
|
+
(r) => r.agentSessionId === agentSessionId || r.originalAgentSessionId === agentSessionId
|
|
1221
|
+
);
|
|
1222
|
+
}
|
|
1192
1223
|
list(channelId) {
|
|
1193
1224
|
const all = [...this.records.values()];
|
|
1194
1225
|
if (channelId) return all.filter((r) => r.channelId === channelId);
|
|
@@ -1392,6 +1423,95 @@ var OpenACPCore = class {
|
|
|
1392
1423
|
}
|
|
1393
1424
|
return session;
|
|
1394
1425
|
}
|
|
1426
|
+
async adoptSession(agentName, agentSessionId, cwd) {
|
|
1427
|
+
const caps = getAgentCapabilities(agentName);
|
|
1428
|
+
if (!caps.supportsResume) {
|
|
1429
|
+
return { ok: false, error: "agent_not_supported", message: `Agent '${agentName}' does not support session resume` };
|
|
1430
|
+
}
|
|
1431
|
+
const agentDef = this.agentManager.getAgent(agentName);
|
|
1432
|
+
if (!agentDef) {
|
|
1433
|
+
return { ok: false, error: "agent_not_supported", message: `Agent '${agentName}' not found` };
|
|
1434
|
+
}
|
|
1435
|
+
const { existsSync } = await import("fs");
|
|
1436
|
+
if (!existsSync(cwd)) {
|
|
1437
|
+
return { ok: false, error: "invalid_cwd", message: `Directory does not exist: ${cwd}` };
|
|
1438
|
+
}
|
|
1439
|
+
const maxSessions = this.configManager.get().security.maxConcurrentSessions;
|
|
1440
|
+
if (this.sessionManager.listSessions().length >= maxSessions) {
|
|
1441
|
+
return { ok: false, error: "session_limit", message: "Maximum concurrent sessions reached" };
|
|
1442
|
+
}
|
|
1443
|
+
const existingRecord = this.sessionManager.getRecordByAgentSessionId(agentSessionId);
|
|
1444
|
+
if (existingRecord) {
|
|
1445
|
+
const platform = existingRecord.platform;
|
|
1446
|
+
if (platform?.topicId) {
|
|
1447
|
+
const adapter2 = this.adapters.values().next().value;
|
|
1448
|
+
if (adapter2) {
|
|
1449
|
+
try {
|
|
1450
|
+
await adapter2.sendMessage(existingRecord.sessionId, {
|
|
1451
|
+
type: "text",
|
|
1452
|
+
text: "Session resumed from CLI."
|
|
1453
|
+
});
|
|
1454
|
+
} catch {
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
return {
|
|
1458
|
+
ok: true,
|
|
1459
|
+
sessionId: existingRecord.sessionId,
|
|
1460
|
+
threadId: String(platform.topicId),
|
|
1461
|
+
status: "existing"
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
let agentInstance;
|
|
1466
|
+
try {
|
|
1467
|
+
agentInstance = await this.agentManager.resume(agentName, cwd, agentSessionId);
|
|
1468
|
+
} catch (err) {
|
|
1469
|
+
return {
|
|
1470
|
+
ok: false,
|
|
1471
|
+
error: "resume_failed",
|
|
1472
|
+
message: `Failed to resume session: ${err instanceof Error ? err.message : String(err)}`
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
const session = new Session({
|
|
1476
|
+
channelId: "api",
|
|
1477
|
+
agentName,
|
|
1478
|
+
workingDirectory: cwd,
|
|
1479
|
+
agentInstance
|
|
1480
|
+
});
|
|
1481
|
+
session.agentSessionId = agentInstance.sessionId;
|
|
1482
|
+
this.sessionManager.registerSession(session);
|
|
1483
|
+
const firstEntry = this.adapters.entries().next().value;
|
|
1484
|
+
if (!firstEntry) {
|
|
1485
|
+
await session.destroy();
|
|
1486
|
+
return { ok: false, error: "no_adapter", message: "No channel adapter registered" };
|
|
1487
|
+
}
|
|
1488
|
+
const [adapterChannelId, adapter] = firstEntry;
|
|
1489
|
+
const threadId = await adapter.createSessionThread(session.id, session.name ?? "Adopted session");
|
|
1490
|
+
session.channelId = adapterChannelId;
|
|
1491
|
+
session.threadId = threadId;
|
|
1492
|
+
this.wireSessionEvents(session, adapter);
|
|
1493
|
+
if (this.sessionStore) {
|
|
1494
|
+
await this.sessionStore.save({
|
|
1495
|
+
sessionId: session.id,
|
|
1496
|
+
agentSessionId: agentInstance.sessionId,
|
|
1497
|
+
originalAgentSessionId: agentSessionId,
|
|
1498
|
+
agentName,
|
|
1499
|
+
workingDir: cwd,
|
|
1500
|
+
channelId: adapterChannelId,
|
|
1501
|
+
status: "active",
|
|
1502
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1503
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1504
|
+
name: session.name,
|
|
1505
|
+
platform: { topicId: Number(threadId) }
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
return {
|
|
1509
|
+
ok: true,
|
|
1510
|
+
sessionId: session.id,
|
|
1511
|
+
threadId,
|
|
1512
|
+
status: "adopted"
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1395
1515
|
async handleNewChat(channelId, currentThreadId) {
|
|
1396
1516
|
const currentSession = this.sessionManager.getSessionByThread(
|
|
1397
1517
|
channelId,
|
|
@@ -1548,6 +1668,8 @@ var ChannelAdapter = class {
|
|
|
1548
1668
|
this.core = core;
|
|
1549
1669
|
this.config = config;
|
|
1550
1670
|
}
|
|
1671
|
+
async deleteSessionThread(_sessionId) {
|
|
1672
|
+
}
|
|
1551
1673
|
// Skill commands — override in adapters that support dynamic commands
|
|
1552
1674
|
async sendSkillCommands(_sessionId, _commands) {
|
|
1553
1675
|
}
|
|
@@ -1560,25 +1682,56 @@ import * as http from "http";
|
|
|
1560
1682
|
import * as fs3 from "fs";
|
|
1561
1683
|
import * as path4 from "path";
|
|
1562
1684
|
import * as os2 from "os";
|
|
1685
|
+
import { fileURLToPath } from "url";
|
|
1563
1686
|
var log5 = createChildLogger({ module: "api-server" });
|
|
1564
1687
|
var DEFAULT_PORT_FILE = path4.join(os2.homedir(), ".openacp", "api.port");
|
|
1688
|
+
var cachedVersion;
|
|
1689
|
+
function getVersion() {
|
|
1690
|
+
if (cachedVersion) return cachedVersion;
|
|
1691
|
+
try {
|
|
1692
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
1693
|
+
const pkgPath = path4.resolve(path4.dirname(__filename), "../../package.json");
|
|
1694
|
+
const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
|
|
1695
|
+
cachedVersion = pkg.version ?? "0.0.0-dev";
|
|
1696
|
+
} catch {
|
|
1697
|
+
cachedVersion = "0.0.0-dev";
|
|
1698
|
+
}
|
|
1699
|
+
return cachedVersion;
|
|
1700
|
+
}
|
|
1701
|
+
var SENSITIVE_KEYS = ["botToken", "token", "apiKey", "secret", "password", "webhookSecret"];
|
|
1702
|
+
function redactConfig(config) {
|
|
1703
|
+
const redacted = structuredClone(config);
|
|
1704
|
+
redactDeep(redacted);
|
|
1705
|
+
return redacted;
|
|
1706
|
+
}
|
|
1707
|
+
function redactDeep(obj) {
|
|
1708
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1709
|
+
if (SENSITIVE_KEYS.includes(key) && typeof value === "string") {
|
|
1710
|
+
obj[key] = "***";
|
|
1711
|
+
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
1712
|
+
redactDeep(value);
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1565
1716
|
var ApiServer = class {
|
|
1566
|
-
constructor(core, config, portFilePath) {
|
|
1717
|
+
constructor(core, config, portFilePath, topicManager) {
|
|
1567
1718
|
this.core = core;
|
|
1568
1719
|
this.config = config;
|
|
1720
|
+
this.topicManager = topicManager;
|
|
1569
1721
|
this.portFilePath = portFilePath ?? DEFAULT_PORT_FILE;
|
|
1570
1722
|
}
|
|
1571
1723
|
server = null;
|
|
1572
1724
|
actualPort = 0;
|
|
1573
1725
|
portFilePath;
|
|
1726
|
+
startedAt = Date.now();
|
|
1574
1727
|
async start() {
|
|
1575
1728
|
this.server = http.createServer((req, res) => this.handleRequest(req, res));
|
|
1576
|
-
await new Promise((
|
|
1729
|
+
await new Promise((resolve2, reject) => {
|
|
1577
1730
|
this.server.on("error", (err) => {
|
|
1578
1731
|
if (err.code === "EADDRINUSE") {
|
|
1579
1732
|
log5.warn({ port: this.config.port }, "API port in use, continuing without API server");
|
|
1580
1733
|
this.server = null;
|
|
1581
|
-
|
|
1734
|
+
resolve2();
|
|
1582
1735
|
} else {
|
|
1583
1736
|
reject(err);
|
|
1584
1737
|
}
|
|
@@ -1590,15 +1743,15 @@ var ApiServer = class {
|
|
|
1590
1743
|
}
|
|
1591
1744
|
this.writePortFile();
|
|
1592
1745
|
log5.info({ host: this.config.host, port: this.actualPort }, "API server listening");
|
|
1593
|
-
|
|
1746
|
+
resolve2();
|
|
1594
1747
|
});
|
|
1595
1748
|
});
|
|
1596
1749
|
}
|
|
1597
1750
|
async stop() {
|
|
1598
1751
|
this.removePortFile();
|
|
1599
1752
|
if (this.server) {
|
|
1600
|
-
await new Promise((
|
|
1601
|
-
this.server.close(() =>
|
|
1753
|
+
await new Promise((resolve2) => {
|
|
1754
|
+
this.server.close(() => resolve2());
|
|
1602
1755
|
});
|
|
1603
1756
|
this.server = null;
|
|
1604
1757
|
}
|
|
@@ -1621,15 +1774,49 @@ var ApiServer = class {
|
|
|
1621
1774
|
const method = req.method?.toUpperCase();
|
|
1622
1775
|
const url = req.url || "";
|
|
1623
1776
|
try {
|
|
1624
|
-
if (method === "POST" && url === "/api/sessions") {
|
|
1777
|
+
if (method === "POST" && url === "/api/sessions/adopt") {
|
|
1778
|
+
await this.handleAdoptSession(req, res);
|
|
1779
|
+
} else if (method === "POST" && url === "/api/sessions") {
|
|
1625
1780
|
await this.handleCreateSession(req, res);
|
|
1626
|
-
} else if (method === "
|
|
1627
|
-
const sessionId = url.match(/^\/api\/sessions\/(
|
|
1781
|
+
} else if (method === "POST" && url.match(/^\/api\/sessions\/([^/]+)\/prompt$/)) {
|
|
1782
|
+
const sessionId = decodeURIComponent(url.match(/^\/api\/sessions\/([^/]+)\/prompt$/)[1]);
|
|
1783
|
+
await this.handleSendPrompt(sessionId, req, res);
|
|
1784
|
+
} else if (method === "PATCH" && url.match(/^\/api\/sessions\/([^/]+)\/dangerous$/)) {
|
|
1785
|
+
const sessionId = decodeURIComponent(url.match(/^\/api\/sessions\/([^/]+)\/dangerous$/)[1]);
|
|
1786
|
+
await this.handleToggleDangerous(sessionId, req, res);
|
|
1787
|
+
} else if (method === "GET" && url.match(/^\/api\/sessions\/([^/]+)$/)) {
|
|
1788
|
+
const sessionId = decodeURIComponent(url.match(/^\/api\/sessions\/([^/]+)$/)[1]);
|
|
1789
|
+
await this.handleGetSession(sessionId, res);
|
|
1790
|
+
} else if (method === "DELETE" && url.match(/^\/api\/sessions\/([^/]+)$/)) {
|
|
1791
|
+
const sessionId = decodeURIComponent(url.match(/^\/api\/sessions\/([^/]+)$/)[1]);
|
|
1628
1792
|
await this.handleCancelSession(sessionId, res);
|
|
1629
1793
|
} else if (method === "GET" && url === "/api/sessions") {
|
|
1630
1794
|
await this.handleListSessions(res);
|
|
1631
1795
|
} else if (method === "GET" && url === "/api/agents") {
|
|
1632
1796
|
await this.handleListAgents(res);
|
|
1797
|
+
} else if (method === "GET" && url === "/api/health") {
|
|
1798
|
+
await this.handleHealth(res);
|
|
1799
|
+
} else if (method === "GET" && url === "/api/version") {
|
|
1800
|
+
await this.handleVersion(res);
|
|
1801
|
+
} else if (method === "GET" && url === "/api/config") {
|
|
1802
|
+
await this.handleGetConfig(res);
|
|
1803
|
+
} else if (method === "PATCH" && url === "/api/config") {
|
|
1804
|
+
await this.handleUpdateConfig(req, res);
|
|
1805
|
+
} else if (method === "GET" && url === "/api/adapters") {
|
|
1806
|
+
await this.handleListAdapters(res);
|
|
1807
|
+
} else if (method === "GET" && url === "/api/tunnel") {
|
|
1808
|
+
await this.handleTunnelStatus(res);
|
|
1809
|
+
} else if (method === "POST" && url === "/api/notify") {
|
|
1810
|
+
await this.handleNotify(req, res);
|
|
1811
|
+
} else if (method === "POST" && url === "/api/restart") {
|
|
1812
|
+
await this.handleRestart(res);
|
|
1813
|
+
} else if (method === "GET" && url.match(/^\/api\/topics(\?.*)?$/)) {
|
|
1814
|
+
await this.handleListTopics(url, res);
|
|
1815
|
+
} else if (method === "POST" && url === "/api/topics/cleanup") {
|
|
1816
|
+
await this.handleCleanupTopics(req, res);
|
|
1817
|
+
} else if (method === "DELETE" && url.match(/^\/api\/topics\/([^/?]+)/)) {
|
|
1818
|
+
const match = url.match(/^\/api\/topics\/([^/?]+)/);
|
|
1819
|
+
await this.handleDeleteTopic(decodeURIComponent(match[1]), url, res);
|
|
1633
1820
|
} else {
|
|
1634
1821
|
this.sendJson(res, 404, { error: "Not found" });
|
|
1635
1822
|
}
|
|
@@ -1687,6 +1874,222 @@ var ApiServer = class {
|
|
|
1687
1874
|
workspace: session.workingDirectory
|
|
1688
1875
|
});
|
|
1689
1876
|
}
|
|
1877
|
+
async handleSendPrompt(sessionId, req, res) {
|
|
1878
|
+
const session = this.core.sessionManager.getSession(sessionId);
|
|
1879
|
+
if (!session) {
|
|
1880
|
+
this.sendJson(res, 404, { error: `Session "${sessionId}" not found` });
|
|
1881
|
+
return;
|
|
1882
|
+
}
|
|
1883
|
+
if (session.status === "cancelled" || session.status === "finished" || session.status === "error") {
|
|
1884
|
+
this.sendJson(res, 400, { error: `Session is ${session.status}` });
|
|
1885
|
+
return;
|
|
1886
|
+
}
|
|
1887
|
+
const body = await this.readBody(req);
|
|
1888
|
+
let prompt;
|
|
1889
|
+
if (body) {
|
|
1890
|
+
try {
|
|
1891
|
+
const parsed = JSON.parse(body);
|
|
1892
|
+
prompt = parsed.prompt;
|
|
1893
|
+
} catch {
|
|
1894
|
+
this.sendJson(res, 400, { error: "Invalid JSON body" });
|
|
1895
|
+
return;
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
if (!prompt) {
|
|
1899
|
+
this.sendJson(res, 400, { error: "Missing prompt" });
|
|
1900
|
+
return;
|
|
1901
|
+
}
|
|
1902
|
+
session.enqueuePrompt(prompt).catch(() => {
|
|
1903
|
+
});
|
|
1904
|
+
this.sendJson(res, 200, { ok: true, sessionId, queueDepth: session.queueDepth });
|
|
1905
|
+
}
|
|
1906
|
+
async handleGetSession(sessionId, res) {
|
|
1907
|
+
const session = this.core.sessionManager.getSession(sessionId);
|
|
1908
|
+
if (!session) {
|
|
1909
|
+
this.sendJson(res, 404, { error: `Session "${sessionId}" not found` });
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
this.sendJson(res, 200, {
|
|
1913
|
+
session: {
|
|
1914
|
+
id: session.id,
|
|
1915
|
+
agent: session.agentName,
|
|
1916
|
+
status: session.status,
|
|
1917
|
+
name: session.name ?? null,
|
|
1918
|
+
workspace: session.workingDirectory,
|
|
1919
|
+
createdAt: session.createdAt.toISOString(),
|
|
1920
|
+
dangerousMode: session.dangerousMode,
|
|
1921
|
+
queueDepth: session.queueDepth,
|
|
1922
|
+
promptRunning: session.promptRunning,
|
|
1923
|
+
threadId: session.threadId,
|
|
1924
|
+
channelId: session.channelId,
|
|
1925
|
+
agentSessionId: session.agentSessionId
|
|
1926
|
+
}
|
|
1927
|
+
});
|
|
1928
|
+
}
|
|
1929
|
+
async handleToggleDangerous(sessionId, req, res) {
|
|
1930
|
+
const session = this.core.sessionManager.getSession(sessionId);
|
|
1931
|
+
if (!session) {
|
|
1932
|
+
this.sendJson(res, 404, { error: `Session "${sessionId}" not found` });
|
|
1933
|
+
return;
|
|
1934
|
+
}
|
|
1935
|
+
const body = await this.readBody(req);
|
|
1936
|
+
let enabled;
|
|
1937
|
+
if (body) {
|
|
1938
|
+
try {
|
|
1939
|
+
const parsed = JSON.parse(body);
|
|
1940
|
+
enabled = parsed.enabled;
|
|
1941
|
+
} catch {
|
|
1942
|
+
this.sendJson(res, 400, { error: "Invalid JSON body" });
|
|
1943
|
+
return;
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
if (typeof enabled !== "boolean") {
|
|
1947
|
+
this.sendJson(res, 400, { error: "Missing enabled boolean" });
|
|
1948
|
+
return;
|
|
1949
|
+
}
|
|
1950
|
+
session.dangerousMode = enabled;
|
|
1951
|
+
await this.core.sessionManager.updateSessionDangerousMode(sessionId, enabled);
|
|
1952
|
+
this.sendJson(res, 200, { ok: true, dangerousMode: enabled });
|
|
1953
|
+
}
|
|
1954
|
+
async handleHealth(res) {
|
|
1955
|
+
const activeSessions = this.core.sessionManager.listSessions();
|
|
1956
|
+
const allRecords = this.core.sessionManager.listRecords();
|
|
1957
|
+
const mem = process.memoryUsage();
|
|
1958
|
+
const tunnel = this.core.tunnelService;
|
|
1959
|
+
this.sendJson(res, 200, {
|
|
1960
|
+
status: "ok",
|
|
1961
|
+
uptime: Date.now() - this.startedAt,
|
|
1962
|
+
version: getVersion(),
|
|
1963
|
+
memory: {
|
|
1964
|
+
rss: mem.rss,
|
|
1965
|
+
heapUsed: mem.heapUsed,
|
|
1966
|
+
heapTotal: mem.heapTotal
|
|
1967
|
+
},
|
|
1968
|
+
sessions: {
|
|
1969
|
+
active: activeSessions.filter((s) => s.status === "active" || s.status === "initializing").length,
|
|
1970
|
+
total: allRecords.length
|
|
1971
|
+
},
|
|
1972
|
+
adapters: Array.from(this.core.adapters.keys()),
|
|
1973
|
+
tunnel: tunnel ? { enabled: true, url: tunnel.getPublicUrl() } : { enabled: false }
|
|
1974
|
+
});
|
|
1975
|
+
}
|
|
1976
|
+
async handleVersion(res) {
|
|
1977
|
+
this.sendJson(res, 200, { version: getVersion() });
|
|
1978
|
+
}
|
|
1979
|
+
async handleGetConfig(res) {
|
|
1980
|
+
const config = this.core.configManager.get();
|
|
1981
|
+
this.sendJson(res, 200, { config: redactConfig(config) });
|
|
1982
|
+
}
|
|
1983
|
+
async handleUpdateConfig(req, res) {
|
|
1984
|
+
const body = await this.readBody(req);
|
|
1985
|
+
let configPath;
|
|
1986
|
+
let value;
|
|
1987
|
+
if (body) {
|
|
1988
|
+
try {
|
|
1989
|
+
const parsed = JSON.parse(body);
|
|
1990
|
+
configPath = parsed.path;
|
|
1991
|
+
value = parsed.value;
|
|
1992
|
+
} catch {
|
|
1993
|
+
this.sendJson(res, 400, { error: "Invalid JSON body" });
|
|
1994
|
+
return;
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
if (!configPath) {
|
|
1998
|
+
this.sendJson(res, 400, { error: "Missing path" });
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
2001
|
+
const currentConfig = this.core.configManager.get();
|
|
2002
|
+
const cloned = structuredClone(currentConfig);
|
|
2003
|
+
const parts = configPath.split(".");
|
|
2004
|
+
let target = cloned;
|
|
2005
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
2006
|
+
if (target[parts[i]] && typeof target[parts[i]] === "object" && !Array.isArray(target[parts[i]])) {
|
|
2007
|
+
target = target[parts[i]];
|
|
2008
|
+
} else {
|
|
2009
|
+
this.sendJson(res, 400, { error: "Invalid config path" });
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
const lastKey = parts[parts.length - 1];
|
|
2014
|
+
if (!(lastKey in target)) {
|
|
2015
|
+
this.sendJson(res, 400, { error: "Invalid config path" });
|
|
2016
|
+
return;
|
|
2017
|
+
}
|
|
2018
|
+
target[lastKey] = value;
|
|
2019
|
+
const { ConfigSchema } = await import("./config-J5YQOMDU.js");
|
|
2020
|
+
const result = ConfigSchema.safeParse(cloned);
|
|
2021
|
+
if (!result.success) {
|
|
2022
|
+
this.sendJson(res, 400, {
|
|
2023
|
+
error: "Validation failed",
|
|
2024
|
+
details: result.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }))
|
|
2025
|
+
});
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
const updates = {};
|
|
2029
|
+
let updateTarget = updates;
|
|
2030
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
2031
|
+
updateTarget[parts[i]] = {};
|
|
2032
|
+
updateTarget = updateTarget[parts[i]];
|
|
2033
|
+
}
|
|
2034
|
+
updateTarget[lastKey] = value;
|
|
2035
|
+
await this.core.configManager.save(updates);
|
|
2036
|
+
const RESTART_PREFIXES = ["api.port", "api.host", "runMode", "channels.", "tunnel.", "agents."];
|
|
2037
|
+
const needsRestart = RESTART_PREFIXES.some(
|
|
2038
|
+
(prefix) => configPath.startsWith(prefix) || configPath === prefix.replace(/\.$/, "")
|
|
2039
|
+
// exact match for non-wildcard
|
|
2040
|
+
);
|
|
2041
|
+
this.sendJson(res, 200, {
|
|
2042
|
+
ok: true,
|
|
2043
|
+
needsRestart,
|
|
2044
|
+
config: redactConfig(this.core.configManager.get())
|
|
2045
|
+
});
|
|
2046
|
+
}
|
|
2047
|
+
async handleListAdapters(res) {
|
|
2048
|
+
const adapters = Array.from(this.core.adapters.entries()).map(([name]) => ({
|
|
2049
|
+
name,
|
|
2050
|
+
type: "built-in"
|
|
2051
|
+
}));
|
|
2052
|
+
this.sendJson(res, 200, { adapters });
|
|
2053
|
+
}
|
|
2054
|
+
async handleTunnelStatus(res) {
|
|
2055
|
+
const tunnel = this.core.tunnelService;
|
|
2056
|
+
if (tunnel) {
|
|
2057
|
+
this.sendJson(res, 200, { enabled: true, url: tunnel.getPublicUrl(), provider: this.core.configManager.get().tunnel.provider });
|
|
2058
|
+
} else {
|
|
2059
|
+
this.sendJson(res, 200, { enabled: false });
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
async handleNotify(req, res) {
|
|
2063
|
+
const body = await this.readBody(req);
|
|
2064
|
+
let message;
|
|
2065
|
+
if (body) {
|
|
2066
|
+
try {
|
|
2067
|
+
const parsed = JSON.parse(body);
|
|
2068
|
+
message = parsed.message;
|
|
2069
|
+
} catch {
|
|
2070
|
+
this.sendJson(res, 400, { error: "Invalid JSON body" });
|
|
2071
|
+
return;
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
if (!message) {
|
|
2075
|
+
this.sendJson(res, 400, { error: "Missing message" });
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
await this.core.notificationManager.notifyAll({
|
|
2079
|
+
sessionId: "system",
|
|
2080
|
+
type: "completed",
|
|
2081
|
+
summary: message
|
|
2082
|
+
});
|
|
2083
|
+
this.sendJson(res, 200, { ok: true });
|
|
2084
|
+
}
|
|
2085
|
+
async handleRestart(res) {
|
|
2086
|
+
if (!this.core.requestRestart) {
|
|
2087
|
+
this.sendJson(res, 501, { error: "Restart not available" });
|
|
2088
|
+
return;
|
|
2089
|
+
}
|
|
2090
|
+
this.sendJson(res, 200, { ok: true, message: "Restarting..." });
|
|
2091
|
+
setImmediate(() => this.core.requestRestart());
|
|
2092
|
+
}
|
|
1690
2093
|
async handleCancelSession(sessionId, res) {
|
|
1691
2094
|
const session = this.core.sessionManager.getSession(sessionId);
|
|
1692
2095
|
if (!session) {
|
|
@@ -1708,34 +2111,180 @@ var ApiServer = class {
|
|
|
1708
2111
|
}))
|
|
1709
2112
|
});
|
|
1710
2113
|
}
|
|
2114
|
+
async handleAdoptSession(req, res) {
|
|
2115
|
+
const body = await this.readBody(req);
|
|
2116
|
+
if (!body) {
|
|
2117
|
+
return this.sendJson(res, 400, { error: "bad_request", message: "Empty request body" });
|
|
2118
|
+
}
|
|
2119
|
+
let parsed;
|
|
2120
|
+
try {
|
|
2121
|
+
parsed = JSON.parse(body);
|
|
2122
|
+
} catch {
|
|
2123
|
+
return this.sendJson(res, 400, { error: "bad_request", message: "Invalid JSON" });
|
|
2124
|
+
}
|
|
2125
|
+
const { agent, agentSessionId, cwd } = parsed;
|
|
2126
|
+
if (!agent || !agentSessionId) {
|
|
2127
|
+
return this.sendJson(res, 400, { error: "bad_request", message: "Missing required fields: agent, agentSessionId" });
|
|
2128
|
+
}
|
|
2129
|
+
const result = await this.core.adoptSession(agent, agentSessionId, cwd ?? process.cwd());
|
|
2130
|
+
if (result.ok) {
|
|
2131
|
+
return this.sendJson(res, 200, result);
|
|
2132
|
+
} else {
|
|
2133
|
+
const status = result.error === "session_limit" ? 429 : result.error === "agent_not_supported" ? 400 : 500;
|
|
2134
|
+
return this.sendJson(res, status, result);
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
1711
2137
|
async handleListAgents(res) {
|
|
1712
2138
|
const agents = this.core.agentManager.getAvailableAgents();
|
|
1713
2139
|
const defaultAgent = this.core.configManager.get().defaultAgent;
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
})),
|
|
1720
|
-
default: defaultAgent
|
|
1721
|
-
});
|
|
2140
|
+
const agentsWithCaps = agents.map((a) => ({
|
|
2141
|
+
...a,
|
|
2142
|
+
capabilities: getAgentCapabilities(a.name)
|
|
2143
|
+
}));
|
|
2144
|
+
this.sendJson(res, 200, { agents: agentsWithCaps, default: defaultAgent });
|
|
1722
2145
|
}
|
|
1723
2146
|
sendJson(res, status, data) {
|
|
1724
2147
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
1725
2148
|
res.end(JSON.stringify(data));
|
|
1726
2149
|
}
|
|
2150
|
+
async handleListTopics(url, res) {
|
|
2151
|
+
if (!this.topicManager) {
|
|
2152
|
+
this.sendJson(res, 501, { error: "Topic management not available" });
|
|
2153
|
+
return;
|
|
2154
|
+
}
|
|
2155
|
+
const params = new URL(url, "http://localhost").searchParams;
|
|
2156
|
+
const statusParam = params.get("status");
|
|
2157
|
+
const filter = statusParam ? { statuses: statusParam.split(",") } : void 0;
|
|
2158
|
+
const topics = this.topicManager.listTopics(filter);
|
|
2159
|
+
this.sendJson(res, 200, { topics });
|
|
2160
|
+
}
|
|
2161
|
+
async handleDeleteTopic(sessionId, url, res) {
|
|
2162
|
+
if (!this.topicManager) {
|
|
2163
|
+
this.sendJson(res, 501, { error: "Topic management not available" });
|
|
2164
|
+
return;
|
|
2165
|
+
}
|
|
2166
|
+
const params = new URL(url, "http://localhost").searchParams;
|
|
2167
|
+
const force = params.get("force") === "true";
|
|
2168
|
+
const result = await this.topicManager.deleteTopic(sessionId, force ? { confirmed: true } : void 0);
|
|
2169
|
+
if (result.ok) {
|
|
2170
|
+
this.sendJson(res, 200, result);
|
|
2171
|
+
} else if (result.needsConfirmation) {
|
|
2172
|
+
this.sendJson(res, 409, { error: "Session is active", needsConfirmation: true, session: result.session });
|
|
2173
|
+
} else if (result.error === "Cannot delete system topic") {
|
|
2174
|
+
this.sendJson(res, 403, { error: result.error });
|
|
2175
|
+
} else {
|
|
2176
|
+
this.sendJson(res, 404, { error: result.error ?? "Not found" });
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
async handleCleanupTopics(req, res) {
|
|
2180
|
+
if (!this.topicManager) {
|
|
2181
|
+
this.sendJson(res, 501, { error: "Topic management not available" });
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
const body = await this.readBody(req);
|
|
2185
|
+
let statuses;
|
|
2186
|
+
if (body) {
|
|
2187
|
+
try {
|
|
2188
|
+
statuses = JSON.parse(body).statuses;
|
|
2189
|
+
} catch {
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
const result = await this.topicManager.cleanup(statuses);
|
|
2193
|
+
this.sendJson(res, 200, result);
|
|
2194
|
+
}
|
|
1727
2195
|
readBody(req) {
|
|
1728
|
-
return new Promise((
|
|
2196
|
+
return new Promise((resolve2) => {
|
|
1729
2197
|
let data = "";
|
|
1730
2198
|
req.on("data", (chunk) => {
|
|
1731
2199
|
data += chunk;
|
|
1732
2200
|
});
|
|
1733
|
-
req.on("end", () =>
|
|
1734
|
-
req.on("error", () =>
|
|
2201
|
+
req.on("end", () => resolve2(data));
|
|
2202
|
+
req.on("error", () => resolve2(""));
|
|
1735
2203
|
});
|
|
1736
2204
|
}
|
|
1737
2205
|
};
|
|
1738
2206
|
|
|
2207
|
+
// src/core/topic-manager.ts
|
|
2208
|
+
var log6 = createChildLogger({ module: "topic-manager" });
|
|
2209
|
+
var TopicManager = class {
|
|
2210
|
+
constructor(sessionManager, adapter, systemTopicIds) {
|
|
2211
|
+
this.sessionManager = sessionManager;
|
|
2212
|
+
this.adapter = adapter;
|
|
2213
|
+
this.systemTopicIds = systemTopicIds;
|
|
2214
|
+
}
|
|
2215
|
+
listTopics(filter) {
|
|
2216
|
+
const records = this.sessionManager.listRecords(filter);
|
|
2217
|
+
return records.filter((r) => !this.isSystemTopic(r)).filter((r) => !filter?.statuses?.length || filter.statuses.includes(r.status)).map((r) => ({
|
|
2218
|
+
sessionId: r.sessionId,
|
|
2219
|
+
topicId: r.platform?.topicId ?? null,
|
|
2220
|
+
name: r.name ?? null,
|
|
2221
|
+
status: r.status,
|
|
2222
|
+
agentName: r.agentName,
|
|
2223
|
+
lastActiveAt: r.lastActiveAt
|
|
2224
|
+
}));
|
|
2225
|
+
}
|
|
2226
|
+
async deleteTopic(sessionId, options) {
|
|
2227
|
+
const records = this.sessionManager.listRecords();
|
|
2228
|
+
const record = records.find((r) => r.sessionId === sessionId);
|
|
2229
|
+
if (!record) return { ok: false, error: "Session not found" };
|
|
2230
|
+
if (this.isSystemTopic(record)) return { ok: false, error: "Cannot delete system topic" };
|
|
2231
|
+
const isActive = record.status === "active" || record.status === "initializing";
|
|
2232
|
+
if (isActive && !options?.confirmed) {
|
|
2233
|
+
return {
|
|
2234
|
+
ok: false,
|
|
2235
|
+
needsConfirmation: true,
|
|
2236
|
+
session: { id: record.sessionId, name: record.name ?? null, status: record.status }
|
|
2237
|
+
};
|
|
2238
|
+
}
|
|
2239
|
+
if (isActive) {
|
|
2240
|
+
await this.sessionManager.cancelSession(sessionId);
|
|
2241
|
+
}
|
|
2242
|
+
const topicId = record.platform?.topicId ?? null;
|
|
2243
|
+
if (this.adapter && topicId) {
|
|
2244
|
+
try {
|
|
2245
|
+
await this.adapter.deleteSessionThread(sessionId);
|
|
2246
|
+
} catch (err) {
|
|
2247
|
+
log6.warn({ err, sessionId, topicId }, "Failed to delete platform thread, removing record anyway");
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
await this.sessionManager.removeRecord(sessionId);
|
|
2251
|
+
return { ok: true, topicId };
|
|
2252
|
+
}
|
|
2253
|
+
async cleanup(statuses) {
|
|
2254
|
+
const targetStatuses = statuses?.length ? statuses : ["finished", "error", "cancelled"];
|
|
2255
|
+
const records = this.sessionManager.listRecords({ statuses: targetStatuses });
|
|
2256
|
+
const targets = records.filter((r) => !this.isSystemTopic(r)).filter((r) => targetStatuses.includes(r.status));
|
|
2257
|
+
const deleted = [];
|
|
2258
|
+
const failed = [];
|
|
2259
|
+
for (const record of targets) {
|
|
2260
|
+
try {
|
|
2261
|
+
const isActive = record.status === "active" || record.status === "initializing";
|
|
2262
|
+
if (isActive) {
|
|
2263
|
+
await this.sessionManager.cancelSession(record.sessionId);
|
|
2264
|
+
}
|
|
2265
|
+
const topicId = record.platform?.topicId;
|
|
2266
|
+
if (this.adapter && topicId) {
|
|
2267
|
+
try {
|
|
2268
|
+
await this.adapter.deleteSessionThread(record.sessionId);
|
|
2269
|
+
} catch (err) {
|
|
2270
|
+
log6.warn({ err, sessionId: record.sessionId }, "Failed to delete platform thread during cleanup");
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
await this.sessionManager.removeRecord(record.sessionId);
|
|
2274
|
+
deleted.push(record.sessionId);
|
|
2275
|
+
} catch (err) {
|
|
2276
|
+
failed.push({ sessionId: record.sessionId, error: err instanceof Error ? err.message : String(err) });
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
return { deleted, failed };
|
|
2280
|
+
}
|
|
2281
|
+
isSystemTopic(record) {
|
|
2282
|
+
const topicId = record.platform?.topicId;
|
|
2283
|
+
if (!topicId) return false;
|
|
2284
|
+
return topicId === this.systemTopicIds.notificationTopicId || topicId === this.systemTopicIds.assistantTopicId;
|
|
2285
|
+
}
|
|
2286
|
+
};
|
|
2287
|
+
|
|
1739
2288
|
// src/adapters/telegram/adapter.ts
|
|
1740
2289
|
import { Bot } from "grammy";
|
|
1741
2290
|
|
|
@@ -2048,7 +2597,7 @@ function buildDeepLink(chatId, messageId) {
|
|
|
2048
2597
|
|
|
2049
2598
|
// src/adapters/telegram/commands.ts
|
|
2050
2599
|
import { InlineKeyboard } from "grammy";
|
|
2051
|
-
var
|
|
2600
|
+
var log8 = createChildLogger({ module: "telegram-commands" });
|
|
2052
2601
|
function setupCommands(bot, core, chatId, assistant) {
|
|
2053
2602
|
bot.command("new", (ctx) => handleNew(ctx, core, chatId, assistant));
|
|
2054
2603
|
bot.command("newchat", (ctx) => handleNewChat(ctx, core, chatId));
|
|
@@ -2122,7 +2671,7 @@ async function handleNew(ctx, core, chatId, assistant) {
|
|
|
2122
2671
|
return;
|
|
2123
2672
|
}
|
|
2124
2673
|
}
|
|
2125
|
-
|
|
2674
|
+
log8.info({ userId: ctx.from?.id, agentName }, "New session command");
|
|
2126
2675
|
let threadId;
|
|
2127
2676
|
try {
|
|
2128
2677
|
const topicName = `\u{1F504} New Session`;
|
|
@@ -2156,9 +2705,9 @@ async function handleNew(ctx, core, chatId, assistant) {
|
|
|
2156
2705
|
reply_markup: buildDangerousModeKeyboard(session.id, false)
|
|
2157
2706
|
}
|
|
2158
2707
|
);
|
|
2159
|
-
session.warmup().catch((err) =>
|
|
2708
|
+
session.warmup().catch((err) => log8.error({ err }, "Warm-up error"));
|
|
2160
2709
|
} catch (err) {
|
|
2161
|
-
|
|
2710
|
+
log8.error({ err }, "Session creation failed");
|
|
2162
2711
|
if (threadId) {
|
|
2163
2712
|
try {
|
|
2164
2713
|
await ctx.api.deleteForumTopic(chatId, threadId);
|
|
@@ -2235,7 +2784,7 @@ async function handleNewChat(ctx, core, chatId) {
|
|
|
2235
2784
|
reply_markup: buildDangerousModeKeyboard(session.id, false)
|
|
2236
2785
|
}
|
|
2237
2786
|
);
|
|
2238
|
-
session.warmup().catch((err) =>
|
|
2787
|
+
session.warmup().catch((err) => log8.error({ err }, "Warm-up error"));
|
|
2239
2788
|
} catch (err) {
|
|
2240
2789
|
if (newThreadId) {
|
|
2241
2790
|
try {
|
|
@@ -2264,14 +2813,14 @@ async function handleCancel(ctx, core, assistant) {
|
|
|
2264
2813
|
String(threadId)
|
|
2265
2814
|
);
|
|
2266
2815
|
if (session) {
|
|
2267
|
-
|
|
2816
|
+
log8.info({ sessionId: session.id }, "Cancel session command");
|
|
2268
2817
|
await session.cancel();
|
|
2269
2818
|
await ctx.reply("\u26D4 Session cancelled.", { parse_mode: "HTML" });
|
|
2270
2819
|
return;
|
|
2271
2820
|
}
|
|
2272
2821
|
const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
|
|
2273
2822
|
if (record && record.status !== "cancelled" && record.status !== "error") {
|
|
2274
|
-
|
|
2823
|
+
log8.info({ sessionId: record.sessionId }, "Cancel session command (from store)");
|
|
2275
2824
|
await core.sessionManager.cancelSession(record.sessionId);
|
|
2276
2825
|
await ctx.reply("\u26D4 Session cancelled.", { parse_mode: "HTML" });
|
|
2277
2826
|
}
|
|
@@ -2365,7 +2914,7 @@ function setupDangerousModeCallbacks(bot, core) {
|
|
|
2365
2914
|
const session = core.sessionManager.getSession(sessionId);
|
|
2366
2915
|
if (session) {
|
|
2367
2916
|
session.dangerousMode = !session.dangerousMode;
|
|
2368
|
-
|
|
2917
|
+
log8.info({ sessionId, dangerousMode: session.dangerousMode }, "Dangerous mode toggled via button");
|
|
2369
2918
|
core.sessionManager.updateSessionDangerousMode(sessionId, session.dangerousMode).catch(() => {
|
|
2370
2919
|
});
|
|
2371
2920
|
const toastText2 = session.dangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
|
|
@@ -2392,7 +2941,7 @@ function setupDangerousModeCallbacks(bot, core) {
|
|
|
2392
2941
|
const newDangerousMode = !(record.dangerousMode ?? false);
|
|
2393
2942
|
core.sessionManager.updateSessionDangerousMode(sessionId, newDangerousMode).catch(() => {
|
|
2394
2943
|
});
|
|
2395
|
-
|
|
2944
|
+
log8.info({ sessionId, dangerousMode: newDangerousMode }, "Dangerous mode toggled via button (store-only, session not in memory)");
|
|
2396
2945
|
const toastText = newDangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
|
|
2397
2946
|
try {
|
|
2398
2947
|
await ctx.answerCallbackQuery({ text: toastText });
|
|
@@ -2560,7 +3109,7 @@ async function executeNewSession(bot, core, chatId, agentName, workspace) {
|
|
|
2560
3109
|
});
|
|
2561
3110
|
const finalName = `\u{1F504} ${session.agentName} \u2014 New Session`;
|
|
2562
3111
|
await renameSessionTopic(bot, chatId, threadId, finalName);
|
|
2563
|
-
session.warmup().catch((err) =>
|
|
3112
|
+
session.warmup().catch((err) => log8.error({ err }, "Warm-up error"));
|
|
2564
3113
|
return { session, threadId, firstMsgId };
|
|
2565
3114
|
} catch (err) {
|
|
2566
3115
|
try {
|
|
@@ -2594,7 +3143,7 @@ var STATIC_COMMANDS = [
|
|
|
2594
3143
|
// src/adapters/telegram/permissions.ts
|
|
2595
3144
|
import { InlineKeyboard as InlineKeyboard2 } from "grammy";
|
|
2596
3145
|
import { nanoid as nanoid2 } from "nanoid";
|
|
2597
|
-
var
|
|
3146
|
+
var log9 = createChildLogger({ module: "telegram-permissions" });
|
|
2598
3147
|
var PermissionHandler = class {
|
|
2599
3148
|
constructor(bot, chatId, getSession, sendNotification) {
|
|
2600
3149
|
this.bot = bot;
|
|
@@ -2654,7 +3203,7 @@ ${escapeHtml(request.description)}`,
|
|
|
2654
3203
|
}
|
|
2655
3204
|
const session = this.getSession(pending.sessionId);
|
|
2656
3205
|
const isAllow = pending.options.find((o) => o.id === optionId)?.isAllow ?? false;
|
|
2657
|
-
|
|
3206
|
+
log9.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
|
|
2658
3207
|
if (session?.permissionGate.requestId === pending.requestId) {
|
|
2659
3208
|
session.permissionGate.resolve(optionId);
|
|
2660
3209
|
}
|
|
@@ -2672,10 +3221,10 @@ ${escapeHtml(request.description)}`,
|
|
|
2672
3221
|
};
|
|
2673
3222
|
|
|
2674
3223
|
// src/adapters/telegram/assistant.ts
|
|
2675
|
-
var
|
|
3224
|
+
var log10 = createChildLogger({ module: "telegram-assistant" });
|
|
2676
3225
|
async function spawnAssistant(core, adapter, assistantTopicId) {
|
|
2677
3226
|
const config = core.configManager.get();
|
|
2678
|
-
|
|
3227
|
+
log10.info({ agent: config.defaultAgent }, "Creating assistant session...");
|
|
2679
3228
|
const session = await core.sessionManager.createSession(
|
|
2680
3229
|
"telegram",
|
|
2681
3230
|
config.defaultAgent,
|
|
@@ -2684,30 +3233,44 @@ async function spawnAssistant(core, adapter, assistantTopicId) {
|
|
|
2684
3233
|
);
|
|
2685
3234
|
session.threadId = String(assistantTopicId);
|
|
2686
3235
|
session.name = "Assistant";
|
|
2687
|
-
|
|
3236
|
+
log10.info({ sessionId: session.id }, "Assistant agent spawned");
|
|
2688
3237
|
core.wireSessionEvents(session, adapter);
|
|
2689
|
-
const
|
|
3238
|
+
const allRecords = core.sessionManager.listRecords();
|
|
3239
|
+
const activeCount = allRecords.filter((r) => r.status === "active" || r.status === "initializing").length;
|
|
3240
|
+
const statusCounts = /* @__PURE__ */ new Map();
|
|
3241
|
+
for (const r of allRecords) {
|
|
3242
|
+
statusCounts.set(r.status, (statusCounts.get(r.status) ?? 0) + 1);
|
|
3243
|
+
}
|
|
3244
|
+
const topicSummary = Array.from(statusCounts.entries()).map(([status, count]) => ({ status, count }));
|
|
3245
|
+
const ctx = {
|
|
3246
|
+
config,
|
|
3247
|
+
activeSessionCount: activeCount,
|
|
3248
|
+
totalSessionCount: allRecords.length,
|
|
3249
|
+
topicSummary
|
|
3250
|
+
};
|
|
3251
|
+
const systemPrompt = buildAssistantSystemPrompt(ctx);
|
|
2690
3252
|
const ready = session.enqueuePrompt(systemPrompt).then(() => {
|
|
2691
|
-
|
|
3253
|
+
log10.info({ sessionId: session.id }, "Assistant system prompt completed");
|
|
2692
3254
|
}).catch((err) => {
|
|
2693
|
-
|
|
3255
|
+
log10.warn({ err }, "Assistant system prompt failed");
|
|
2694
3256
|
});
|
|
2695
3257
|
return { session, ready };
|
|
2696
3258
|
}
|
|
2697
|
-
function buildAssistantSystemPrompt(
|
|
3259
|
+
function buildAssistantSystemPrompt(ctx) {
|
|
3260
|
+
const { config, activeSessionCount, totalSessionCount, topicSummary } = ctx;
|
|
2698
3261
|
const agentNames = Object.keys(config.agents).join(", ");
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
Available agents: ${agentNames}
|
|
2702
|
-
Default agent: ${config.defaultAgent}
|
|
2703
|
-
Workspace base: ${config.workspace.baseDir}
|
|
3262
|
+
const topicBreakdown = topicSummary.map((s) => `${s.status}: ${s.count}`).join(", ") || "none";
|
|
3263
|
+
return `You are the OpenACP Assistant. Help users manage their AI coding sessions and topics.
|
|
2704
3264
|
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
3265
|
+
## Current State
|
|
3266
|
+
- Active sessions: ${activeSessionCount} / ${totalSessionCount} total
|
|
3267
|
+
- Topics by status: ${topicBreakdown}
|
|
3268
|
+
- Available agents: ${agentNames}
|
|
3269
|
+
- Default agent: ${config.defaultAgent}
|
|
3270
|
+
- Workspace base: ${config.workspace.baseDir}
|
|
2709
3271
|
|
|
2710
|
-
Commands
|
|
3272
|
+
## Session Management Commands
|
|
3273
|
+
These are Telegram bot commands (type directly in chat):
|
|
2711
3274
|
- /new [agent] [workspace] \u2014 Create new session
|
|
2712
3275
|
- /newchat \u2014 New chat with same agent & workspace
|
|
2713
3276
|
- /cancel \u2014 Cancel current session
|
|
@@ -2715,7 +3278,48 @@ Commands reference:
|
|
|
2715
3278
|
- /agents \u2014 List agents
|
|
2716
3279
|
- /help \u2014 Show help
|
|
2717
3280
|
|
|
2718
|
-
|
|
3281
|
+
## Management Commands (via CLI)
|
|
3282
|
+
You have access to bash. Use these commands to manage OpenACP:
|
|
3283
|
+
|
|
3284
|
+
### Session management
|
|
3285
|
+
\`\`\`bash
|
|
3286
|
+
openacp api status # List active sessions
|
|
3287
|
+
openacp api session <id> # Session detail
|
|
3288
|
+
openacp api send <id> "prompt text" # Send prompt to session
|
|
3289
|
+
openacp api cancel <id> # Cancel session
|
|
3290
|
+
openacp api dangerous <id> on|off # Toggle dangerous mode
|
|
3291
|
+
\`\`\`
|
|
3292
|
+
|
|
3293
|
+
### Topic management
|
|
3294
|
+
\`\`\`bash
|
|
3295
|
+
openacp api topics # List topics
|
|
3296
|
+
openacp api topics --status finished,error
|
|
3297
|
+
openacp api delete-topic <id> # Delete topic
|
|
3298
|
+
openacp api delete-topic <id> --force # Force delete active
|
|
3299
|
+
openacp api cleanup # Cleanup finished topics
|
|
3300
|
+
openacp api cleanup --status finished,error
|
|
3301
|
+
\`\`\`
|
|
3302
|
+
|
|
3303
|
+
### System
|
|
3304
|
+
\`\`\`bash
|
|
3305
|
+
openacp api health # System health
|
|
3306
|
+
openacp api config # Show config
|
|
3307
|
+
openacp api config set <key> <value> # Update config
|
|
3308
|
+
openacp api adapters # List adapters
|
|
3309
|
+
openacp api tunnel # Tunnel status
|
|
3310
|
+
openacp api notify "message" # Send notification
|
|
3311
|
+
openacp api version # Daemon version
|
|
3312
|
+
openacp api restart # Restart daemon
|
|
3313
|
+
\`\`\`
|
|
3314
|
+
|
|
3315
|
+
## Guidelines
|
|
3316
|
+
- When a user asks about sessions or topics, run \`openacp api topics\` or \`openacp api status\` to get current data.
|
|
3317
|
+
- When deleting: if the session is active/initializing, warn the user first. Only use --force if they confirm.
|
|
3318
|
+
- Use \`openacp api health\` to check system status.
|
|
3319
|
+
- Use \`openacp api config\` to check configuration, \`openacp api config set\` to update values.
|
|
3320
|
+
- Format responses nicely for Telegram (use bold, code blocks).
|
|
3321
|
+
- Be concise and helpful. Respond in the same language the user uses.
|
|
3322
|
+
- When creating sessions, guide through: agent selection \u2192 workspace \u2192 confirm.`;
|
|
2719
3323
|
}
|
|
2720
3324
|
async function handleAssistantMessage(session, text) {
|
|
2721
3325
|
if (!session) return;
|
|
@@ -2728,7 +3332,7 @@ function redirectToAssistant(chatId, assistantTopicId) {
|
|
|
2728
3332
|
}
|
|
2729
3333
|
|
|
2730
3334
|
// src/adapters/telegram/activity.ts
|
|
2731
|
-
var
|
|
3335
|
+
var log11 = createChildLogger({ module: "telegram:activity" });
|
|
2732
3336
|
var THINKING_REFRESH_MS = 15e3;
|
|
2733
3337
|
var THINKING_MAX_MS = 3 * 60 * 1e3;
|
|
2734
3338
|
var ThinkingIndicator = class {
|
|
@@ -2760,7 +3364,7 @@ var ThinkingIndicator = class {
|
|
|
2760
3364
|
this.startRefreshTimer();
|
|
2761
3365
|
}
|
|
2762
3366
|
} catch (err) {
|
|
2763
|
-
|
|
3367
|
+
log11.warn({ err }, "ThinkingIndicator.show() failed");
|
|
2764
3368
|
} finally {
|
|
2765
3369
|
this.sending = false;
|
|
2766
3370
|
}
|
|
@@ -2833,7 +3437,7 @@ var UsageMessage = class {
|
|
|
2833
3437
|
if (result) this.msgId = result.message_id;
|
|
2834
3438
|
}
|
|
2835
3439
|
} catch (err) {
|
|
2836
|
-
|
|
3440
|
+
log11.warn({ err }, "UsageMessage.send() failed");
|
|
2837
3441
|
}
|
|
2838
3442
|
}
|
|
2839
3443
|
getMsgId() {
|
|
@@ -2846,7 +3450,7 @@ var UsageMessage = class {
|
|
|
2846
3450
|
try {
|
|
2847
3451
|
await this.sendQueue.enqueue(() => this.api.deleteMessage(this.chatId, id));
|
|
2848
3452
|
} catch (err) {
|
|
2849
|
-
|
|
3453
|
+
log11.warn({ err }, "UsageMessage.delete() failed");
|
|
2850
3454
|
}
|
|
2851
3455
|
}
|
|
2852
3456
|
};
|
|
@@ -2881,6 +3485,7 @@ var PlanCard = class {
|
|
|
2881
3485
|
msgId;
|
|
2882
3486
|
flushPromise = Promise.resolve();
|
|
2883
3487
|
latestEntries;
|
|
3488
|
+
lastSentText;
|
|
2884
3489
|
flushTimer;
|
|
2885
3490
|
update(entries) {
|
|
2886
3491
|
this.latestEntries = entries;
|
|
@@ -2911,6 +3516,8 @@ var PlanCard = class {
|
|
|
2911
3516
|
async _flush() {
|
|
2912
3517
|
if (!this.latestEntries) return;
|
|
2913
3518
|
const text = formatPlanCard(this.latestEntries);
|
|
3519
|
+
if (this.msgId && text === this.lastSentText) return;
|
|
3520
|
+
this.lastSentText = text;
|
|
2914
3521
|
try {
|
|
2915
3522
|
if (this.msgId) {
|
|
2916
3523
|
await this.sendQueue.enqueue(
|
|
@@ -2929,7 +3536,7 @@ var PlanCard = class {
|
|
|
2929
3536
|
if (result) this.msgId = result.message_id;
|
|
2930
3537
|
}
|
|
2931
3538
|
} catch (err) {
|
|
2932
|
-
|
|
3539
|
+
log11.warn({ err }, "PlanCard flush failed");
|
|
2933
3540
|
}
|
|
2934
3541
|
}
|
|
2935
3542
|
};
|
|
@@ -2992,7 +3599,7 @@ var ActivityTracker = class {
|
|
|
2992
3599
|
})
|
|
2993
3600
|
);
|
|
2994
3601
|
} catch (err) {
|
|
2995
|
-
|
|
3602
|
+
log11.warn({ err }, "ActivityTracker.onComplete() Done send failed");
|
|
2996
3603
|
}
|
|
2997
3604
|
}
|
|
2998
3605
|
}
|
|
@@ -3019,19 +3626,19 @@ var TelegramSendQueue = class {
|
|
|
3019
3626
|
enqueue(fn, opts) {
|
|
3020
3627
|
const type = opts?.type ?? "other";
|
|
3021
3628
|
const key = opts?.key;
|
|
3022
|
-
return new Promise((
|
|
3629
|
+
return new Promise((resolve2, reject) => {
|
|
3023
3630
|
if (type === "text" && key) {
|
|
3024
3631
|
const idx = this.items.findIndex(
|
|
3025
3632
|
(item) => item.type === "text" && item.key === key
|
|
3026
3633
|
);
|
|
3027
3634
|
if (idx !== -1) {
|
|
3028
3635
|
this.items[idx].resolve(void 0);
|
|
3029
|
-
this.items[idx] = { fn, type, key, resolve, reject };
|
|
3636
|
+
this.items[idx] = { fn, type, key, resolve: resolve2, reject };
|
|
3030
3637
|
this.scheduleProcess();
|
|
3031
3638
|
return;
|
|
3032
3639
|
}
|
|
3033
3640
|
}
|
|
3034
|
-
this.items.push({ fn, type, key, resolve, reject });
|
|
3641
|
+
this.items.push({ fn, type, key, resolve: resolve2, reject });
|
|
3035
3642
|
this.scheduleProcess();
|
|
3036
3643
|
});
|
|
3037
3644
|
}
|
|
@@ -3219,7 +3826,7 @@ function setupActionCallbacks(bot, core, chatId, getAssistantSessionId) {
|
|
|
3219
3826
|
}
|
|
3220
3827
|
|
|
3221
3828
|
// src/adapters/telegram/adapter.ts
|
|
3222
|
-
var
|
|
3829
|
+
var log12 = createChildLogger({ module: "telegram" });
|
|
3223
3830
|
function patchedFetch(input, init) {
|
|
3224
3831
|
if (init?.signal && !(init.signal instanceof AbortSignal)) {
|
|
3225
3832
|
const nativeController = new AbortController();
|
|
@@ -3270,7 +3877,7 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
3270
3877
|
this.bot = new Bot(this.telegramConfig.botToken, { client: { fetch: patchedFetch } });
|
|
3271
3878
|
this.bot.catch((err) => {
|
|
3272
3879
|
const rootCause = err.error instanceof Error ? err.error : err;
|
|
3273
|
-
|
|
3880
|
+
log12.error({ err: rootCause }, "Telegram bot error");
|
|
3274
3881
|
});
|
|
3275
3882
|
this.bot.api.config.use(async (prev, method, payload, signal) => {
|
|
3276
3883
|
const maxRetries = 3;
|
|
@@ -3284,7 +3891,7 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
3284
3891
|
if (rateLimitedMethods.includes(method)) {
|
|
3285
3892
|
this.sendQueue.onRateLimited();
|
|
3286
3893
|
}
|
|
3287
|
-
|
|
3894
|
+
log12.warn(
|
|
3288
3895
|
{ method, retryAfter, attempt: attempt + 1 },
|
|
3289
3896
|
"Rate limited by Telegram, retrying"
|
|
3290
3897
|
);
|
|
@@ -3350,10 +3957,46 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
3350
3957
|
}
|
|
3351
3958
|
);
|
|
3352
3959
|
this.permissionHandler.setupCallbackHandler();
|
|
3960
|
+
this.bot.command("handoff", async (ctx) => {
|
|
3961
|
+
const threadId = ctx.message?.message_thread_id;
|
|
3962
|
+
if (!threadId) return;
|
|
3963
|
+
if (threadId === this.notificationTopicId || threadId === this.assistantTopicId) {
|
|
3964
|
+
await ctx.reply("This command only works in session topics.", {
|
|
3965
|
+
message_thread_id: threadId
|
|
3966
|
+
});
|
|
3967
|
+
return;
|
|
3968
|
+
}
|
|
3969
|
+
const session = this.core.sessionManager.getSessionByThread("telegram", String(threadId));
|
|
3970
|
+
if (!session) {
|
|
3971
|
+
await ctx.reply("No active session in this topic.", {
|
|
3972
|
+
message_thread_id: threadId
|
|
3973
|
+
});
|
|
3974
|
+
return;
|
|
3975
|
+
}
|
|
3976
|
+
const { getAgentCapabilities: getAgentCapabilities2 } = await import("./agent-registry-7HC6D4CH.js");
|
|
3977
|
+
const caps = getAgentCapabilities2(session.agentName);
|
|
3978
|
+
if (!caps.supportsResume || !caps.resumeCommand) {
|
|
3979
|
+
await ctx.reply("This agent does not support CLI handoff.", {
|
|
3980
|
+
message_thread_id: threadId
|
|
3981
|
+
});
|
|
3982
|
+
return;
|
|
3983
|
+
}
|
|
3984
|
+
const agentSessionId = session.agentSessionId;
|
|
3985
|
+
const command = caps.resumeCommand(agentSessionId);
|
|
3986
|
+
await ctx.reply(
|
|
3987
|
+
`Resume this session on CLI:
|
|
3988
|
+
|
|
3989
|
+
<code>${command}</code>`,
|
|
3990
|
+
{
|
|
3991
|
+
message_thread_id: threadId,
|
|
3992
|
+
parse_mode: "HTML"
|
|
3993
|
+
}
|
|
3994
|
+
);
|
|
3995
|
+
});
|
|
3353
3996
|
this.setupRoutes();
|
|
3354
3997
|
this.bot.start({
|
|
3355
3998
|
allowed_updates: ["message", "callback_query"],
|
|
3356
|
-
onStart: () =>
|
|
3999
|
+
onStart: () => log12.info(
|
|
3357
4000
|
{ chatId: this.telegramConfig.chatId },
|
|
3358
4001
|
"Telegram bot started"
|
|
3359
4002
|
)
|
|
@@ -3375,10 +4018,10 @@ Workspace: <code>${workspace}</code>
|
|
|
3375
4018
|
reply_markup: buildMenuKeyboard()
|
|
3376
4019
|
});
|
|
3377
4020
|
} catch (err) {
|
|
3378
|
-
|
|
4021
|
+
log12.warn({ err }, "Failed to send welcome message");
|
|
3379
4022
|
}
|
|
3380
4023
|
try {
|
|
3381
|
-
|
|
4024
|
+
log12.info("Spawning assistant session...");
|
|
3382
4025
|
const { session, ready } = await spawnAssistant(
|
|
3383
4026
|
this.core,
|
|
3384
4027
|
this,
|
|
@@ -3386,13 +4029,13 @@ Workspace: <code>${workspace}</code>
|
|
|
3386
4029
|
);
|
|
3387
4030
|
this.assistantSession = session;
|
|
3388
4031
|
this.assistantInitializing = true;
|
|
3389
|
-
|
|
4032
|
+
log12.info({ sessionId: session.id }, "Assistant session ready, system prompt running in background");
|
|
3390
4033
|
ready.then(() => {
|
|
3391
4034
|
this.assistantInitializing = false;
|
|
3392
|
-
|
|
4035
|
+
log12.info({ sessionId: session.id }, "Assistant ready for user messages");
|
|
3393
4036
|
});
|
|
3394
4037
|
} catch (err) {
|
|
3395
|
-
|
|
4038
|
+
log12.error({ err }, "Failed to spawn assistant");
|
|
3396
4039
|
this.bot.api.sendMessage(
|
|
3397
4040
|
this.telegramConfig.chatId,
|
|
3398
4041
|
`\u26A0\uFE0F <b>Failed to start assistant session.</b>
|
|
@@ -3408,7 +4051,7 @@ Workspace: <code>${workspace}</code>
|
|
|
3408
4051
|
await this.assistantSession.destroy();
|
|
3409
4052
|
}
|
|
3410
4053
|
await this.bot.stop();
|
|
3411
|
-
|
|
4054
|
+
log12.info("Telegram bot stopped");
|
|
3412
4055
|
}
|
|
3413
4056
|
setupRoutes() {
|
|
3414
4057
|
this.bot.on("message:text", async (ctx) => {
|
|
@@ -3431,7 +4074,7 @@ Workspace: <code>${workspace}</code>
|
|
|
3431
4074
|
ctx.replyWithChatAction("typing").catch(() => {
|
|
3432
4075
|
});
|
|
3433
4076
|
handleAssistantMessage(this.assistantSession, ctx.message.text).catch(
|
|
3434
|
-
(err) =>
|
|
4077
|
+
(err) => log12.error({ err }, "Assistant error")
|
|
3435
4078
|
);
|
|
3436
4079
|
return;
|
|
3437
4080
|
}
|
|
@@ -3448,7 +4091,7 @@ Workspace: <code>${workspace}</code>
|
|
|
3448
4091
|
threadId: String(threadId),
|
|
3449
4092
|
userId: String(ctx.from.id),
|
|
3450
4093
|
text: ctx.message.text
|
|
3451
|
-
}).catch((err) =>
|
|
4094
|
+
}).catch((err) => log12.error({ err }, "handleMessage error"));
|
|
3452
4095
|
});
|
|
3453
4096
|
}
|
|
3454
4097
|
// --- ChannelAdapter implementations ---
|
|
@@ -3528,7 +4171,7 @@ Workspace: <code>${workspace}</code>
|
|
|
3528
4171
|
if (toolState) {
|
|
3529
4172
|
if (meta.viewerLinks) {
|
|
3530
4173
|
toolState.viewerLinks = meta.viewerLinks;
|
|
3531
|
-
|
|
4174
|
+
log12.debug({ toolId: meta.id, viewerLinks: meta.viewerLinks }, "Accumulated viewerLinks");
|
|
3532
4175
|
}
|
|
3533
4176
|
const viewerFilePath = content.metadata?.viewerFilePath;
|
|
3534
4177
|
if (viewerFilePath) toolState.viewerFilePath = viewerFilePath;
|
|
@@ -3537,7 +4180,7 @@ Workspace: <code>${workspace}</code>
|
|
|
3537
4180
|
const isTerminal = meta.status === "completed" || meta.status === "failed";
|
|
3538
4181
|
if (!isTerminal && !meta.viewerLinks) break;
|
|
3539
4182
|
await toolState.ready;
|
|
3540
|
-
|
|
4183
|
+
log12.debug(
|
|
3541
4184
|
{ toolId: meta.id, status: meta.status, hasViewerLinks: !!toolState.viewerLinks, viewerLinks: toolState.viewerLinks, name: toolState.name, msgId: toolState.msgId },
|
|
3542
4185
|
"Tool completed, preparing edit"
|
|
3543
4186
|
);
|
|
@@ -3559,7 +4202,7 @@ Workspace: <code>${workspace}</code>
|
|
|
3559
4202
|
)
|
|
3560
4203
|
);
|
|
3561
4204
|
} catch (err) {
|
|
3562
|
-
|
|
4205
|
+
log12.warn(
|
|
3563
4206
|
{ err, msgId: toolState.msgId, textLen: formattedText.length, hasViewerLinks: !!merged.viewerLinks },
|
|
3564
4207
|
"Tool update edit failed"
|
|
3565
4208
|
);
|
|
@@ -3654,15 +4297,23 @@ Task completed.
|
|
|
3654
4297
|
}
|
|
3655
4298
|
}
|
|
3656
4299
|
async sendPermissionRequest(sessionId, request) {
|
|
3657
|
-
|
|
4300
|
+
log12.info({ sessionId, requestId: request.id }, "Permission request sent");
|
|
3658
4301
|
const session = this.core.sessionManager.getSession(
|
|
3659
4302
|
sessionId
|
|
3660
4303
|
);
|
|
3661
4304
|
if (!session) return;
|
|
4305
|
+
if (request.description.includes("openacp")) {
|
|
4306
|
+
const allowOption = request.options.find((o) => o.isAllow);
|
|
4307
|
+
if (allowOption && session.permissionGate.requestId === request.id) {
|
|
4308
|
+
log12.info({ sessionId, requestId: request.id }, "Auto-approving openacp command");
|
|
4309
|
+
session.permissionGate.resolve(allowOption.id);
|
|
4310
|
+
}
|
|
4311
|
+
return;
|
|
4312
|
+
}
|
|
3662
4313
|
if (session.dangerousMode) {
|
|
3663
4314
|
const allowOption = request.options.find((o) => o.isAllow);
|
|
3664
4315
|
if (allowOption && session.permissionGate.requestId === request.id) {
|
|
3665
|
-
|
|
4316
|
+
log12.info({ sessionId, requestId: request.id, optionId: allowOption.id }, "Dangerous mode: auto-approving permission");
|
|
3666
4317
|
session.permissionGate.resolve(allowOption.id);
|
|
3667
4318
|
}
|
|
3668
4319
|
return;
|
|
@@ -3673,7 +4324,7 @@ Task completed.
|
|
|
3673
4324
|
}
|
|
3674
4325
|
async sendNotification(notification) {
|
|
3675
4326
|
if (notification.sessionId === this.assistantSession?.id) return;
|
|
3676
|
-
|
|
4327
|
+
log12.info(
|
|
3677
4328
|
{ sessionId: notification.sessionId, type: notification.type },
|
|
3678
4329
|
"Notification sent"
|
|
3679
4330
|
);
|
|
@@ -3709,7 +4360,7 @@ Task completed.
|
|
|
3709
4360
|
);
|
|
3710
4361
|
}
|
|
3711
4362
|
async createSessionThread(sessionId, name) {
|
|
3712
|
-
|
|
4363
|
+
log12.info({ sessionId, name }, "Session topic created");
|
|
3713
4364
|
return String(
|
|
3714
4365
|
await createSessionTopic(this.bot, this.telegramConfig.chatId, name)
|
|
3715
4366
|
);
|
|
@@ -3730,6 +4381,17 @@ Task completed.
|
|
|
3730
4381
|
newName
|
|
3731
4382
|
);
|
|
3732
4383
|
}
|
|
4384
|
+
async deleteSessionThread(sessionId) {
|
|
4385
|
+
const record = this.core.sessionManager.getSessionRecord(sessionId);
|
|
4386
|
+
const platform = record?.platform;
|
|
4387
|
+
const topicId = platform?.topicId;
|
|
4388
|
+
if (!topicId) return;
|
|
4389
|
+
try {
|
|
4390
|
+
await this.bot.api.deleteForumTopic(this.telegramConfig.chatId, topicId);
|
|
4391
|
+
} catch (err) {
|
|
4392
|
+
log12.warn({ err, sessionId, topicId }, "Failed to delete forum topic (may already be deleted)");
|
|
4393
|
+
}
|
|
4394
|
+
}
|
|
3733
4395
|
async sendSkillCommands(sessionId, commands) {
|
|
3734
4396
|
if (sessionId === this.assistantSession?.id) return;
|
|
3735
4397
|
const session = this.core.sessionManager.getSession(sessionId);
|
|
@@ -3800,7 +4462,7 @@ Task completed.
|
|
|
3800
4462
|
{ disable_notification: true }
|
|
3801
4463
|
);
|
|
3802
4464
|
} catch (err) {
|
|
3803
|
-
|
|
4465
|
+
log12.error({ err, sessionId }, "Failed to send skill commands");
|
|
3804
4466
|
}
|
|
3805
4467
|
}
|
|
3806
4468
|
async cleanupSkillCommands(sessionId) {
|
|
@@ -3868,6 +4530,7 @@ export {
|
|
|
3868
4530
|
OpenACPCore,
|
|
3869
4531
|
ChannelAdapter,
|
|
3870
4532
|
ApiServer,
|
|
4533
|
+
TopicManager,
|
|
3871
4534
|
TelegramAdapter
|
|
3872
4535
|
};
|
|
3873
|
-
//# sourceMappingURL=chunk-
|
|
4536
|
+
//# sourceMappingURL=chunk-2M4O7AFI.js.map
|