@openacp/cli 0.5.2 → 0.6.0
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 +40 -14
- package/dist/action-detect-6M5GCGAU.js +15 -0
- package/dist/admin-IKPS5PFC.js +16 -0
- package/dist/agents-55NX3DHM.js +14 -0
- package/dist/{api-client-UN7BXQOQ.js → api-client-BH2JFHQW.js} +4 -2
- package/dist/{autostart-K73RQZVV.js → autostart-A7JRU4WJ.js} +6 -2
- package/dist/chunk-3WPG7GXA.js +134 -0
- package/dist/chunk-3WPG7GXA.js.map +1 -0
- package/dist/{chunk-NDR5JCS7.js → chunk-437NLISU.js} +2 -2
- package/dist/chunk-5NBWM7P6.js +438 -0
- package/dist/chunk-5NBWM7P6.js.map +1 -0
- package/dist/{chunk-F4TB4UBK.js → chunk-6Q7PZWCL.js} +171 -26
- package/dist/chunk-6Q7PZWCL.js.map +1 -0
- package/dist/chunk-7G5QKLLF.js +105 -0
- package/dist/chunk-7G5QKLLF.js.map +1 -0
- package/dist/chunk-AKIU4JBF.js +145 -0
- package/dist/chunk-AKIU4JBF.js.map +1 -0
- package/dist/{chunk-JRF4G4X7.js → chunk-DWQKUECJ.js} +13 -2
- package/dist/chunk-DWQKUECJ.js.map +1 -0
- package/dist/{chunk-KSIQZC3J.js → chunk-EVFJW45N.js} +1 -1
- package/dist/chunk-EVFJW45N.js.map +1 -0
- package/dist/chunk-H7ZMPBZC.js +203 -0
- package/dist/chunk-H7ZMPBZC.js.map +1 -0
- package/dist/chunk-I7WC6E5S.js +71 -0
- package/dist/chunk-I7WC6E5S.js.map +1 -0
- package/dist/{chunk-4ZGMSNRP.js → chunk-MHFCZGRW.js} +114 -16
- package/dist/chunk-MHFCZGRW.js.map +1 -0
- package/dist/{chunk-X6LLG7XN.js → chunk-PMGNLNSH.js} +15 -6
- package/dist/chunk-PMGNLNSH.js.map +1 -0
- package/dist/chunk-SM3G6UAX.js +122 -0
- package/dist/chunk-SM3G6UAX.js.map +1 -0
- package/dist/{chunk-IRGYTNLP.js → chunk-SPX7CKWV.js} +76 -2
- package/dist/chunk-SPX7CKWV.js.map +1 -0
- package/dist/chunk-T22OLSET.js +265 -0
- package/dist/chunk-T22OLSET.js.map +1 -0
- package/dist/chunk-THBR6OXH.js +62 -0
- package/dist/chunk-THBR6OXH.js.map +1 -0
- package/dist/{chunk-65XE66HK.js → chunk-V2V767XI.js} +373 -489
- package/dist/chunk-V2V767XI.js.map +1 -0
- package/dist/{chunk-OORPX73T.js → chunk-W3EYKZNQ.js} +17 -2
- package/dist/chunk-W3EYKZNQ.js.map +1 -0
- package/dist/{chunk-VBEWSWVL.js → chunk-YYQXWA62.js} +52 -9
- package/dist/chunk-YYQXWA62.js.map +1 -0
- package/dist/cli.js +121 -32
- package/dist/cli.js.map +1 -1
- package/dist/{config-PCPIBPUA.js → config-KF2MQWAP.js} +2 -2
- package/dist/config-editor-OTODXUF7.js +12 -0
- package/dist/{daemon-JZLFRUW6.js → daemon-U6UC7OM4.js} +3 -3
- package/dist/discord-SLLKRUP7.js +2034 -0
- package/dist/discord-SLLKRUP7.js.map +1 -0
- package/dist/doctor-DB5PRQ6D.js +14 -0
- package/dist/doctor-DB5PRQ6D.js.map +1 -0
- package/dist/doctor-SYWNJFYK.js +9 -0
- package/dist/doctor-SYWNJFYK.js.map +1 -0
- package/dist/index.d.ts +47 -7
- package/dist/index.js +11 -9
- package/dist/{integrate-BLETI3UO.js → integrate-VOUYBPPZ.js} +106 -3
- package/dist/integrate-VOUYBPPZ.js.map +1 -0
- package/dist/{main-AH3NCVM3.js → main-M6RH3SS5.js} +31 -23
- package/dist/main-M6RH3SS5.js.map +1 -0
- package/dist/new-session-DRRP2J7E.js +16 -0
- package/dist/new-session-DRRP2J7E.js.map +1 -0
- package/dist/session-FVFLBREJ.js +19 -0
- package/dist/session-FVFLBREJ.js.map +1 -0
- package/dist/settings-LPOLJ6SA.js +12 -0
- package/dist/settings-LPOLJ6SA.js.map +1 -0
- package/dist/{setup-7JINXQOA.js → setup-LI5CKYDK.js} +9 -5
- package/dist/setup-LI5CKYDK.js.map +1 -0
- package/dist/{tunnel-service-LEVPLXAZ.js → tunnel-service-U6V4HQOO.js} +263 -47
- package/dist/tunnel-service-U6V4HQOO.js.map +1 -0
- package/dist/{version-VC5CPXBX.js → version-ALWGGVKM.js} +2 -2
- package/dist/version-ALWGGVKM.js.map +1 -0
- package/package.json +2 -1
- package/dist/chunk-4ZGMSNRP.js.map +0 -1
- package/dist/chunk-65XE66HK.js.map +0 -1
- package/dist/chunk-F4TB4UBK.js.map +0 -1
- package/dist/chunk-IRGYTNLP.js.map +0 -1
- package/dist/chunk-JRF4G4X7.js.map +0 -1
- package/dist/chunk-KSIQZC3J.js.map +0 -1
- package/dist/chunk-OORPX73T.js.map +0 -1
- package/dist/chunk-VBEWSWVL.js.map +0 -1
- package/dist/chunk-X6LLG7XN.js.map +0 -1
- package/dist/config-editor-DDF3ZFJK.js +0 -12
- package/dist/doctor-N2HKKUUQ.js +0 -9
- package/dist/integrate-BLETI3UO.js.map +0 -1
- package/dist/main-AH3NCVM3.js.map +0 -1
- package/dist/tunnel-service-LEVPLXAZ.js.map +0 -1
- /package/dist/{api-client-UN7BXQOQ.js.map → action-detect-6M5GCGAU.js.map} +0 -0
- /package/dist/{autostart-K73RQZVV.js.map → admin-IKPS5PFC.js.map} +0 -0
- /package/dist/{config-PCPIBPUA.js.map → agents-55NX3DHM.js.map} +0 -0
- /package/dist/{config-editor-DDF3ZFJK.js.map → api-client-BH2JFHQW.js.map} +0 -0
- /package/dist/{daemon-JZLFRUW6.js.map → autostart-A7JRU4WJ.js.map} +0 -0
- /package/dist/{chunk-NDR5JCS7.js.map → chunk-437NLISU.js.map} +0 -0
- /package/dist/{doctor-N2HKKUUQ.js.map → config-KF2MQWAP.js.map} +0 -0
- /package/dist/{setup-7JINXQOA.js.map → config-editor-OTODXUF7.js.map} +0 -0
- /package/dist/{version-VC5CPXBX.js.map → daemon-U6UC7OM4.js.map} +0 -0
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChannelAdapter,
|
|
3
|
+
PRODUCT_GUIDE
|
|
4
|
+
} from "./chunk-5NBWM7P6.js";
|
|
1
5
|
import {
|
|
2
6
|
DoctorEngine
|
|
3
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-SPX7CKWV.js";
|
|
4
8
|
import {
|
|
5
9
|
buildMenuKeyboard,
|
|
6
10
|
buildSkillMessages,
|
|
@@ -488,9 +492,10 @@ ${stderr}`
|
|
|
488
492
|
}
|
|
489
493
|
async prompt(text, attachments) {
|
|
490
494
|
const contentBlocks = [{ type: "text", text }];
|
|
495
|
+
const SUPPORTED_IMAGE_MIMES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/gif", "image/webp"]);
|
|
491
496
|
for (const att of attachments ?? []) {
|
|
492
497
|
const tooLarge = att.size > 10 * 1024 * 1024;
|
|
493
|
-
if (att.type === "image" && this.promptCapabilities?.image && !tooLarge) {
|
|
498
|
+
if (att.type === "image" && this.promptCapabilities?.image && !tooLarge && SUPPORTED_IMAGE_MIMES.has(att.mimeType)) {
|
|
494
499
|
const data = await fs.promises.readFile(att.filePath);
|
|
495
500
|
contentBlocks.push({ type: "image", data: data.toString("base64"), mimeType: att.mimeType });
|
|
496
501
|
} else if (att.type === "audio" && this.promptCapabilities?.audio && !tooLarge) {
|
|
@@ -640,6 +645,7 @@ var PromptQueue = class {
|
|
|
640
645
|
}
|
|
641
646
|
queue = [];
|
|
642
647
|
processing = false;
|
|
648
|
+
abortController = null;
|
|
643
649
|
async enqueue(text, attachments) {
|
|
644
650
|
if (this.processing) {
|
|
645
651
|
return new Promise((resolve2) => {
|
|
@@ -650,11 +656,21 @@ var PromptQueue = class {
|
|
|
650
656
|
}
|
|
651
657
|
async process(text, attachments) {
|
|
652
658
|
this.processing = true;
|
|
659
|
+
this.abortController = new AbortController();
|
|
660
|
+
const { signal } = this.abortController;
|
|
653
661
|
try {
|
|
654
|
-
await
|
|
662
|
+
await Promise.race([
|
|
663
|
+
this.processor(text, attachments),
|
|
664
|
+
new Promise((_, reject) => {
|
|
665
|
+
signal.addEventListener("abort", () => reject(new Error("Prompt aborted")), { once: true });
|
|
666
|
+
})
|
|
667
|
+
]);
|
|
655
668
|
} catch (err) {
|
|
656
|
-
|
|
669
|
+
if (!(err instanceof Error && err.message === "Prompt aborted")) {
|
|
670
|
+
this.onError?.(err);
|
|
671
|
+
}
|
|
657
672
|
} finally {
|
|
673
|
+
this.abortController = null;
|
|
658
674
|
this.processing = false;
|
|
659
675
|
this.drainNext();
|
|
660
676
|
}
|
|
@@ -666,6 +682,9 @@ var PromptQueue = class {
|
|
|
666
682
|
}
|
|
667
683
|
}
|
|
668
684
|
clear() {
|
|
685
|
+
if (this.abortController) {
|
|
686
|
+
this.abortController.abort();
|
|
687
|
+
}
|
|
669
688
|
for (const item of this.queue) {
|
|
670
689
|
item.resolve();
|
|
671
690
|
}
|
|
@@ -958,7 +977,7 @@ var SessionManager = class {
|
|
|
958
977
|
getRecordByThread(channelId, threadId) {
|
|
959
978
|
return this.store?.findByPlatform(
|
|
960
979
|
channelId,
|
|
961
|
-
(p) => String(p.topicId) === threadId
|
|
980
|
+
(p) => String(p.topicId) === threadId || p.threadId === threadId
|
|
962
981
|
);
|
|
963
982
|
}
|
|
964
983
|
registerSession(session) {
|
|
@@ -1701,9 +1720,10 @@ var OpenACPCore = class {
|
|
|
1701
1720
|
"Incoming message"
|
|
1702
1721
|
);
|
|
1703
1722
|
if (config.security.allowedUserIds.length > 0) {
|
|
1704
|
-
|
|
1723
|
+
const userId = String(message.userId);
|
|
1724
|
+
if (!config.security.allowedUserIds.includes(userId)) {
|
|
1705
1725
|
log5.warn(
|
|
1706
|
-
{ userId
|
|
1726
|
+
{ userId },
|
|
1707
1727
|
"Rejected message from unauthorized user"
|
|
1708
1728
|
);
|
|
1709
1729
|
return;
|
|
@@ -1779,12 +1799,32 @@ var OpenACPCore = class {
|
|
|
1779
1799
|
const bridge = this.createBridge(session, adapter);
|
|
1780
1800
|
bridge.connect();
|
|
1781
1801
|
}
|
|
1802
|
+
session.on("status_change", (_from, to) => {
|
|
1803
|
+
if ((to === "finished" || to === "cancelled") && this._tunnelService) {
|
|
1804
|
+
this._tunnelService.stopBySession(session.id).then((stopped) => {
|
|
1805
|
+
for (const entry of stopped) {
|
|
1806
|
+
this.notificationManager.notifyAll({
|
|
1807
|
+
sessionId: session.id,
|
|
1808
|
+
sessionName: session.name,
|
|
1809
|
+
type: "completed",
|
|
1810
|
+
summary: `Tunnel stopped: port ${entry.port}${entry.label ? ` (${entry.label})` : ""} \u2014 session ended`
|
|
1811
|
+
}).catch(() => {
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
}).catch(() => {
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
});
|
|
1782
1818
|
const existingRecord = this.sessionStore?.get(session.id);
|
|
1783
1819
|
const platform = {
|
|
1784
1820
|
...existingRecord?.platform ?? {}
|
|
1785
1821
|
};
|
|
1786
1822
|
if (session.threadId) {
|
|
1787
|
-
|
|
1823
|
+
if (params.channelId === "telegram") {
|
|
1824
|
+
platform.topicId = Number(session.threadId);
|
|
1825
|
+
} else {
|
|
1826
|
+
platform.threadId = session.threadId;
|
|
1827
|
+
}
|
|
1788
1828
|
}
|
|
1789
1829
|
await this.sessionManager.patchRecord(session.id, {
|
|
1790
1830
|
sessionId: session.id,
|
|
@@ -1928,10 +1968,10 @@ var OpenACPCore = class {
|
|
|
1928
1968
|
);
|
|
1929
1969
|
return null;
|
|
1930
1970
|
}
|
|
1931
|
-
if (record.status === "
|
|
1971
|
+
if (record.status === "error") {
|
|
1932
1972
|
log5.debug(
|
|
1933
1973
|
{ threadId: message.threadId, sessionId: record.sessionId, status: record.status },
|
|
1934
|
-
"Skipping resume of
|
|
1974
|
+
"Skipping resume of error session"
|
|
1935
1975
|
);
|
|
1936
1976
|
return null;
|
|
1937
1977
|
}
|
|
@@ -1989,26 +2029,12 @@ var OpenACPCore = class {
|
|
|
1989
2029
|
}
|
|
1990
2030
|
};
|
|
1991
2031
|
|
|
1992
|
-
// src/core/channel.ts
|
|
1993
|
-
var ChannelAdapter = class {
|
|
1994
|
-
constructor(core, config) {
|
|
1995
|
-
this.core = core;
|
|
1996
|
-
this.config = config;
|
|
1997
|
-
}
|
|
1998
|
-
async deleteSessionThread(_sessionId) {
|
|
1999
|
-
}
|
|
2000
|
-
// Skill commands — override in adapters that support dynamic commands
|
|
2001
|
-
async sendSkillCommands(_sessionId, _commands) {
|
|
2002
|
-
}
|
|
2003
|
-
async cleanupSkillCommands(_sessionId) {
|
|
2004
|
-
}
|
|
2005
|
-
};
|
|
2006
|
-
|
|
2007
2032
|
// src/core/api-server.ts
|
|
2008
2033
|
import * as http from "http";
|
|
2009
2034
|
import * as fs4 from "fs";
|
|
2010
2035
|
import * as path5 from "path";
|
|
2011
2036
|
import * as os2 from "os";
|
|
2037
|
+
import * as crypto from "crypto";
|
|
2012
2038
|
import { fileURLToPath } from "url";
|
|
2013
2039
|
var log6 = createChildLogger({ module: "api-server" });
|
|
2014
2040
|
var DEFAULT_PORT_FILE = path5.join(os2.homedir(), ".openacp", "api.port");
|
|
@@ -2041,17 +2067,21 @@ function redactDeep(obj) {
|
|
|
2041
2067
|
}
|
|
2042
2068
|
}
|
|
2043
2069
|
var ApiServer = class {
|
|
2044
|
-
constructor(core, config, portFilePath, topicManager) {
|
|
2070
|
+
constructor(core, config, portFilePath, topicManager, secretFilePath) {
|
|
2045
2071
|
this.core = core;
|
|
2046
2072
|
this.config = config;
|
|
2047
2073
|
this.topicManager = topicManager;
|
|
2048
2074
|
this.portFilePath = portFilePath ?? DEFAULT_PORT_FILE;
|
|
2075
|
+
this.secretFilePath = secretFilePath ?? path5.join(os2.homedir(), ".openacp", "api-secret");
|
|
2049
2076
|
}
|
|
2050
2077
|
server = null;
|
|
2051
2078
|
actualPort = 0;
|
|
2052
2079
|
portFilePath;
|
|
2053
2080
|
startedAt = Date.now();
|
|
2081
|
+
secret = "";
|
|
2082
|
+
secretFilePath;
|
|
2054
2083
|
async start() {
|
|
2084
|
+
this.loadOrCreateSecret();
|
|
2055
2085
|
this.server = http.createServer((req, res) => this.handleRequest(req, res));
|
|
2056
2086
|
await new Promise((resolve2, reject) => {
|
|
2057
2087
|
this.server.on("error", (err) => {
|
|
@@ -2097,9 +2127,42 @@ var ApiServer = class {
|
|
|
2097
2127
|
} catch {
|
|
2098
2128
|
}
|
|
2099
2129
|
}
|
|
2130
|
+
loadOrCreateSecret() {
|
|
2131
|
+
const dir = path5.dirname(this.secretFilePath);
|
|
2132
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
2133
|
+
try {
|
|
2134
|
+
this.secret = fs4.readFileSync(this.secretFilePath, "utf-8").trim();
|
|
2135
|
+
if (this.secret) {
|
|
2136
|
+
try {
|
|
2137
|
+
const stat = fs4.statSync(this.secretFilePath);
|
|
2138
|
+
const mode = stat.mode & 511;
|
|
2139
|
+
if (mode & 63) {
|
|
2140
|
+
log6.warn({ path: this.secretFilePath, mode: "0" + mode.toString(8) }, "API secret file has insecure permissions (should be 0600). Run: chmod 600 %s", this.secretFilePath);
|
|
2141
|
+
}
|
|
2142
|
+
} catch {
|
|
2143
|
+
}
|
|
2144
|
+
return;
|
|
2145
|
+
}
|
|
2146
|
+
} catch {
|
|
2147
|
+
}
|
|
2148
|
+
this.secret = crypto.randomBytes(32).toString("hex");
|
|
2149
|
+
fs4.writeFileSync(this.secretFilePath, this.secret, { mode: 384 });
|
|
2150
|
+
}
|
|
2151
|
+
authenticate(req) {
|
|
2152
|
+
const authHeader = req.headers.authorization;
|
|
2153
|
+
if (!authHeader?.startsWith("Bearer ")) return false;
|
|
2154
|
+
const token = authHeader.slice(7);
|
|
2155
|
+
if (token.length !== this.secret.length) return false;
|
|
2156
|
+
return crypto.timingSafeEqual(Buffer.from(token, "utf-8"), Buffer.from(this.secret, "utf-8"));
|
|
2157
|
+
}
|
|
2100
2158
|
async handleRequest(req, res) {
|
|
2101
2159
|
const method = req.method?.toUpperCase();
|
|
2102
2160
|
const url = req.url || "";
|
|
2161
|
+
const isExempt = method === "GET" && (url === "/api/health" || url === "/api/version");
|
|
2162
|
+
if (!isExempt && !this.authenticate(req)) {
|
|
2163
|
+
this.sendJson(res, 401, { error: "Unauthorized" });
|
|
2164
|
+
return;
|
|
2165
|
+
}
|
|
2103
2166
|
try {
|
|
2104
2167
|
if (method === "POST" && url === "/api/sessions/adopt") {
|
|
2105
2168
|
await this.handleAdoptSession(req, res);
|
|
@@ -2135,6 +2198,15 @@ var ApiServer = class {
|
|
|
2135
2198
|
await this.handleListAdapters(res);
|
|
2136
2199
|
} else if (method === "GET" && url === "/api/tunnel") {
|
|
2137
2200
|
await this.handleTunnelStatus(res);
|
|
2201
|
+
} else if (method === "GET" && url === "/api/tunnel/list") {
|
|
2202
|
+
await this.handleTunnelList(res);
|
|
2203
|
+
} else if (method === "POST" && url === "/api/tunnel") {
|
|
2204
|
+
await this.handleTunnelAdd(req, res);
|
|
2205
|
+
} else if (method === "DELETE" && url === "/api/tunnel") {
|
|
2206
|
+
await this.handleTunnelStopAll(res);
|
|
2207
|
+
} else if (method === "DELETE" && url.match(/^\/api\/tunnel\/(\d+)$/)) {
|
|
2208
|
+
const port = parseInt(url.match(/^\/api\/tunnel\/(\d+)$/)[1], 10);
|
|
2209
|
+
await this.handleTunnelStop(port, res);
|
|
2138
2210
|
} else if (method === "POST" && url === "/api/notify") {
|
|
2139
2211
|
await this.handleNotify(req, res);
|
|
2140
2212
|
} else if (method === "POST" && url === "/api/restart") {
|
|
@@ -2361,7 +2433,7 @@ var ApiServer = class {
|
|
|
2361
2433
|
return;
|
|
2362
2434
|
}
|
|
2363
2435
|
target[lastKey] = value;
|
|
2364
|
-
const { ConfigSchema } = await import("./config-
|
|
2436
|
+
const { ConfigSchema } = await import("./config-KF2MQWAP.js");
|
|
2365
2437
|
const result = ConfigSchema.safeParse(cloned);
|
|
2366
2438
|
if (!result.success) {
|
|
2367
2439
|
this.sendJson(res, 400, {
|
|
@@ -2401,6 +2473,60 @@ var ApiServer = class {
|
|
|
2401
2473
|
this.sendJson(res, 200, { enabled: false });
|
|
2402
2474
|
}
|
|
2403
2475
|
}
|
|
2476
|
+
async handleTunnelList(res) {
|
|
2477
|
+
const tunnel = this.core.tunnelService;
|
|
2478
|
+
if (!tunnel) {
|
|
2479
|
+
this.sendJson(res, 200, []);
|
|
2480
|
+
return;
|
|
2481
|
+
}
|
|
2482
|
+
this.sendJson(res, 200, tunnel.listTunnels());
|
|
2483
|
+
}
|
|
2484
|
+
async handleTunnelAdd(req, res) {
|
|
2485
|
+
const tunnel = this.core.tunnelService;
|
|
2486
|
+
if (!tunnel) {
|
|
2487
|
+
this.sendJson(res, 400, { error: "Tunnel service is not enabled" });
|
|
2488
|
+
return;
|
|
2489
|
+
}
|
|
2490
|
+
const body = await this.readBody(req);
|
|
2491
|
+
if (!body) {
|
|
2492
|
+
this.sendJson(res, 400, { error: "Missing request body" });
|
|
2493
|
+
return;
|
|
2494
|
+
}
|
|
2495
|
+
try {
|
|
2496
|
+
const { port, label, sessionId } = JSON.parse(body);
|
|
2497
|
+
if (!port || typeof port !== "number") {
|
|
2498
|
+
this.sendJson(res, 400, { error: "port is required and must be a number" });
|
|
2499
|
+
return;
|
|
2500
|
+
}
|
|
2501
|
+
const entry = await tunnel.addTunnel(port, { label, sessionId });
|
|
2502
|
+
this.sendJson(res, 200, entry);
|
|
2503
|
+
} catch (err) {
|
|
2504
|
+
this.sendJson(res, 400, { error: err.message });
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
async handleTunnelStop(port, res) {
|
|
2508
|
+
const tunnel = this.core.tunnelService;
|
|
2509
|
+
if (!tunnel) {
|
|
2510
|
+
this.sendJson(res, 400, { error: "Tunnel service is not enabled" });
|
|
2511
|
+
return;
|
|
2512
|
+
}
|
|
2513
|
+
try {
|
|
2514
|
+
await tunnel.stopTunnel(port);
|
|
2515
|
+
this.sendJson(res, 200, { ok: true });
|
|
2516
|
+
} catch (err) {
|
|
2517
|
+
this.sendJson(res, 400, { error: err.message });
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
async handleTunnelStopAll(res) {
|
|
2521
|
+
const tunnel = this.core.tunnelService;
|
|
2522
|
+
if (!tunnel) {
|
|
2523
|
+
this.sendJson(res, 400, { error: "Tunnel service is not enabled" });
|
|
2524
|
+
return;
|
|
2525
|
+
}
|
|
2526
|
+
const count = tunnel.listTunnels().length;
|
|
2527
|
+
await tunnel.stopAllUser();
|
|
2528
|
+
this.sendJson(res, 200, { ok: true, stopped: count });
|
|
2529
|
+
}
|
|
2404
2530
|
async handleNotify(req, res) {
|
|
2405
2531
|
const body = await this.readBody(req);
|
|
2406
2532
|
let message;
|
|
@@ -2955,7 +3081,7 @@ async function handleUpdate(ctx, core) {
|
|
|
2955
3081
|
await ctx.reply("\u26A0\uFE0F Update is not available (no restart handler registered).", { parse_mode: "HTML" });
|
|
2956
3082
|
return;
|
|
2957
3083
|
}
|
|
2958
|
-
const { getCurrentVersion, getLatestVersion, compareVersions, runUpdate } = await import("./version-
|
|
3084
|
+
const { getCurrentVersion, getLatestVersion, compareVersions, runUpdate } = await import("./version-ALWGGVKM.js");
|
|
2959
3085
|
const current = getCurrentVersion();
|
|
2960
3086
|
const statusMsg = await ctx.reply(`\u{1F50D} Checking for updates... (current: v${escapeHtml(current)})`, { parse_mode: "HTML" });
|
|
2961
3087
|
const latest = await getLatestVersion();
|
|
@@ -3405,16 +3531,15 @@ async function handleCancel(ctx, core, assistant) {
|
|
|
3405
3531
|
String(threadId)
|
|
3406
3532
|
);
|
|
3407
3533
|
if (session) {
|
|
3408
|
-
log11.info({ sessionId: session.id }, "
|
|
3534
|
+
log11.info({ sessionId: session.id }, "Abort prompt command");
|
|
3409
3535
|
await session.abortPrompt();
|
|
3410
|
-
await ctx.reply("\u26D4 Session
|
|
3536
|
+
await ctx.reply("\u26D4 Prompt aborted. Session is still active \u2014 send a new message to continue.", { parse_mode: "HTML" });
|
|
3411
3537
|
return;
|
|
3412
3538
|
}
|
|
3413
3539
|
const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
|
|
3414
|
-
if (record && record.status !== "
|
|
3415
|
-
log11.info({ sessionId: record.sessionId }, "Cancel
|
|
3416
|
-
await
|
|
3417
|
-
await ctx.reply("\u26D4 Session cancelled.", { parse_mode: "HTML" });
|
|
3540
|
+
if (record && record.status !== "error") {
|
|
3541
|
+
log11.info({ sessionId: record.sessionId, status: record.status }, "Cancel command \u2014 no active prompt to abort");
|
|
3542
|
+
await ctx.reply("\u2139\uFE0F No active prompt to cancel. Send a new message to resume the session.", { parse_mode: "HTML" });
|
|
3418
3543
|
}
|
|
3419
3544
|
}
|
|
3420
3545
|
async function handleStatus(ctx, core) {
|
|
@@ -3874,7 +3999,7 @@ Downloading... ${bar} ${percent}%`, { parse_mode: "HTML" });
|
|
|
3874
3999
|
const { getAgentCapabilities: getAgentCapabilities2 } = await import("./agent-dependencies-QY5QSULV.js");
|
|
3875
4000
|
const caps = getAgentCapabilities2(result.agentKey);
|
|
3876
4001
|
if (caps.integration) {
|
|
3877
|
-
const { installIntegration } = await import("./integrate-
|
|
4002
|
+
const { installIntegration } = await import("./integrate-VOUYBPPZ.js");
|
|
3878
4003
|
const intResult = await installIntegration(result.agentKey, caps.integration);
|
|
3879
4004
|
if (intResult.success) {
|
|
3880
4005
|
try {
|
|
@@ -3944,7 +4069,7 @@ function buildProgressBar(percent) {
|
|
|
3944
4069
|
// src/adapters/telegram/commands/integrate.ts
|
|
3945
4070
|
import { InlineKeyboard as InlineKeyboard5 } from "grammy";
|
|
3946
4071
|
async function handleIntegrate(ctx, _core) {
|
|
3947
|
-
const { listIntegrations } = await import("./integrate-
|
|
4072
|
+
const { listIntegrations } = await import("./integrate-VOUYBPPZ.js");
|
|
3948
4073
|
const agents = listIntegrations();
|
|
3949
4074
|
const keyboard = new InlineKeyboard5();
|
|
3950
4075
|
for (const agent of agents) {
|
|
@@ -3977,7 +4102,7 @@ function setupIntegrateCallbacks(bot, core) {
|
|
|
3977
4102
|
} catch {
|
|
3978
4103
|
}
|
|
3979
4104
|
if (data === "i:back") {
|
|
3980
|
-
const { listIntegrations } = await import("./integrate-
|
|
4105
|
+
const { listIntegrations } = await import("./integrate-VOUYBPPZ.js");
|
|
3981
4106
|
const agents = listIntegrations();
|
|
3982
4107
|
const keyboard2 = new InlineKeyboard5();
|
|
3983
4108
|
for (const agent of agents) {
|
|
@@ -3997,7 +4122,7 @@ Select an agent to manage its integrations.`,
|
|
|
3997
4122
|
const agentMatch = data.match(/^i:agent:(.+)$/);
|
|
3998
4123
|
if (agentMatch) {
|
|
3999
4124
|
const agentName2 = agentMatch[1];
|
|
4000
|
-
const { getIntegration: getIntegration2 } = await import("./integrate-
|
|
4125
|
+
const { getIntegration: getIntegration2 } = await import("./integrate-VOUYBPPZ.js");
|
|
4001
4126
|
const integration2 = getIntegration2(agentName2);
|
|
4002
4127
|
if (!integration2) {
|
|
4003
4128
|
await ctx.reply(`\u274C No integration available for '${escapeHtml(agentName2)}'.`, { parse_mode: "HTML" });
|
|
@@ -4024,7 +4149,7 @@ ${integration2.items.map((i) => `\u2022 <b>${escapeHtml(i.name)}</b> \u2014 ${es
|
|
|
4024
4149
|
const action = actionMatch[1];
|
|
4025
4150
|
const agentName = actionMatch[2];
|
|
4026
4151
|
const itemId = actionMatch[3];
|
|
4027
|
-
const { getIntegration } = await import("./integrate-
|
|
4152
|
+
const { getIntegration } = await import("./integrate-VOUYBPPZ.js");
|
|
4028
4153
|
const integration = getIntegration(agentName);
|
|
4029
4154
|
if (!integration) return;
|
|
4030
4155
|
const item = integration.items.find((i) => i.id === itemId);
|
|
@@ -4351,6 +4476,155 @@ function setupDoctorCallbacks(bot) {
|
|
|
4351
4476
|
});
|
|
4352
4477
|
}
|
|
4353
4478
|
|
|
4479
|
+
// src/adapters/telegram/commands/tunnel.ts
|
|
4480
|
+
import { InlineKeyboard as InlineKeyboard8 } from "grammy";
|
|
4481
|
+
var log14 = createChildLogger({ module: "telegram-cmd-tunnel" });
|
|
4482
|
+
async function handleTunnel(ctx, core) {
|
|
4483
|
+
if (!core.tunnelService) {
|
|
4484
|
+
await ctx.reply("\u274C Tunnel service is not enabled.", { parse_mode: "HTML" });
|
|
4485
|
+
return;
|
|
4486
|
+
}
|
|
4487
|
+
const match = ctx.match?.trim() ?? "";
|
|
4488
|
+
if (match.startsWith("stop ")) {
|
|
4489
|
+
const portStr = match.slice(5).trim();
|
|
4490
|
+
const port = parseInt(portStr, 10);
|
|
4491
|
+
if (isNaN(port)) {
|
|
4492
|
+
await ctx.reply("\u274C Invalid port number.", { parse_mode: "HTML" });
|
|
4493
|
+
return;
|
|
4494
|
+
}
|
|
4495
|
+
try {
|
|
4496
|
+
await core.tunnelService.stopTunnel(port);
|
|
4497
|
+
await ctx.reply(`\u{1F50C} Tunnel stopped: port ${port}`, { parse_mode: "HTML" });
|
|
4498
|
+
} catch (err) {
|
|
4499
|
+
await ctx.reply(`\u274C ${escapeHtml(err.message)}`, { parse_mode: "HTML" });
|
|
4500
|
+
}
|
|
4501
|
+
return;
|
|
4502
|
+
}
|
|
4503
|
+
if (match) {
|
|
4504
|
+
const parts = match.split(/\s+/);
|
|
4505
|
+
const port = parseInt(parts[0], 10);
|
|
4506
|
+
if (isNaN(port)) {
|
|
4507
|
+
await ctx.reply("\u274C Invalid port number. Usage: <code>/tunnel 3000 [label]</code>", { parse_mode: "HTML" });
|
|
4508
|
+
return;
|
|
4509
|
+
}
|
|
4510
|
+
const label = parts.slice(1).join(" ") || void 0;
|
|
4511
|
+
const threadId = ctx.message?.message_thread_id;
|
|
4512
|
+
let sessionId;
|
|
4513
|
+
if (threadId) {
|
|
4514
|
+
const session = core.sessionManager.getSessionByThread("telegram", String(threadId));
|
|
4515
|
+
if (session) sessionId = session.id;
|
|
4516
|
+
}
|
|
4517
|
+
try {
|
|
4518
|
+
await ctx.reply(`\u23F3 Starting tunnel for port ${port}...`, { parse_mode: "HTML" });
|
|
4519
|
+
const entry = await core.tunnelService.addTunnel(port, { label, sessionId });
|
|
4520
|
+
await ctx.reply(
|
|
4521
|
+
`\u{1F517} <b>Tunnel active</b>
|
|
4522
|
+
|
|
4523
|
+
Port ${port}${label ? ` (${escapeHtml(label)})` : ""}
|
|
4524
|
+
\u2192 <a href="${escapeHtml(entry.publicUrl || "")}">${escapeHtml(entry.publicUrl || "")}</a>`,
|
|
4525
|
+
{ parse_mode: "HTML" }
|
|
4526
|
+
);
|
|
4527
|
+
} catch (err) {
|
|
4528
|
+
await ctx.reply(`\u274C ${escapeHtml(err.message)}`, { parse_mode: "HTML" });
|
|
4529
|
+
}
|
|
4530
|
+
return;
|
|
4531
|
+
}
|
|
4532
|
+
await ctx.reply(
|
|
4533
|
+
`<b>Tunnel commands:</b>
|
|
4534
|
+
|
|
4535
|
+
<code>/tunnel <port> [label]</code> \u2014 Create tunnel
|
|
4536
|
+
<code>/tunnel stop <port></code> \u2014 Stop tunnel
|
|
4537
|
+
<code>/tunnels</code> \u2014 List active tunnels`,
|
|
4538
|
+
{ parse_mode: "HTML" }
|
|
4539
|
+
);
|
|
4540
|
+
}
|
|
4541
|
+
async function handleTunnels(ctx, core) {
|
|
4542
|
+
if (!core.tunnelService) {
|
|
4543
|
+
await ctx.reply("\u274C Tunnel service is not enabled.", { parse_mode: "HTML" });
|
|
4544
|
+
return;
|
|
4545
|
+
}
|
|
4546
|
+
const entries = core.tunnelService.listTunnels();
|
|
4547
|
+
if (entries.length === 0) {
|
|
4548
|
+
await ctx.reply(
|
|
4549
|
+
"No active tunnels.\n\nUse <code>/tunnel <port></code> to create one.",
|
|
4550
|
+
{ parse_mode: "HTML" }
|
|
4551
|
+
);
|
|
4552
|
+
return;
|
|
4553
|
+
}
|
|
4554
|
+
const lines = entries.map((e) => {
|
|
4555
|
+
const status = e.status === "active" ? "\u2705" : e.status === "starting" ? "\u23F3" : "\u274C";
|
|
4556
|
+
const label = e.label ? ` (${escapeHtml(e.label)})` : "";
|
|
4557
|
+
const url = e.publicUrl ? `
|
|
4558
|
+
\u2192 <a href="${escapeHtml(e.publicUrl)}">${escapeHtml(e.publicUrl)}</a>` : "";
|
|
4559
|
+
return `${status} Port <b>${e.port}</b>${label}${url}`;
|
|
4560
|
+
});
|
|
4561
|
+
const keyboard = new InlineKeyboard8();
|
|
4562
|
+
for (const e of entries) {
|
|
4563
|
+
keyboard.text(`\u{1F50C} Stop ${e.port}${e.label ? ` (${e.label})` : ""}`, `tn:stop:${e.port}`).row();
|
|
4564
|
+
}
|
|
4565
|
+
if (entries.length > 1) {
|
|
4566
|
+
keyboard.text("\u{1F50C} Stop all", "tn:stop-all").row();
|
|
4567
|
+
}
|
|
4568
|
+
await ctx.reply(
|
|
4569
|
+
`<b>Active tunnels:</b>
|
|
4570
|
+
|
|
4571
|
+
${lines.join("\n\n")}`,
|
|
4572
|
+
{ parse_mode: "HTML", reply_markup: keyboard }
|
|
4573
|
+
);
|
|
4574
|
+
}
|
|
4575
|
+
function setupTunnelCallbacks(bot, core) {
|
|
4576
|
+
bot.callbackQuery(/^tn:/, async (ctx) => {
|
|
4577
|
+
const data = ctx.callbackQuery.data;
|
|
4578
|
+
if (!core.tunnelService) {
|
|
4579
|
+
await ctx.answerCallbackQuery({ text: "Tunnel not enabled" });
|
|
4580
|
+
return;
|
|
4581
|
+
}
|
|
4582
|
+
try {
|
|
4583
|
+
if (data === "tn:stop-all") {
|
|
4584
|
+
const entries = core.tunnelService.listTunnels();
|
|
4585
|
+
for (const e of entries) {
|
|
4586
|
+
try {
|
|
4587
|
+
await core.tunnelService.stopTunnel(e.port);
|
|
4588
|
+
} catch {
|
|
4589
|
+
}
|
|
4590
|
+
}
|
|
4591
|
+
await ctx.answerCallbackQuery({ text: "All tunnels stopped" });
|
|
4592
|
+
await ctx.editMessageText("\u{1F50C} All tunnels stopped.", { parse_mode: "HTML" });
|
|
4593
|
+
} else if (data.startsWith("tn:stop:")) {
|
|
4594
|
+
const port = parseInt(data.replace("tn:stop:", ""), 10);
|
|
4595
|
+
await core.tunnelService.stopTunnel(port);
|
|
4596
|
+
await ctx.answerCallbackQuery({ text: `Port ${port} stopped` });
|
|
4597
|
+
const remaining = core.tunnelService.listTunnels();
|
|
4598
|
+
if (remaining.length === 0) {
|
|
4599
|
+
await ctx.editMessageText("\u{1F50C} All tunnels stopped.", { parse_mode: "HTML" });
|
|
4600
|
+
} else {
|
|
4601
|
+
const kb = new InlineKeyboard8();
|
|
4602
|
+
for (const e of remaining) {
|
|
4603
|
+
kb.text(`\u{1F50C} Stop ${e.port}${e.label ? ` (${e.label})` : ""}`, `tn:stop:${e.port}`).row();
|
|
4604
|
+
}
|
|
4605
|
+
if (remaining.length > 1) {
|
|
4606
|
+
kb.text("\u{1F50C} Stop all", "tn:stop-all").row();
|
|
4607
|
+
}
|
|
4608
|
+
await ctx.editMessageText(
|
|
4609
|
+
`<b>Active tunnels:</b>
|
|
4610
|
+
|
|
4611
|
+
` + remaining.map((e) => {
|
|
4612
|
+
const status = e.status === "active" ? "\u2705" : "\u23F3";
|
|
4613
|
+
const label = e.label ? ` (${escapeHtml(e.label)})` : "";
|
|
4614
|
+
const url = e.publicUrl ? `
|
|
4615
|
+
\u2192 <a href="${escapeHtml(e.publicUrl)}">${escapeHtml(e.publicUrl)}</a>` : "";
|
|
4616
|
+
return `${status} Port <b>${e.port}</b>${label}${url}`;
|
|
4617
|
+
}).join("\n\n"),
|
|
4618
|
+
{ parse_mode: "HTML", reply_markup: kb }
|
|
4619
|
+
);
|
|
4620
|
+
}
|
|
4621
|
+
}
|
|
4622
|
+
} catch (err) {
|
|
4623
|
+
await ctx.answerCallbackQuery({ text: err.message });
|
|
4624
|
+
}
|
|
4625
|
+
});
|
|
4626
|
+
}
|
|
4627
|
+
|
|
4354
4628
|
// src/adapters/telegram/commands/index.ts
|
|
4355
4629
|
function setupCommands(bot, core, chatId, assistant) {
|
|
4356
4630
|
bot.command("new", (ctx) => handleNew(ctx, core, chatId, assistant));
|
|
@@ -4369,12 +4643,15 @@ function setupCommands(bot, core, chatId, assistant) {
|
|
|
4369
4643
|
bot.command("integrate", (ctx) => handleIntegrate(ctx, core));
|
|
4370
4644
|
bot.command("clear", (ctx) => handleClear(ctx, assistant));
|
|
4371
4645
|
bot.command("doctor", (ctx) => handleDoctor(ctx));
|
|
4646
|
+
bot.command("tunnel", (ctx) => handleTunnel(ctx, core));
|
|
4647
|
+
bot.command("tunnels", (ctx) => handleTunnels(ctx, core));
|
|
4372
4648
|
}
|
|
4373
4649
|
function setupAllCallbacks(bot, core, chatId, systemTopicIds, getAssistantSession) {
|
|
4374
4650
|
setupNewSessionCallbacks(bot, core, chatId);
|
|
4375
4651
|
setupSessionCallbacks(bot, core, chatId, systemTopicIds);
|
|
4376
4652
|
setupSettingsCallbacks(bot, core, getAssistantSession ?? (() => void 0));
|
|
4377
4653
|
setupDoctorCallbacks(bot);
|
|
4654
|
+
setupTunnelCallbacks(bot, core);
|
|
4378
4655
|
bot.callbackQuery(/^ag:/, (ctx) => handleAgentCallback(ctx, core));
|
|
4379
4656
|
bot.callbackQuery(/^na:/, async (ctx) => {
|
|
4380
4657
|
const agentKey = ctx.callbackQuery.data.replace("na:", "");
|
|
@@ -4435,13 +4712,15 @@ var STATIC_COMMANDS = [
|
|
|
4435
4712
|
{ command: "clear", description: "Clear assistant history" },
|
|
4436
4713
|
{ command: "restart", description: "Restart OpenACP" },
|
|
4437
4714
|
{ command: "update", description: "Update to latest version and restart" },
|
|
4438
|
-
{ command: "doctor", description: "Run system diagnostics" }
|
|
4715
|
+
{ command: "doctor", description: "Run system diagnostics" },
|
|
4716
|
+
{ command: "tunnel", description: "Create/stop tunnel for a local port" },
|
|
4717
|
+
{ command: "tunnels", description: "List active tunnels" }
|
|
4439
4718
|
];
|
|
4440
4719
|
|
|
4441
4720
|
// src/adapters/telegram/permissions.ts
|
|
4442
|
-
import { InlineKeyboard as
|
|
4721
|
+
import { InlineKeyboard as InlineKeyboard9 } from "grammy";
|
|
4443
4722
|
import { nanoid as nanoid2 } from "nanoid";
|
|
4444
|
-
var
|
|
4723
|
+
var log15 = createChildLogger({ module: "telegram-permissions" });
|
|
4445
4724
|
var PermissionHandler = class {
|
|
4446
4725
|
constructor(bot, chatId, getSession, sendNotification) {
|
|
4447
4726
|
this.bot = bot;
|
|
@@ -4458,7 +4737,7 @@ var PermissionHandler = class {
|
|
|
4458
4737
|
requestId: request.id,
|
|
4459
4738
|
options: request.options.map((o) => ({ id: o.id, isAllow: o.isAllow }))
|
|
4460
4739
|
});
|
|
4461
|
-
const keyboard = new
|
|
4740
|
+
const keyboard = new InlineKeyboard9();
|
|
4462
4741
|
for (const option of request.options) {
|
|
4463
4742
|
const emoji = option.isAllow ? "\u2705" : "\u274C";
|
|
4464
4743
|
keyboard.text(`${emoji} ${option.label}`, `p:${callbackKey}:${option.id}`);
|
|
@@ -4501,7 +4780,7 @@ ${escapeHtml(request.description)}`,
|
|
|
4501
4780
|
}
|
|
4502
4781
|
const session = this.getSession(pending.sessionId);
|
|
4503
4782
|
const isAllow = pending.options.find((o) => o.id === optionId)?.isAllow ?? false;
|
|
4504
|
-
|
|
4783
|
+
log15.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
|
|
4505
4784
|
if (session?.permissionGate.requestId === pending.requestId) {
|
|
4506
4785
|
session.permissionGate.resolve(optionId);
|
|
4507
4786
|
}
|
|
@@ -4518,408 +4797,11 @@ ${escapeHtml(request.description)}`,
|
|
|
4518
4797
|
}
|
|
4519
4798
|
};
|
|
4520
4799
|
|
|
4521
|
-
// src/product-guide.ts
|
|
4522
|
-
var PRODUCT_GUIDE = `
|
|
4523
|
-
# OpenACP \u2014 Product Guide
|
|
4524
|
-
|
|
4525
|
-
OpenACP lets you chat with AI coding agents (like Claude Code) through Telegram.
|
|
4526
|
-
You type messages in Telegram, the agent reads/writes/runs code in your project folder, and results stream back in real time.
|
|
4527
|
-
|
|
4528
|
-
---
|
|
4529
|
-
|
|
4530
|
-
## Quick Start
|
|
4531
|
-
|
|
4532
|
-
1. Start OpenACP: \`openacp\` (or \`openacp start\` for background daemon)
|
|
4533
|
-
2. Open your Telegram group \u2014 you'll see the Assistant topic
|
|
4534
|
-
3. Tap \u{1F195} New Session or type /new
|
|
4535
|
-
4. Pick an agent and a project folder
|
|
4536
|
-
5. Chat in the session topic \u2014 the agent works on your code
|
|
4537
|
-
|
|
4538
|
-
---
|
|
4539
|
-
|
|
4540
|
-
## Core Concepts
|
|
4541
|
-
|
|
4542
|
-
### Sessions
|
|
4543
|
-
A session = one conversation with one AI agent working in one project folder.
|
|
4544
|
-
Each session gets its own Telegram topic. Chat there to give instructions to the agent.
|
|
4545
|
-
|
|
4546
|
-
### Agents
|
|
4547
|
-
An agent is an AI coding tool (e.g., Claude Code, Gemini, Cursor, Codex, etc.).
|
|
4548
|
-
OpenACP supports 28+ agents from the official ACP Registry (agentclientprotocol.com).
|
|
4549
|
-
You can install multiple agents and choose which one to use per session.
|
|
4550
|
-
The default agent is used when you don't specify one.
|
|
4551
|
-
|
|
4552
|
-
### Agent Management
|
|
4553
|
-
- Browse agents: \`/agents\` in Telegram or \`openacp agents\` in CLI
|
|
4554
|
-
- Install: tap the install button in /agents, or \`openacp agents install <name>\`
|
|
4555
|
-
- Uninstall: \`openacp agents uninstall <name>\`
|
|
4556
|
-
- Setup/login: \`openacp agents run <name> -- <args>\` (e.g., \`openacp agents run gemini -- auth login\`)
|
|
4557
|
-
- Details: \`openacp agents info <name>\` shows version, dependencies, and setup steps
|
|
4558
|
-
|
|
4559
|
-
Some agents need additional setup before they can be used:
|
|
4560
|
-
- Claude: requires \`claude login\`
|
|
4561
|
-
- Gemini: requires \`openacp agents run gemini -- auth login\`
|
|
4562
|
-
- Codex: requires setting \`OPENAI_API_KEY\` environment variable
|
|
4563
|
-
- GitHub Copilot: requires \`openacp agents run copilot -- auth login\`
|
|
4564
|
-
|
|
4565
|
-
Agents are installed in three ways depending on the agent:
|
|
4566
|
-
- **npx** \u2014 Node.js agents, downloaded automatically on first use
|
|
4567
|
-
- **uvx** \u2014 Python agents, downloaded automatically on first use
|
|
4568
|
-
- **binary** \u2014 Platform-specific binaries, downloaded to \`~/.openacp/agents/\`
|
|
4569
|
-
|
|
4570
|
-
### Project Folder (Workspace)
|
|
4571
|
-
The directory where the agent reads, writes, and runs code.
|
|
4572
|
-
When creating a session, you choose which folder the agent works in.
|
|
4573
|
-
You can type a full path like \`~/code/my-project\` or just a name like \`my-project\` (it becomes \`<base-dir>/my-project\`).
|
|
4574
|
-
|
|
4575
|
-
### System Topics
|
|
4576
|
-
- **Assistant** \u2014 Always-on helper that can answer questions, create sessions, check status, troubleshoot
|
|
4577
|
-
- **Notifications** \u2014 System alerts (permission requests, session errors, completions)
|
|
4578
|
-
|
|
4579
|
-
---
|
|
4580
|
-
|
|
4581
|
-
## Creating Sessions
|
|
4582
|
-
|
|
4583
|
-
### From menu
|
|
4584
|
-
Tap \u{1F195} New Session \u2192 choose agent (if multiple) \u2192 choose project folder \u2192 confirm
|
|
4585
|
-
|
|
4586
|
-
### From command
|
|
4587
|
-
- \`/new\` \u2014 Interactive flow (asks agent + folder)
|
|
4588
|
-
- \`/new claude ~/code/my-project\` \u2014 Create directly with specific agent and folder
|
|
4589
|
-
|
|
4590
|
-
### From Assistant topic
|
|
4591
|
-
Just ask: "Create a session for my-project with claude" \u2014 the assistant handles it
|
|
4592
|
-
|
|
4593
|
-
### Quick new chat
|
|
4594
|
-
\`/newchat\` in a session topic \u2014 creates new session with same agent and folder as current one
|
|
4595
|
-
|
|
4596
|
-
---
|
|
4597
|
-
|
|
4598
|
-
## Working with Sessions
|
|
4599
|
-
|
|
4600
|
-
### Chat
|
|
4601
|
-
Type messages in the session topic. The agent responds with code changes, explanations, tool outputs.
|
|
4602
|
-
|
|
4603
|
-
### What you see while the agent works
|
|
4604
|
-
- **\u{1F4AD} Thinking indicator** \u2014 Shows when the agent is reasoning, with elapsed time
|
|
4605
|
-
- **Text responses** \u2014 Streamed in real time, updated every few seconds
|
|
4606
|
-
- **Tool calls** \u2014 When the agent runs commands or edits files, you see tool name, input, status, and output
|
|
4607
|
-
- **\u{1F4CB} Plan card** \u2014 Visual task progress with completed/in-progress/pending items and progress bar
|
|
4608
|
-
- **"View File" / "View Diff" buttons** \u2014 Opens in browser with Monaco editor (requires tunnel)
|
|
4609
|
-
|
|
4610
|
-
### Session lifecycle
|
|
4611
|
-
1. **Creating** \u2014 Topic created, agent spawning
|
|
4612
|
-
2. **Warming up** \u2014 Agent primes its cache (happens automatically, invisible to you)
|
|
4613
|
-
3. **Active** \u2014 Ready for your messages
|
|
4614
|
-
4. **Auto-naming** \u2014 After your first message, the session gets a descriptive name (agent summarizes in ~5 words). The topic title updates automatically.
|
|
4615
|
-
5. **Finished/Error** \u2014 Session completed or hit an error
|
|
4616
|
-
|
|
4617
|
-
### Agent skills
|
|
4618
|
-
Some agents provide slash commands (e.g., /compact, /review). Available skills are pinned in the session topic.
|
|
4619
|
-
|
|
4620
|
-
### Permission requests
|
|
4621
|
-
When the agent wants to run a command, it asks for permission.
|
|
4622
|
-
You see buttons: \u2705 Allow, \u274C Reject (and sometimes "Always Allow").
|
|
4623
|
-
A notification also appears in the Notifications topic with a link to the request.
|
|
4624
|
-
|
|
4625
|
-
### Dangerous mode
|
|
4626
|
-
Auto-approves ALL permission requests \u2014 the agent runs any command without asking.
|
|
4627
|
-
- Enable: \`/enable_dangerous\` or tap the \u2620\uFE0F button in the session
|
|
4628
|
-
- Disable: \`/disable_dangerous\` or tap the \u{1F510} button
|
|
4629
|
-
- \u26A0\uFE0F Use with caution \u2014 the agent can execute anything
|
|
4630
|
-
|
|
4631
|
-
### Session timeout
|
|
4632
|
-
Idle sessions are automatically cancelled after a configurable timeout (default: 60 minutes).
|
|
4633
|
-
Configure via \`security.sessionTimeoutMinutes\` in config.
|
|
4634
|
-
|
|
4635
|
-
---
|
|
4636
|
-
|
|
4637
|
-
## Session Transfer (Handoff)
|
|
4638
|
-
|
|
4639
|
-
### Telegram \u2192 Terminal
|
|
4640
|
-
1. Type \`/handoff\` in a session topic
|
|
4641
|
-
2. You get a command like \`claude --resume <SESSION_ID>\`
|
|
4642
|
-
3. Copy and run it in your terminal \u2014 the session continues there with full conversation history
|
|
4643
|
-
|
|
4644
|
-
### Terminal \u2192 Telegram
|
|
4645
|
-
1. First time: run \`openacp integrate claude\` to install the handoff skill (one-time setup)
|
|
4646
|
-
2. In Claude Code, use the /openacp:handoff slash command
|
|
4647
|
-
3. The session appears as a new topic in Telegram and you can continue chatting there
|
|
4648
|
-
|
|
4649
|
-
### How it works
|
|
4650
|
-
- The agent session ID is shared between platforms
|
|
4651
|
-
- Conversation history is preserved \u2014 pick up where you left off
|
|
4652
|
-
- The agent that supports resume (e.g., Claude with \`--resume\`) handles the actual transfer
|
|
4653
|
-
|
|
4654
|
-
---
|
|
4655
|
-
|
|
4656
|
-
## Managing Sessions
|
|
4657
|
-
|
|
4658
|
-
### Status
|
|
4659
|
-
- \`/status\` \u2014 Shows active sessions count and details
|
|
4660
|
-
- Ask the Assistant: "What sessions are running?"
|
|
4661
|
-
|
|
4662
|
-
### List all sessions
|
|
4663
|
-
- \`/sessions\` \u2014 Shows all sessions with status (active, finished, error)
|
|
4664
|
-
|
|
4665
|
-
### Cancel
|
|
4666
|
-
- \`/cancel\` in a session topic \u2014 cancels that session
|
|
4667
|
-
- Ask the Assistant: "Cancel the stuck session"
|
|
4668
|
-
|
|
4669
|
-
### Cleanup
|
|
4670
|
-
- From \`/sessions\` \u2192 tap cleanup buttons (finished, errors, all)
|
|
4671
|
-
- Ask the Assistant: "Clean up old sessions"
|
|
4672
|
-
|
|
4673
|
-
---
|
|
4674
|
-
|
|
4675
|
-
## Assistant Topic
|
|
4676
|
-
|
|
4677
|
-
The Assistant is an always-on AI helper in its own topic. It can:
|
|
4678
|
-
- Answer questions about OpenACP
|
|
4679
|
-
- Create sessions for you
|
|
4680
|
-
- Check status and health
|
|
4681
|
-
- Cancel sessions
|
|
4682
|
-
- Clean up old sessions
|
|
4683
|
-
- Troubleshoot issues
|
|
4684
|
-
- Manage configuration
|
|
4685
|
-
|
|
4686
|
-
Just chat naturally: "How do I create a session?", "What's the status?", "Something is stuck"
|
|
4687
|
-
|
|
4688
|
-
### Clear history
|
|
4689
|
-
\`/clear\` in the Assistant topic \u2014 resets the conversation
|
|
4690
|
-
|
|
4691
|
-
---
|
|
4692
|
-
|
|
4693
|
-
## System Commands
|
|
4694
|
-
|
|
4695
|
-
| Command | Where | What it does |
|
|
4696
|
-
|---------|-------|-------------|
|
|
4697
|
-
| \`/new [agent] [path]\` | Anywhere | Create new session |
|
|
4698
|
-
| \`/newchat\` | Session topic | New session, same agent + folder |
|
|
4699
|
-
| \`/cancel\` | Session topic | Cancel current session |
|
|
4700
|
-
| \`/status\` | Anywhere | Show status |
|
|
4701
|
-
| \`/sessions\` | Anywhere | List all sessions |
|
|
4702
|
-
| \`/agents\` | Anywhere | Browse & install agents from ACP Registry |
|
|
4703
|
-
| \`/install <name>\` | Anywhere | Install an agent |
|
|
4704
|
-
| \`/enable_dangerous\` | Session topic | Auto-approve all permissions |
|
|
4705
|
-
| \`/disable_dangerous\` | Session topic | Restore permission prompts |
|
|
4706
|
-
| \`/handoff\` | Session topic | Transfer session to terminal |
|
|
4707
|
-
| \`/clear\` | Assistant topic | Clear assistant history |
|
|
4708
|
-
| \`/menu\` | Anywhere | Show action menu |
|
|
4709
|
-
| \`/help\` | Anywhere | Show help |
|
|
4710
|
-
| \`/restart\` | Anywhere | Restart OpenACP |
|
|
4711
|
-
| \`/update\` | Anywhere | Update to latest version |
|
|
4712
|
-
| \`/integrate\` | Anywhere | Manage agent integrations |
|
|
4713
|
-
|
|
4714
|
-
---
|
|
4715
|
-
|
|
4716
|
-
## Menu Buttons
|
|
4717
|
-
|
|
4718
|
-
| Button | Action |
|
|
4719
|
-
|--------|--------|
|
|
4720
|
-
| \u{1F195} New Session | Create new session (interactive) |
|
|
4721
|
-
| \u{1F4CB} Sessions | List all sessions with cleanup options |
|
|
4722
|
-
| \u{1F4CA} Status | Show active/total session count |
|
|
4723
|
-
| \u{1F916} Agents | List available agents |
|
|
4724
|
-
| \u{1F517} Integrate | Manage agent integrations |
|
|
4725
|
-
| \u2753 Help | Show help text |
|
|
4726
|
-
| \u{1F504} Restart | Restart OpenACP |
|
|
4727
|
-
| \u2B06\uFE0F Update | Check and install updates |
|
|
4728
|
-
|
|
4729
|
-
---
|
|
4730
|
-
|
|
4731
|
-
## CLI Commands
|
|
4732
|
-
|
|
4733
|
-
### Server
|
|
4734
|
-
- \`openacp\` \u2014 Start (uses configured mode: foreground or daemon)
|
|
4735
|
-
- \`openacp start\` \u2014 Start as background daemon
|
|
4736
|
-
- \`openacp stop\` \u2014 Stop daemon
|
|
4737
|
-
- \`openacp status\` \u2014 Show daemon status
|
|
4738
|
-
- \`openacp logs\` \u2014 Tail daemon logs
|
|
4739
|
-
- \`openacp --foreground\` \u2014 Force foreground mode (useful for debugging or containers)
|
|
4740
|
-
|
|
4741
|
-
### Auto-start (run on boot)
|
|
4742
|
-
- macOS: installs a LaunchAgent in \`~/Library/LaunchAgents/\`
|
|
4743
|
-
- Linux: installs a systemd user service in \`~/.config/systemd/user/\`
|
|
4744
|
-
- Enabled automatically when you start the daemon. Remove with \`openacp stop\`.
|
|
4745
|
-
|
|
4746
|
-
### Configuration
|
|
4747
|
-
- \`openacp config\` \u2014 Interactive config editor
|
|
4748
|
-
- \`openacp reset\` \u2014 Delete all data and start fresh
|
|
4749
|
-
|
|
4750
|
-
### Agent Management (CLI)
|
|
4751
|
-
- \`openacp agents\` \u2014 List all agents (installed + available from ACP Registry)
|
|
4752
|
-
- \`openacp agents install <name>\` \u2014 Install an agent
|
|
4753
|
-
- \`openacp agents uninstall <name>\` \u2014 Remove an agent
|
|
4754
|
-
- \`openacp agents info <name>\` \u2014 Show details, dependencies, and setup guide
|
|
4755
|
-
- \`openacp agents run <name> [-- args]\` \u2014 Run agent CLI directly (for login, config, etc.)
|
|
4756
|
-
- \`openacp agents refresh\` \u2014 Force-refresh registry cache
|
|
4757
|
-
|
|
4758
|
-
### Plugins
|
|
4759
|
-
- \`openacp install <package>\` \u2014 Install adapter plugin (e.g., \`@openacp/adapter-discord\`)
|
|
4760
|
-
- \`openacp uninstall <package>\` \u2014 Remove adapter plugin
|
|
4761
|
-
- \`openacp plugins\` \u2014 List installed plugins
|
|
4762
|
-
|
|
4763
|
-
### Integration
|
|
4764
|
-
- \`openacp integrate <agent>\` \u2014 Install agent integration (e.g., Claude handoff skill)
|
|
4765
|
-
- \`openacp integrate <agent> --uninstall\` \u2014 Remove integration
|
|
4766
|
-
|
|
4767
|
-
### API (requires running daemon)
|
|
4768
|
-
\`openacp api <command>\` \u2014 Interact with running daemon:
|
|
4769
|
-
|
|
4770
|
-
| Command | Description |
|
|
4771
|
-
|---------|-------------|
|
|
4772
|
-
| \`status\` | List active sessions |
|
|
4773
|
-
| \`session <id>\` | Session details |
|
|
4774
|
-
| \`new <agent> <path>\` | Create session |
|
|
4775
|
-
| \`send <id> "text"\` | Send prompt |
|
|
4776
|
-
| \`cancel <id>\` | Cancel session |
|
|
4777
|
-
| \`dangerous <id> on/off\` | Toggle dangerous mode |
|
|
4778
|
-
| \`topics [--status x,y]\` | List topics |
|
|
4779
|
-
| \`delete-topic <id> [--force]\` | Delete topic |
|
|
4780
|
-
| \`cleanup [--status x,y]\` | Cleanup old topics |
|
|
4781
|
-
| \`agents\` | List agents |
|
|
4782
|
-
| \`health\` | System health |
|
|
4783
|
-
| \`config\` | Show config |
|
|
4784
|
-
| \`config set <key> <value>\` | Update config |
|
|
4785
|
-
| \`adapters\` | List adapters |
|
|
4786
|
-
| \`tunnel\` | Tunnel status |
|
|
4787
|
-
| \`notify "message"\` | Send notification |
|
|
4788
|
-
| \`version\` | Daemon version |
|
|
4789
|
-
| \`restart\` | Restart daemon |
|
|
4790
|
-
|
|
4791
|
-
---
|
|
4792
|
-
|
|
4793
|
-
## File Viewer (Tunnel)
|
|
4794
|
-
|
|
4795
|
-
When tunnel is enabled, file edits and diffs get "View" buttons that open in your browser:
|
|
4796
|
-
- **Monaco Editor** \u2014 Full VS Code editor with syntax highlighting
|
|
4797
|
-
- **Diff viewer** \u2014 Side-by-side or inline comparison
|
|
4798
|
-
- **Line highlighting** \u2014 Click lines to highlight
|
|
4799
|
-
- Dark/light theme toggle
|
|
4800
|
-
|
|
4801
|
-
### Setup
|
|
4802
|
-
Enable in config: set \`tunnel.enabled\` to \`true\`.
|
|
4803
|
-
Providers: Cloudflare (default, free), ngrok, bore, Tailscale Funnel.
|
|
4804
|
-
|
|
4805
|
-
---
|
|
4806
|
-
|
|
4807
|
-
## Configuration
|
|
4808
|
-
|
|
4809
|
-
Config file: \`~/.openacp/config.json\`
|
|
4810
|
-
|
|
4811
|
-
### Telegram
|
|
4812
|
-
- **telegram.botToken** \u2014 Your Telegram bot token
|
|
4813
|
-
- **telegram.chatId** \u2014 Your Telegram supergroup ID
|
|
4814
|
-
|
|
4815
|
-
### Agents
|
|
4816
|
-
- **defaultAgent** \u2014 Which agent to use by default
|
|
4817
|
-
- Agents are managed via \`/agents\` (Telegram) or \`openacp agents\` (CLI)
|
|
4818
|
-
- Installed agents are stored in \`~/.openacp/agents.json\`
|
|
4819
|
-
- Agent list is fetched from the ACP Registry CDN and cached locally (24h)
|
|
4820
|
-
|
|
4821
|
-
### Workspace
|
|
4822
|
-
- **workspace.baseDir** \u2014 Base directory for project folders (default: \`~/openacp-workspace\`)
|
|
4823
|
-
|
|
4824
|
-
### Security
|
|
4825
|
-
- **security.allowedUserIds** \u2014 Restrict who can use the bot (empty = everyone)
|
|
4826
|
-
- **security.maxConcurrentSessions** \u2014 Max parallel sessions (default: 5)
|
|
4827
|
-
- **security.sessionTimeoutMinutes** \u2014 Auto-cancel idle sessions (default: 60)
|
|
4828
|
-
|
|
4829
|
-
### Tunnel / File Viewer
|
|
4830
|
-
- **tunnel.enabled** \u2014 Enable file viewer tunnel
|
|
4831
|
-
- **tunnel.provider** \u2014 Tunnel provider: cloudflare (default, free), ngrok, bore, tailscale
|
|
4832
|
-
- **tunnel.port** \u2014 Local port for tunnel server (default: 3100)
|
|
4833
|
-
- **tunnel.auth.enabled** \u2014 Enable authentication for tunnel URLs
|
|
4834
|
-
- **tunnel.auth.token** \u2014 Auth token for tunnel access
|
|
4835
|
-
- **tunnel.storeTtlMinutes** \u2014 How long viewer links stay cached (default: 60)
|
|
4836
|
-
|
|
4837
|
-
### Logging
|
|
4838
|
-
- **logging.level** \u2014 Log level: silent, debug, info, warn, error, fatal (default: info)
|
|
4839
|
-
- **logging.logDir** \u2014 Log directory (default: \`~/.openacp/logs\`)
|
|
4840
|
-
- **logging.maxFileSize** \u2014 Max log file size before rotation
|
|
4841
|
-
- **logging.maxFiles** \u2014 Max number of rotated log files
|
|
4842
|
-
- **logging.sessionLogRetentionDays** \u2014 Auto-delete old session logs (default: 30)
|
|
4843
|
-
|
|
4844
|
-
### Data Retention
|
|
4845
|
-
- **sessionStore.ttlDays** \u2014 How long session records persist (default: 30). Old records are cleaned up automatically.
|
|
4846
|
-
|
|
4847
|
-
### Environment variables
|
|
4848
|
-
Override config with env vars:
|
|
4849
|
-
- \`OPENACP_TELEGRAM_BOT_TOKEN\`
|
|
4850
|
-
- \`OPENACP_TELEGRAM_CHAT_ID\`
|
|
4851
|
-
- \`OPENACP_DEFAULT_AGENT\`
|
|
4852
|
-
- \`OPENACP_RUN_MODE\` \u2014 foreground or daemon
|
|
4853
|
-
- \`OPENACP_API_PORT\` \u2014 API server port (default: 21420)
|
|
4854
|
-
- \`OPENACP_TUNNEL_ENABLED\`
|
|
4855
|
-
- \`OPENACP_TUNNEL_PORT\`
|
|
4856
|
-
- \`OPENACP_TUNNEL_PROVIDER\`
|
|
4857
|
-
- \`OPENACP_LOG_LEVEL\`
|
|
4858
|
-
- \`OPENACP_LOG_DIR\`
|
|
4859
|
-
- \`OPENACP_DEBUG\` \u2014 Sets log level to debug
|
|
4860
|
-
|
|
4861
|
-
---
|
|
4862
|
-
|
|
4863
|
-
## Troubleshooting
|
|
4864
|
-
|
|
4865
|
-
### Session stuck / not responding
|
|
4866
|
-
- Check status: ask Assistant "Is anything stuck?"
|
|
4867
|
-
- Cancel and create new: \`/cancel\` then \`/new\`
|
|
4868
|
-
- Check system health: Assistant can run health check
|
|
4869
|
-
|
|
4870
|
-
### Agent not found
|
|
4871
|
-
- Check available agents: \`/agents\` or \`openacp agents\`
|
|
4872
|
-
- Install missing agent: \`openacp agents install <name>\`
|
|
4873
|
-
- Some agents need login first: \`openacp agents info <name>\` to see setup steps
|
|
4874
|
-
- Run agent CLI for setup: \`openacp agents run <name> -- <args>\`
|
|
4875
|
-
|
|
4876
|
-
### Permission request not showing
|
|
4877
|
-
- Check Notifications topic for the alert
|
|
4878
|
-
- Try \`/enable_dangerous\` to auto-approve (if you trust the agent)
|
|
4879
|
-
|
|
4880
|
-
### Session disappeared after restart
|
|
4881
|
-
- Sessions persist across restarts
|
|
4882
|
-
- Send a message in the old topic \u2014 it auto-resumes
|
|
4883
|
-
- If topic was deleted, the session record may still exist in status
|
|
4884
|
-
|
|
4885
|
-
### Bot not responding at all
|
|
4886
|
-
- Check daemon: \`openacp status\`
|
|
4887
|
-
- Check logs: \`openacp logs\`
|
|
4888
|
-
- Restart: \`openacp start\` or \`/restart\`
|
|
4889
|
-
|
|
4890
|
-
### Messages going to wrong topic
|
|
4891
|
-
- Each session is bound to a specific Telegram topic
|
|
4892
|
-
- If you see messages appearing in the Assistant topic instead of the session topic, try creating a new session
|
|
4893
|
-
|
|
4894
|
-
### Viewing logs
|
|
4895
|
-
- Session-specific logs: \`~/.openacp/logs/sessions/\`
|
|
4896
|
-
- System logs: \`openacp logs\` to tail live
|
|
4897
|
-
- Set \`OPENACP_DEBUG=true\` for verbose output
|
|
4898
|
-
|
|
4899
|
-
---
|
|
4900
|
-
|
|
4901
|
-
## Data & Storage
|
|
4902
|
-
|
|
4903
|
-
All data is stored in \`~/.openacp/\`:
|
|
4904
|
-
- \`config.json\` \u2014 Configuration
|
|
4905
|
-
- \`agents.json\` \u2014 Installed agents (managed by AgentCatalog)
|
|
4906
|
-
- \`registry-cache.json\` \u2014 Cached ACP Registry data (refreshes every 24h)
|
|
4907
|
-
- \`agents/\` \u2014 Downloaded binary agents
|
|
4908
|
-
- \`sessions/\` \u2014 Session records and state
|
|
4909
|
-
- \`topics/\` \u2014 Topic-to-session mappings
|
|
4910
|
-
- \`logs/\` \u2014 System and session logs
|
|
4911
|
-
- \`plugins/\` \u2014 Installed adapter plugins
|
|
4912
|
-
- \`openacp.pid\` \u2014 Daemon PID file
|
|
4913
|
-
|
|
4914
|
-
Session records auto-cleanup: 30 days (configurable via \`sessionStore.ttlDays\`).
|
|
4915
|
-
Session logs auto-cleanup: 30 days (configurable via \`logging.sessionLogRetentionDays\`).
|
|
4916
|
-
`;
|
|
4917
|
-
|
|
4918
4800
|
// src/adapters/telegram/assistant.ts
|
|
4919
|
-
var
|
|
4801
|
+
var log16 = createChildLogger({ module: "telegram-assistant" });
|
|
4920
4802
|
async function spawnAssistant(core, adapter, assistantTopicId) {
|
|
4921
4803
|
const config = core.configManager.get();
|
|
4922
|
-
|
|
4804
|
+
log16.info({ agent: config.defaultAgent }, "Creating assistant session...");
|
|
4923
4805
|
const session = await core.createSession({
|
|
4924
4806
|
channelId: "telegram",
|
|
4925
4807
|
agentName: config.defaultAgent,
|
|
@@ -4928,7 +4810,7 @@ async function spawnAssistant(core, adapter, assistantTopicId) {
|
|
|
4928
4810
|
// Prevent auto-naming from triggering after system prompt
|
|
4929
4811
|
});
|
|
4930
4812
|
session.threadId = String(assistantTopicId);
|
|
4931
|
-
|
|
4813
|
+
log16.info({ sessionId: session.id }, "Assistant agent spawned");
|
|
4932
4814
|
const allRecords = core.sessionManager.listRecords();
|
|
4933
4815
|
const activeCount = allRecords.filter((r) => r.status === "active" || r.status === "initializing").length;
|
|
4934
4816
|
const statusCounts = /* @__PURE__ */ new Map();
|
|
@@ -4949,9 +4831,9 @@ async function spawnAssistant(core, adapter, assistantTopicId) {
|
|
|
4949
4831
|
};
|
|
4950
4832
|
const systemPrompt = buildAssistantSystemPrompt(ctx);
|
|
4951
4833
|
const ready = session.enqueuePrompt(systemPrompt).then(() => {
|
|
4952
|
-
|
|
4834
|
+
log16.info({ sessionId: session.id }, "Assistant system prompt completed");
|
|
4953
4835
|
}).catch((err) => {
|
|
4954
|
-
|
|
4836
|
+
log16.warn({ err }, "Assistant system prompt failed");
|
|
4955
4837
|
});
|
|
4956
4838
|
return { session, ready };
|
|
4957
4839
|
}
|
|
@@ -5106,7 +4988,7 @@ function redirectToAssistant(chatId, assistantTopicId) {
|
|
|
5106
4988
|
}
|
|
5107
4989
|
|
|
5108
4990
|
// src/adapters/telegram/activity.ts
|
|
5109
|
-
var
|
|
4991
|
+
var log17 = createChildLogger({ module: "telegram:activity" });
|
|
5110
4992
|
var THINKING_REFRESH_MS = 15e3;
|
|
5111
4993
|
var THINKING_MAX_MS = 3 * 60 * 1e3;
|
|
5112
4994
|
var ThinkingIndicator = class {
|
|
@@ -5138,7 +5020,7 @@ var ThinkingIndicator = class {
|
|
|
5138
5020
|
this.startRefreshTimer();
|
|
5139
5021
|
}
|
|
5140
5022
|
} catch (err) {
|
|
5141
|
-
|
|
5023
|
+
log17.warn({ err }, "ThinkingIndicator.show() failed");
|
|
5142
5024
|
} finally {
|
|
5143
5025
|
this.sending = false;
|
|
5144
5026
|
}
|
|
@@ -5211,7 +5093,7 @@ var UsageMessage = class {
|
|
|
5211
5093
|
if (result) this.msgId = result.message_id;
|
|
5212
5094
|
}
|
|
5213
5095
|
} catch (err) {
|
|
5214
|
-
|
|
5096
|
+
log17.warn({ err }, "UsageMessage.send() failed");
|
|
5215
5097
|
}
|
|
5216
5098
|
}
|
|
5217
5099
|
getMsgId() {
|
|
@@ -5224,7 +5106,7 @@ var UsageMessage = class {
|
|
|
5224
5106
|
try {
|
|
5225
5107
|
await this.sendQueue.enqueue(() => this.api.deleteMessage(this.chatId, id));
|
|
5226
5108
|
} catch (err) {
|
|
5227
|
-
|
|
5109
|
+
log17.warn({ err }, "UsageMessage.delete() failed");
|
|
5228
5110
|
}
|
|
5229
5111
|
}
|
|
5230
5112
|
};
|
|
@@ -5310,7 +5192,7 @@ var PlanCard = class {
|
|
|
5310
5192
|
if (result) this.msgId = result.message_id;
|
|
5311
5193
|
}
|
|
5312
5194
|
} catch (err) {
|
|
5313
|
-
|
|
5195
|
+
log17.warn({ err }, "PlanCard flush failed");
|
|
5314
5196
|
}
|
|
5315
5197
|
}
|
|
5316
5198
|
};
|
|
@@ -5373,7 +5255,7 @@ var ActivityTracker = class {
|
|
|
5373
5255
|
})
|
|
5374
5256
|
);
|
|
5375
5257
|
} catch (err) {
|
|
5376
|
-
|
|
5258
|
+
log17.warn({ err }, "ActivityTracker.onComplete() Done send failed");
|
|
5377
5259
|
}
|
|
5378
5260
|
}
|
|
5379
5261
|
}
|
|
@@ -5456,7 +5338,7 @@ var TelegramSendQueue = class {
|
|
|
5456
5338
|
|
|
5457
5339
|
// src/adapters/telegram/action-detect.ts
|
|
5458
5340
|
import { nanoid as nanoid3 } from "nanoid";
|
|
5459
|
-
import { InlineKeyboard as
|
|
5341
|
+
import { InlineKeyboard as InlineKeyboard10 } from "grammy";
|
|
5460
5342
|
var CMD_NEW_RE = /\/new(?:\s+([^\s\u0080-\uFFFF]+)(?:\s+([^\s\u0080-\uFFFF]+))?)?/;
|
|
5461
5343
|
var CMD_CANCEL_RE = /\/cancel\b/;
|
|
5462
5344
|
var KW_NEW_RE = /(?:create|new)\s+session/i;
|
|
@@ -5503,7 +5385,7 @@ function removeAction(id) {
|
|
|
5503
5385
|
actionMap.delete(id);
|
|
5504
5386
|
}
|
|
5505
5387
|
function buildActionKeyboard(actionId, action) {
|
|
5506
|
-
const keyboard = new
|
|
5388
|
+
const keyboard = new InlineKeyboard10();
|
|
5507
5389
|
if (action.action === "new_session") {
|
|
5508
5390
|
keyboard.text("\u2705 Create session", `a:${actionId}`);
|
|
5509
5391
|
keyboard.text("\u274C Cancel", `a:dismiss:${actionId}`);
|
|
@@ -5611,7 +5493,7 @@ function setupActionCallbacks(bot, core, chatId, getAssistantSessionId) {
|
|
|
5611
5493
|
}
|
|
5612
5494
|
|
|
5613
5495
|
// src/adapters/telegram/tool-call-tracker.ts
|
|
5614
|
-
var
|
|
5496
|
+
var log18 = createChildLogger({ module: "tool-call-tracker" });
|
|
5615
5497
|
var ToolCallTracker = class {
|
|
5616
5498
|
constructor(bot, chatId, sendQueue) {
|
|
5617
5499
|
this.bot = bot;
|
|
@@ -5655,7 +5537,7 @@ var ToolCallTracker = class {
|
|
|
5655
5537
|
if (!toolState) return;
|
|
5656
5538
|
if (meta.viewerLinks) {
|
|
5657
5539
|
toolState.viewerLinks = meta.viewerLinks;
|
|
5658
|
-
|
|
5540
|
+
log18.debug({ toolId: meta.id, viewerLinks: meta.viewerLinks }, "Accumulated viewerLinks");
|
|
5659
5541
|
}
|
|
5660
5542
|
if (meta.viewerFilePath) toolState.viewerFilePath = meta.viewerFilePath;
|
|
5661
5543
|
if (meta.name) toolState.name = meta.name;
|
|
@@ -5663,7 +5545,7 @@ var ToolCallTracker = class {
|
|
|
5663
5545
|
const isTerminal = meta.status === "completed" || meta.status === "failed";
|
|
5664
5546
|
if (!isTerminal) return;
|
|
5665
5547
|
await toolState.ready;
|
|
5666
|
-
|
|
5548
|
+
log18.debug(
|
|
5667
5549
|
{
|
|
5668
5550
|
toolId: meta.id,
|
|
5669
5551
|
status: meta.status,
|
|
@@ -5692,7 +5574,7 @@ var ToolCallTracker = class {
|
|
|
5692
5574
|
)
|
|
5693
5575
|
);
|
|
5694
5576
|
} catch (err) {
|
|
5695
|
-
|
|
5577
|
+
log18.warn(
|
|
5696
5578
|
{
|
|
5697
5579
|
err,
|
|
5698
5580
|
msgId: toolState.msgId,
|
|
@@ -5942,7 +5824,7 @@ var DraftManager = class {
|
|
|
5942
5824
|
};
|
|
5943
5825
|
|
|
5944
5826
|
// src/adapters/telegram/skill-command-manager.ts
|
|
5945
|
-
var
|
|
5827
|
+
var log19 = createChildLogger({ module: "skill-commands" });
|
|
5946
5828
|
var SkillCommandManager = class {
|
|
5947
5829
|
// sessionId → pinned msgId
|
|
5948
5830
|
constructor(bot, chatId, sendQueue, sessionManager) {
|
|
@@ -6008,7 +5890,7 @@ var SkillCommandManager = class {
|
|
|
6008
5890
|
disable_notification: true
|
|
6009
5891
|
});
|
|
6010
5892
|
} catch (err) {
|
|
6011
|
-
|
|
5893
|
+
log19.error({ err, sessionId }, "Failed to send skill commands");
|
|
6012
5894
|
}
|
|
6013
5895
|
}
|
|
6014
5896
|
async cleanup(sessionId) {
|
|
@@ -6034,7 +5916,7 @@ var SkillCommandManager = class {
|
|
|
6034
5916
|
};
|
|
6035
5917
|
|
|
6036
5918
|
// src/adapters/telegram/adapter.ts
|
|
6037
|
-
var
|
|
5919
|
+
var log20 = createChildLogger({ module: "telegram" });
|
|
6038
5920
|
function patchedFetch(input, init) {
|
|
6039
5921
|
if (init?.signal && !(init.signal instanceof AbortSignal)) {
|
|
6040
5922
|
const nativeController = new AbortController();
|
|
@@ -6093,7 +5975,7 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
6093
5975
|
);
|
|
6094
5976
|
this.bot.catch((err) => {
|
|
6095
5977
|
const rootCause = err.error instanceof Error ? err.error : err;
|
|
6096
|
-
|
|
5978
|
+
log20.error({ err: rootCause }, "Telegram bot error");
|
|
6097
5979
|
});
|
|
6098
5980
|
this.bot.api.config.use(async (prev, method, payload, signal) => {
|
|
6099
5981
|
const maxRetries = 3;
|
|
@@ -6107,7 +5989,7 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
6107
5989
|
if (rateLimitedMethods.includes(method)) {
|
|
6108
5990
|
this.sendQueue.onRateLimited();
|
|
6109
5991
|
}
|
|
6110
|
-
|
|
5992
|
+
log20.warn(
|
|
6111
5993
|
{ method, retryAfter, attempt: attempt + 1 },
|
|
6112
5994
|
"Rate limited by Telegram, retrying"
|
|
6113
5995
|
);
|
|
@@ -6239,7 +6121,7 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
6239
6121
|
this.setupRoutes();
|
|
6240
6122
|
this.bot.start({
|
|
6241
6123
|
allowed_updates: ["message", "callback_query"],
|
|
6242
|
-
onStart: () =>
|
|
6124
|
+
onStart: () => log20.info(
|
|
6243
6125
|
{ chatId: this.telegramConfig.chatId },
|
|
6244
6126
|
"Telegram bot started"
|
|
6245
6127
|
)
|
|
@@ -6261,10 +6143,10 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
6261
6143
|
reply_markup: buildMenuKeyboard()
|
|
6262
6144
|
});
|
|
6263
6145
|
} catch (err) {
|
|
6264
|
-
|
|
6146
|
+
log20.warn({ err }, "Failed to send welcome message");
|
|
6265
6147
|
}
|
|
6266
6148
|
try {
|
|
6267
|
-
|
|
6149
|
+
log20.info("Spawning assistant session...");
|
|
6268
6150
|
const { session, ready } = await spawnAssistant(
|
|
6269
6151
|
this.core,
|
|
6270
6152
|
this,
|
|
@@ -6272,13 +6154,13 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
6272
6154
|
);
|
|
6273
6155
|
this.assistantSession = session;
|
|
6274
6156
|
this.assistantInitializing = true;
|
|
6275
|
-
|
|
6157
|
+
log20.info({ sessionId: session.id }, "Assistant session ready, system prompt running in background");
|
|
6276
6158
|
ready.then(() => {
|
|
6277
6159
|
this.assistantInitializing = false;
|
|
6278
|
-
|
|
6160
|
+
log20.info({ sessionId: session.id }, "Assistant ready for user messages");
|
|
6279
6161
|
});
|
|
6280
6162
|
} catch (err) {
|
|
6281
|
-
|
|
6163
|
+
log20.error({ err }, "Failed to spawn assistant");
|
|
6282
6164
|
this.bot.api.sendMessage(
|
|
6283
6165
|
this.telegramConfig.chatId,
|
|
6284
6166
|
`\u26A0\uFE0F <b>Failed to start assistant session.</b>
|
|
@@ -6294,11 +6176,12 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
6294
6176
|
await this.assistantSession.destroy();
|
|
6295
6177
|
}
|
|
6296
6178
|
await this.bot.stop();
|
|
6297
|
-
|
|
6179
|
+
log20.info("Telegram bot stopped");
|
|
6298
6180
|
}
|
|
6299
6181
|
setupRoutes() {
|
|
6300
6182
|
this.bot.on("message:text", async (ctx) => {
|
|
6301
6183
|
const threadId = ctx.message.message_thread_id;
|
|
6184
|
+
const text = ctx.message.text;
|
|
6302
6185
|
if (await handlePendingWorkspaceInput(ctx, this.core, this.telegramConfig.chatId, this.assistantTopicId)) {
|
|
6303
6186
|
return;
|
|
6304
6187
|
}
|
|
@@ -6311,6 +6194,7 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
6311
6194
|
return;
|
|
6312
6195
|
}
|
|
6313
6196
|
if (threadId === this.notificationTopicId) return;
|
|
6197
|
+
const forwardText = text.startsWith("/") ? text.slice(1) : text;
|
|
6314
6198
|
if (threadId === this.assistantTopicId) {
|
|
6315
6199
|
if (!this.assistantSession) {
|
|
6316
6200
|
await ctx.reply("\u26A0\uFE0F Assistant is not available yet. Please try again shortly.", { parse_mode: "HTML" });
|
|
@@ -6319,8 +6203,8 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
6319
6203
|
await this.draftManager.finalize(this.assistantSession.id, this.assistantSession.id);
|
|
6320
6204
|
ctx.replyWithChatAction("typing").catch(() => {
|
|
6321
6205
|
});
|
|
6322
|
-
handleAssistantMessage(this.assistantSession,
|
|
6323
|
-
(err) =>
|
|
6206
|
+
handleAssistantMessage(this.assistantSession, forwardText).catch(
|
|
6207
|
+
(err) => log20.error({ err }, "Assistant error")
|
|
6324
6208
|
);
|
|
6325
6209
|
return;
|
|
6326
6210
|
}
|
|
@@ -6336,8 +6220,8 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
6336
6220
|
channelId: "telegram",
|
|
6337
6221
|
threadId: String(threadId),
|
|
6338
6222
|
userId: String(ctx.from.id),
|
|
6339
|
-
text:
|
|
6340
|
-
}).catch((err) =>
|
|
6223
|
+
text: forwardText
|
|
6224
|
+
}).catch((err) => log20.error({ err }, "handleMessage error"));
|
|
6341
6225
|
});
|
|
6342
6226
|
this.bot.on("message:photo", async (ctx) => {
|
|
6343
6227
|
const threadId = ctx.message.message_thread_id;
|
|
@@ -6414,7 +6298,7 @@ var TelegramAdapter = class extends ChannelAdapter {
|
|
|
6414
6298
|
if (!session) return;
|
|
6415
6299
|
const threadId = Number(session.threadId);
|
|
6416
6300
|
if (!threadId || isNaN(threadId)) {
|
|
6417
|
-
|
|
6301
|
+
log20.warn({ sessionId, threadId: session.threadId }, "Session has no valid threadId, skipping message");
|
|
6418
6302
|
return;
|
|
6419
6303
|
}
|
|
6420
6304
|
switch (content.type) {
|
|
@@ -6496,7 +6380,7 @@ Task completed.
|
|
|
6496
6380
|
if (!content.attachment) break;
|
|
6497
6381
|
const { attachment } = content;
|
|
6498
6382
|
if (attachment.size > 50 * 1024 * 1024) {
|
|
6499
|
-
|
|
6383
|
+
log20.warn({ sessionId, fileName: attachment.fileName, size: attachment.size }, "File too large for Telegram (>50MB)");
|
|
6500
6384
|
await this.sendQueue.enqueue(
|
|
6501
6385
|
() => this.bot.api.sendMessage(
|
|
6502
6386
|
this.telegramConfig.chatId,
|
|
@@ -6528,7 +6412,7 @@ Task completed.
|
|
|
6528
6412
|
);
|
|
6529
6413
|
}
|
|
6530
6414
|
} catch (err) {
|
|
6531
|
-
|
|
6415
|
+
log20.error({ err, sessionId, fileName: attachment.fileName }, "Failed to send attachment");
|
|
6532
6416
|
}
|
|
6533
6417
|
break;
|
|
6534
6418
|
}
|
|
@@ -6580,13 +6464,13 @@ Task completed.
|
|
|
6580
6464
|
}
|
|
6581
6465
|
}
|
|
6582
6466
|
async sendPermissionRequest(sessionId, request) {
|
|
6583
|
-
|
|
6467
|
+
log20.info({ sessionId, requestId: request.id }, "Permission request sent");
|
|
6584
6468
|
const session = this.core.sessionManager.getSession(sessionId);
|
|
6585
6469
|
if (!session) return;
|
|
6586
6470
|
if (request.description.includes("openacp")) {
|
|
6587
6471
|
const allowOption = request.options.find((o) => o.isAllow);
|
|
6588
6472
|
if (allowOption && session.permissionGate.requestId === request.id) {
|
|
6589
|
-
|
|
6473
|
+
log20.info({ sessionId, requestId: request.id }, "Auto-approving openacp command");
|
|
6590
6474
|
session.permissionGate.resolve(allowOption.id);
|
|
6591
6475
|
}
|
|
6592
6476
|
return;
|
|
@@ -6594,7 +6478,7 @@ Task completed.
|
|
|
6594
6478
|
if (session.dangerousMode) {
|
|
6595
6479
|
const allowOption = request.options.find((o) => o.isAllow);
|
|
6596
6480
|
if (allowOption && session.permissionGate.requestId === request.id) {
|
|
6597
|
-
|
|
6481
|
+
log20.info({ sessionId, requestId: request.id, optionId: allowOption.id }, "Dangerous mode: auto-approving permission");
|
|
6598
6482
|
session.permissionGate.resolve(allowOption.id);
|
|
6599
6483
|
}
|
|
6600
6484
|
return;
|
|
@@ -6605,7 +6489,7 @@ Task completed.
|
|
|
6605
6489
|
}
|
|
6606
6490
|
async sendNotification(notification) {
|
|
6607
6491
|
if (notification.sessionId === this.assistantSession?.id) return;
|
|
6608
|
-
|
|
6492
|
+
log20.info(
|
|
6609
6493
|
{ sessionId: notification.sessionId, type: notification.type },
|
|
6610
6494
|
"Notification sent"
|
|
6611
6495
|
);
|
|
@@ -6641,7 +6525,7 @@ Task completed.
|
|
|
6641
6525
|
);
|
|
6642
6526
|
}
|
|
6643
6527
|
async createSessionThread(sessionId, name) {
|
|
6644
|
-
|
|
6528
|
+
log20.info({ sessionId, name }, "Session topic created");
|
|
6645
6529
|
return String(
|
|
6646
6530
|
await createSessionTopic(this.bot, this.telegramConfig.chatId, name)
|
|
6647
6531
|
);
|
|
@@ -6665,7 +6549,7 @@ Task completed.
|
|
|
6665
6549
|
try {
|
|
6666
6550
|
await this.bot.api.deleteForumTopic(this.telegramConfig.chatId, topicId);
|
|
6667
6551
|
} catch (err) {
|
|
6668
|
-
|
|
6552
|
+
log20.warn({ err, sessionId, topicId }, "Failed to delete forum topic (may already be deleted)");
|
|
6669
6553
|
}
|
|
6670
6554
|
}
|
|
6671
6555
|
async sendSkillCommands(sessionId, commands) {
|
|
@@ -6689,7 +6573,7 @@ Task completed.
|
|
|
6689
6573
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
6690
6574
|
return { buffer, filePath: file.file_path };
|
|
6691
6575
|
} catch (err) {
|
|
6692
|
-
|
|
6576
|
+
log20.error({ err }, "Failed to download file from Telegram");
|
|
6693
6577
|
return null;
|
|
6694
6578
|
}
|
|
6695
6579
|
}
|
|
@@ -6701,14 +6585,15 @@ Task completed.
|
|
|
6701
6585
|
try {
|
|
6702
6586
|
buffer = await this.fileService.convertOggToWav(buffer);
|
|
6703
6587
|
} catch (err) {
|
|
6704
|
-
|
|
6588
|
+
log20.warn({ err }, "OGG\u2192WAV conversion failed, saving original OGG");
|
|
6705
6589
|
fileName = "voice.ogg";
|
|
6706
6590
|
mimeType = "audio/ogg";
|
|
6707
6591
|
}
|
|
6708
6592
|
}
|
|
6709
6593
|
const sessionId = this.resolveSessionId(threadId) || "unknown";
|
|
6710
6594
|
const att = await this.fileService.saveFile(sessionId, fileName, buffer, mimeType);
|
|
6711
|
-
const
|
|
6595
|
+
const rawText = caption || `[${att.type === "image" ? "Photo" : att.type === "audio" ? "Audio" : "File"}: ${att.fileName}]`;
|
|
6596
|
+
const text = rawText.startsWith("/") ? rawText.slice(1) : rawText;
|
|
6712
6597
|
if (threadId === this.assistantTopicId) {
|
|
6713
6598
|
if (this.assistantSession) {
|
|
6714
6599
|
await this.assistantSession.enqueuePrompt(text, [att]);
|
|
@@ -6723,7 +6608,7 @@ Task completed.
|
|
|
6723
6608
|
userId: String(userId),
|
|
6724
6609
|
text,
|
|
6725
6610
|
attachments: [att]
|
|
6726
|
-
}).catch((err) =>
|
|
6611
|
+
}).catch((err) => log20.error({ err }, "handleMessage error"));
|
|
6727
6612
|
}
|
|
6728
6613
|
async cleanupSkillCommands(sessionId) {
|
|
6729
6614
|
await this.skillManager.cleanup(sessionId);
|
|
@@ -6746,9 +6631,8 @@ export {
|
|
|
6746
6631
|
NotificationManager,
|
|
6747
6632
|
MessageTransformer,
|
|
6748
6633
|
OpenACPCore,
|
|
6749
|
-
ChannelAdapter,
|
|
6750
6634
|
ApiServer,
|
|
6751
6635
|
TopicManager,
|
|
6752
6636
|
TelegramAdapter
|
|
6753
6637
|
};
|
|
6754
|
-
//# sourceMappingURL=chunk-
|
|
6638
|
+
//# sourceMappingURL=chunk-V2V767XI.js.map
|