@openacp/cli 2026.330.3 → 2026.331.2
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 +19 -1
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +25134 -278
- package/dist/cli.js.map +1 -1
- package/dist/data/registry-snapshot.json +1 -1
- package/dist/index.d.ts +259 -30
- package/dist/index.js +17632 -404
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/adapter-AWSI4GML.js +0 -13
- package/dist/adapter-AWSI4GML.js.map +0 -1
- package/dist/agent-catalog-SZQQERV7.js +0 -10
- package/dist/agent-catalog-SZQQERV7.js.map +0 -1
- package/dist/agent-dependencies-ED2ZTUHG.js +0 -23
- package/dist/agent-dependencies-ED2ZTUHG.js.map +0 -1
- package/dist/agent-registry-YOGP656W.js +0 -8
- package/dist/agent-registry-YOGP656W.js.map +0 -1
- package/dist/agent-store-5UHZH2XI.js +0 -8
- package/dist/agent-store-5UHZH2XI.js.map +0 -1
- package/dist/api-client-XTLRRFPX.js +0 -13
- package/dist/api-client-XTLRRFPX.js.map +0 -1
- package/dist/api-server-5VNYFWJE.js +0 -7
- package/dist/api-server-5VNYFWJE.js.map +0 -1
- package/dist/api-server-JLBDKCU4.js +0 -10
- package/dist/api-server-JLBDKCU4.js.map +0 -1
- package/dist/autostart-CUPZMKKC.js +0 -22
- package/dist/autostart-CUPZMKKC.js.map +0 -1
- package/dist/chunk-237WYH6H.js +0 -235
- package/dist/chunk-237WYH6H.js.map +0 -1
- package/dist/chunk-2HEFALTZ.js +0 -44
- package/dist/chunk-2HEFALTZ.js.map +0 -1
- package/dist/chunk-2KT6TROD.js +0 -129
- package/dist/chunk-2KT6TROD.js.map +0 -1
- package/dist/chunk-2R5XM3ES.js +0 -154
- package/dist/chunk-2R5XM3ES.js.map +0 -1
- package/dist/chunk-3EWTPOF7.js +0 -51
- package/dist/chunk-3EWTPOF7.js.map +0 -1
- package/dist/chunk-3NAFXVQM.js +0 -67
- package/dist/chunk-3NAFXVQM.js.map +0 -1
- package/dist/chunk-4WXALZA3.js +0 -45
- package/dist/chunk-4WXALZA3.js.map +0 -1
- package/dist/chunk-566W6INH.js +0 -83
- package/dist/chunk-566W6INH.js.map +0 -1
- package/dist/chunk-5HKQCYOI.js +0 -145
- package/dist/chunk-5HKQCYOI.js.map +0 -1
- package/dist/chunk-5OCGO27U.js +0 -125
- package/dist/chunk-5OCGO27U.js.map +0 -1
- package/dist/chunk-5WGVYX3C.js +0 -55
- package/dist/chunk-5WGVYX3C.js.map +0 -1
- package/dist/chunk-7ZCQF6QM.js +0 -27
- package/dist/chunk-7ZCQF6QM.js.map +0 -1
- package/dist/chunk-AFKX424Q.js +0 -92
- package/dist/chunk-AFKX424Q.js.map +0 -1
- package/dist/chunk-APS6UEFU.js +0 -259
- package/dist/chunk-APS6UEFU.js.map +0 -1
- package/dist/chunk-BTJHGSLM.js +0 -1116
- package/dist/chunk-BTJHGSLM.js.map +0 -1
- package/dist/chunk-CDAUYTVP.js +0 -41
- package/dist/chunk-CDAUYTVP.js.map +0 -1
- package/dist/chunk-FCTC7KDT.js +0 -101
- package/dist/chunk-FCTC7KDT.js.map +0 -1
- package/dist/chunk-FNRSWA2K.js +0 -1
- package/dist/chunk-FNRSWA2K.js.map +0 -1
- package/dist/chunk-GEOXPGCO.js +0 -650
- package/dist/chunk-GEOXPGCO.js.map +0 -1
- package/dist/chunk-IZ5UEZF7.js +0 -138
- package/dist/chunk-IZ5UEZF7.js.map +0 -1
- package/dist/chunk-KDU3ZEWT.js +0 -97
- package/dist/chunk-KDU3ZEWT.js.map +0 -1
- package/dist/chunk-LGFWH3AE.js +0 -26
- package/dist/chunk-LGFWH3AE.js.map +0 -1
- package/dist/chunk-MITTQMGZ.js +0 -543
- package/dist/chunk-MITTQMGZ.js.map +0 -1
- package/dist/chunk-MLF4W5R6.js +0 -101
- package/dist/chunk-MLF4W5R6.js.map +0 -1
- package/dist/chunk-MPGEHTGE.js +0 -679
- package/dist/chunk-MPGEHTGE.js.map +0 -1
- package/dist/chunk-OYSAN7UX.js +0 -15
- package/dist/chunk-OYSAN7UX.js.map +0 -1
- package/dist/chunk-PA6MNBG4.js +0 -190
- package/dist/chunk-PA6MNBG4.js.map +0 -1
- package/dist/chunk-QWVHCTCA.js +0 -172
- package/dist/chunk-QWVHCTCA.js.map +0 -1
- package/dist/chunk-R6KZYF7D.js +0 -231
- package/dist/chunk-R6KZYF7D.js.map +0 -1
- package/dist/chunk-S64CB6J3.js +0 -98
- package/dist/chunk-S64CB6J3.js.map +0 -1
- package/dist/chunk-TMVTSWVH.js +0 -228
- package/dist/chunk-TMVTSWVH.js.map +0 -1
- package/dist/chunk-UCIZM5SW.js +0 -3917
- package/dist/chunk-UCIZM5SW.js.map +0 -1
- package/dist/chunk-UWH7KIAA.js +0 -701
- package/dist/chunk-UWH7KIAA.js.map +0 -1
- package/dist/chunk-V2YZWYXT.js +0 -484
- package/dist/chunk-V2YZWYXT.js.map +0 -1
- package/dist/chunk-W26AUH5B.js +0 -61
- package/dist/chunk-W26AUH5B.js.map +0 -1
- package/dist/chunk-W4LK6WJP.js +0 -446
- package/dist/chunk-W4LK6WJP.js.map +0 -1
- package/dist/chunk-WQCJTU2C.js +0 -84
- package/dist/chunk-WQCJTU2C.js.map +0 -1
- package/dist/chunk-XBZIHNKV.js +0 -6410
- package/dist/chunk-XBZIHNKV.js.map +0 -1
- package/dist/chunk-ZSLHHQPQ.js +0 -282
- package/dist/chunk-ZSLHHQPQ.js.map +0 -1
- package/dist/config-KN6NKKPF.js +0 -20
- package/dist/config-KN6NKKPF.js.map +0 -1
- package/dist/config-editor-76RVZS4B.js +0 -10
- package/dist/config-editor-76RVZS4B.js.map +0 -1
- package/dist/config-registry-ZXAIJNYB.js +0 -17
- package/dist/config-registry-ZXAIJNYB.js.map +0 -1
- package/dist/context-NXXW62NJ.js +0 -9
- package/dist/context-NXXW62NJ.js.map +0 -1
- package/dist/core-plugins-BPZY7SEB.js +0 -22
- package/dist/core-plugins-BPZY7SEB.js.map +0 -1
- package/dist/daemon-XFEMMJSZ.js +0 -29
- package/dist/daemon-XFEMMJSZ.js.map +0 -1
- package/dist/dev-loader-7P3HZCIA.js +0 -37
- package/dist/dev-loader-7P3HZCIA.js.map +0 -1
- package/dist/doctor-AV6AUO22.js +0 -9
- package/dist/doctor-AV6AUO22.js.map +0 -1
- package/dist/file-service-HHB3JQIO.js +0 -8
- package/dist/file-service-HHB3JQIO.js.map +0 -1
- package/dist/install-cloudflared-JRJ4BSOM.js +0 -32
- package/dist/install-cloudflared-JRJ4BSOM.js.map +0 -1
- package/dist/install-context-EHYV5WRY.js +0 -77
- package/dist/install-context-EHYV5WRY.js.map +0 -1
- package/dist/install-jq-ISTGT263.js +0 -31
- package/dist/install-jq-ISTGT263.js.map +0 -1
- package/dist/integrate-JIEZYDOR.js +0 -371
- package/dist/integrate-JIEZYDOR.js.map +0 -1
- package/dist/log-YZ243M5G.js +0 -25
- package/dist/log-YZ243M5G.js.map +0 -1
- package/dist/main-VEJCG5PY.js +0 -654
- package/dist/main-VEJCG5PY.js.map +0 -1
- package/dist/menu-ALFN37IR.js +0 -15
- package/dist/menu-ALFN37IR.js.map +0 -1
- package/dist/notifications-MO23S7S3.js +0 -8
- package/dist/notifications-MO23S7S3.js.map +0 -1
- package/dist/plugin-create-EHL76ZZG.js +0 -966
- package/dist/plugin-create-EHL76ZZG.js.map +0 -1
- package/dist/plugin-installer-VSTYZSXC.js +0 -9
- package/dist/plugin-installer-VSTYZSXC.js.map +0 -1
- package/dist/plugin-registry-6J3YSFHF.js +0 -7
- package/dist/plugin-registry-6J3YSFHF.js.map +0 -1
- package/dist/plugin-search-MGKAL5JM.js +0 -39
- package/dist/plugin-search-MGKAL5JM.js.map +0 -1
- package/dist/post-upgrade-Y26S2ZQ7.js +0 -79
- package/dist/post-upgrade-Y26S2ZQ7.js.map +0 -1
- package/dist/read-text-file-DJBTITIB.js +0 -7
- package/dist/read-text-file-DJBTITIB.js.map +0 -1
- package/dist/registry-client-GTBWLXYU.js +0 -7
- package/dist/registry-client-GTBWLXYU.js.map +0 -1
- package/dist/security-2BA265LN.js +0 -8
- package/dist/security-2BA265LN.js.map +0 -1
- package/dist/settings-manager-B4UN2LAC.js +0 -7
- package/dist/settings-manager-B4UN2LAC.js.map +0 -1
- package/dist/setup-DISPNDEK.js +0 -802
- package/dist/setup-DISPNDEK.js.map +0 -1
- package/dist/speech-SG62JYIF.js +0 -9
- package/dist/speech-SG62JYIF.js.map +0 -1
- package/dist/suggest-RST5VOHB.js +0 -36
- package/dist/suggest-RST5VOHB.js.map +0 -1
- package/dist/telegram-L3YM6SQJ.js +0 -7
- package/dist/telegram-L3YM6SQJ.js.map +0 -1
- package/dist/tunnel-HWJ27WDH.js +0 -7
- package/dist/tunnel-HWJ27WDH.js.map +0 -1
- package/dist/tunnel-service-ZMO4THKE.js +0 -1261
- package/dist/tunnel-service-ZMO4THKE.js.map +0 -1
- package/dist/validators-GITLOFXC.js +0 -11
- package/dist/validators-GITLOFXC.js.map +0 -1
- package/dist/version-AXXV6IV2.js +0 -15
- package/dist/version-AXXV6IV2.js.map +0 -1
package/dist/chunk-BTJHGSLM.js
DELETED
|
@@ -1,1116 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getAgentCapabilities
|
|
3
|
-
} from "./chunk-ZSLHHQPQ.js";
|
|
4
|
-
import {
|
|
5
|
-
createChildLogger
|
|
6
|
-
} from "./chunk-R6KZYF7D.js";
|
|
7
|
-
|
|
8
|
-
// src/plugins/api-server/api-server.ts
|
|
9
|
-
import * as http from "http";
|
|
10
|
-
import * as fs2 from "fs";
|
|
11
|
-
import * as path2 from "path";
|
|
12
|
-
import * as os from "os";
|
|
13
|
-
import * as crypto from "crypto";
|
|
14
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
15
|
-
|
|
16
|
-
// src/plugins/api-server/sse-manager.ts
|
|
17
|
-
var SSEManager = class {
|
|
18
|
-
constructor(eventBus, getSessionStats, startedAt) {
|
|
19
|
-
this.eventBus = eventBus;
|
|
20
|
-
this.getSessionStats = getSessionStats;
|
|
21
|
-
this.startedAt = startedAt;
|
|
22
|
-
}
|
|
23
|
-
sseConnections = /* @__PURE__ */ new Set();
|
|
24
|
-
sseCleanupHandlers = /* @__PURE__ */ new Map();
|
|
25
|
-
healthInterval;
|
|
26
|
-
boundHandlers = [];
|
|
27
|
-
setup() {
|
|
28
|
-
if (!this.eventBus) return;
|
|
29
|
-
const events = [
|
|
30
|
-
"session:created",
|
|
31
|
-
"session:updated",
|
|
32
|
-
"session:deleted",
|
|
33
|
-
"agent:event",
|
|
34
|
-
"permission:request"
|
|
35
|
-
];
|
|
36
|
-
for (const eventName of events) {
|
|
37
|
-
const handler = (data) => {
|
|
38
|
-
this.broadcast(eventName, data);
|
|
39
|
-
};
|
|
40
|
-
this.eventBus.on(eventName, handler);
|
|
41
|
-
this.boundHandlers.push({ event: eventName, handler });
|
|
42
|
-
}
|
|
43
|
-
this.healthInterval = setInterval(() => {
|
|
44
|
-
const mem = process.memoryUsage();
|
|
45
|
-
const stats = this.getSessionStats();
|
|
46
|
-
this.broadcast("health", {
|
|
47
|
-
uptime: Date.now() - this.startedAt,
|
|
48
|
-
memory: {
|
|
49
|
-
rss: mem.rss,
|
|
50
|
-
heapUsed: mem.heapUsed,
|
|
51
|
-
heapTotal: mem.heapTotal
|
|
52
|
-
},
|
|
53
|
-
sessions: stats
|
|
54
|
-
});
|
|
55
|
-
}, 3e4);
|
|
56
|
-
}
|
|
57
|
-
handleRequest(req, res) {
|
|
58
|
-
const parsedUrl = new URL(req.url || "", "http://localhost");
|
|
59
|
-
const sessionFilter = parsedUrl.searchParams.get("sessionId");
|
|
60
|
-
res.writeHead(200, {
|
|
61
|
-
"Content-Type": "text/event-stream",
|
|
62
|
-
"Cache-Control": "no-cache",
|
|
63
|
-
Connection: "keep-alive"
|
|
64
|
-
});
|
|
65
|
-
res.flushHeaders();
|
|
66
|
-
res.sessionFilter = sessionFilter ?? void 0;
|
|
67
|
-
this.sseConnections.add(res);
|
|
68
|
-
const cleanup = () => {
|
|
69
|
-
this.sseConnections.delete(res);
|
|
70
|
-
this.sseCleanupHandlers.delete(res);
|
|
71
|
-
};
|
|
72
|
-
this.sseCleanupHandlers.set(res, cleanup);
|
|
73
|
-
req.on("close", cleanup);
|
|
74
|
-
}
|
|
75
|
-
broadcast(event, data) {
|
|
76
|
-
const payload = `event: ${event}
|
|
77
|
-
data: ${JSON.stringify(data)}
|
|
78
|
-
|
|
79
|
-
`;
|
|
80
|
-
const sessionEvents = [
|
|
81
|
-
"agent:event",
|
|
82
|
-
"permission:request",
|
|
83
|
-
"session:updated"
|
|
84
|
-
];
|
|
85
|
-
for (const res of this.sseConnections) {
|
|
86
|
-
const filter = res.sessionFilter;
|
|
87
|
-
if (filter && sessionEvents.includes(event)) {
|
|
88
|
-
const eventData = data;
|
|
89
|
-
if (eventData.sessionId !== filter) continue;
|
|
90
|
-
}
|
|
91
|
-
try {
|
|
92
|
-
if (res.writable) res.write(payload);
|
|
93
|
-
} catch {
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
stop() {
|
|
98
|
-
if (this.healthInterval) clearInterval(this.healthInterval);
|
|
99
|
-
if (this.eventBus) {
|
|
100
|
-
for (const { event, handler } of this.boundHandlers) {
|
|
101
|
-
this.eventBus.off(event, handler);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
this.boundHandlers = [];
|
|
105
|
-
const entries = [...this.sseCleanupHandlers];
|
|
106
|
-
for (const [res, cleanup] of entries) {
|
|
107
|
-
res.end();
|
|
108
|
-
cleanup();
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
// src/plugins/api-server/static-server.ts
|
|
114
|
-
import * as fs from "fs";
|
|
115
|
-
import * as path from "path";
|
|
116
|
-
import { fileURLToPath } from "url";
|
|
117
|
-
var MIME_TYPES = {
|
|
118
|
-
".html": "text/html; charset=utf-8",
|
|
119
|
-
".js": "application/javascript; charset=utf-8",
|
|
120
|
-
".css": "text/css; charset=utf-8",
|
|
121
|
-
".json": "application/json; charset=utf-8",
|
|
122
|
-
".png": "image/png",
|
|
123
|
-
".jpg": "image/jpeg",
|
|
124
|
-
".svg": "image/svg+xml",
|
|
125
|
-
".ico": "image/x-icon",
|
|
126
|
-
".woff": "font/woff",
|
|
127
|
-
".woff2": "font/woff2"
|
|
128
|
-
};
|
|
129
|
-
var StaticServer = class {
|
|
130
|
-
uiDir;
|
|
131
|
-
constructor(uiDir) {
|
|
132
|
-
this.uiDir = uiDir;
|
|
133
|
-
if (!this.uiDir) {
|
|
134
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
135
|
-
const candidate = path.resolve(path.dirname(__filename), "../../ui/dist");
|
|
136
|
-
if (fs.existsSync(path.join(candidate, "index.html"))) {
|
|
137
|
-
this.uiDir = candidate;
|
|
138
|
-
}
|
|
139
|
-
if (!this.uiDir) {
|
|
140
|
-
const publishCandidate = path.resolve(
|
|
141
|
-
path.dirname(__filename),
|
|
142
|
-
"../ui"
|
|
143
|
-
);
|
|
144
|
-
if (fs.existsSync(path.join(publishCandidate, "index.html"))) {
|
|
145
|
-
this.uiDir = publishCandidate;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
isAvailable() {
|
|
151
|
-
return this.uiDir !== void 0;
|
|
152
|
-
}
|
|
153
|
-
serve(req, res) {
|
|
154
|
-
if (!this.uiDir) return false;
|
|
155
|
-
const urlPath = (req.url || "/").split("?")[0];
|
|
156
|
-
const safePath = path.normalize(urlPath);
|
|
157
|
-
const filePath = path.join(this.uiDir, safePath);
|
|
158
|
-
if (!filePath.startsWith(this.uiDir + path.sep) && filePath !== this.uiDir)
|
|
159
|
-
return false;
|
|
160
|
-
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
|
|
161
|
-
const ext = path.extname(filePath);
|
|
162
|
-
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
163
|
-
const isHashed = /\.[a-zA-Z0-9]{8,}\.(js|css)$/.test(filePath);
|
|
164
|
-
const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "no-cache";
|
|
165
|
-
res.writeHead(200, {
|
|
166
|
-
"Content-Type": contentType,
|
|
167
|
-
"Cache-Control": cacheControl
|
|
168
|
-
});
|
|
169
|
-
fs.createReadStream(filePath).pipe(res);
|
|
170
|
-
return true;
|
|
171
|
-
}
|
|
172
|
-
const indexPath = path.join(this.uiDir, "index.html");
|
|
173
|
-
if (fs.existsSync(indexPath)) {
|
|
174
|
-
res.writeHead(200, {
|
|
175
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
176
|
-
"Cache-Control": "no-cache"
|
|
177
|
-
});
|
|
178
|
-
fs.createReadStream(indexPath).pipe(res);
|
|
179
|
-
return true;
|
|
180
|
-
}
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
// src/plugins/api-server/router.ts
|
|
186
|
-
var Router = class {
|
|
187
|
-
routes = [];
|
|
188
|
-
get(path3, handler) {
|
|
189
|
-
this.add("GET", path3, handler);
|
|
190
|
-
}
|
|
191
|
-
post(path3, handler) {
|
|
192
|
-
this.add("POST", path3, handler);
|
|
193
|
-
}
|
|
194
|
-
put(path3, handler) {
|
|
195
|
-
this.add("PUT", path3, handler);
|
|
196
|
-
}
|
|
197
|
-
patch(path3, handler) {
|
|
198
|
-
this.add("PATCH", path3, handler);
|
|
199
|
-
}
|
|
200
|
-
delete(path3, handler) {
|
|
201
|
-
this.add("DELETE", path3, handler);
|
|
202
|
-
}
|
|
203
|
-
match(method, url) {
|
|
204
|
-
const pathname = url.split("?")[0];
|
|
205
|
-
for (const route of this.routes) {
|
|
206
|
-
if (route.method !== method) continue;
|
|
207
|
-
const m = pathname.match(route.pattern);
|
|
208
|
-
if (!m) continue;
|
|
209
|
-
const params = {};
|
|
210
|
-
for (let i = 0; i < route.keys.length; i++) {
|
|
211
|
-
params[route.keys[i]] = m[i + 1];
|
|
212
|
-
}
|
|
213
|
-
return { handler: route.handler, params };
|
|
214
|
-
}
|
|
215
|
-
return null;
|
|
216
|
-
}
|
|
217
|
-
add(method, path3, handler) {
|
|
218
|
-
const keys = [];
|
|
219
|
-
const pattern = path3.replace(/:(\w+)/g, (_, key) => {
|
|
220
|
-
keys.push(key);
|
|
221
|
-
return "([^/]+)";
|
|
222
|
-
});
|
|
223
|
-
this.routes.push({
|
|
224
|
-
method,
|
|
225
|
-
pattern: new RegExp(`^${pattern}$`),
|
|
226
|
-
keys,
|
|
227
|
-
handler
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
// src/plugins/api-server/routes/health.ts
|
|
233
|
-
function registerHealthRoutes(router, deps) {
|
|
234
|
-
router.get("/api/health", async (_req, res) => {
|
|
235
|
-
const activeSessions = deps.core.sessionManager.listSessions();
|
|
236
|
-
const allRecords = deps.core.sessionManager.listRecords();
|
|
237
|
-
const mem = process.memoryUsage();
|
|
238
|
-
const tunnel = deps.core.tunnelService;
|
|
239
|
-
deps.sendJson(res, 200, {
|
|
240
|
-
status: "ok",
|
|
241
|
-
uptime: Date.now() - deps.startedAt,
|
|
242
|
-
version: deps.getVersion(),
|
|
243
|
-
memory: {
|
|
244
|
-
rss: mem.rss,
|
|
245
|
-
heapUsed: mem.heapUsed,
|
|
246
|
-
heapTotal: mem.heapTotal
|
|
247
|
-
},
|
|
248
|
-
sessions: {
|
|
249
|
-
active: activeSessions.filter(
|
|
250
|
-
(s) => s.status === "active" || s.status === "initializing"
|
|
251
|
-
).length,
|
|
252
|
-
total: allRecords.length
|
|
253
|
-
},
|
|
254
|
-
adapters: Array.from(deps.core.adapters.keys()),
|
|
255
|
-
tunnel: tunnel ? { enabled: true, url: tunnel.getPublicUrl() } : { enabled: false }
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
router.get("/api/version", async (_req, res) => {
|
|
259
|
-
deps.sendJson(res, 200, { version: deps.getVersion() });
|
|
260
|
-
});
|
|
261
|
-
router.post("/api/restart", async (_req, res) => {
|
|
262
|
-
if (!deps.core.requestRestart) {
|
|
263
|
-
deps.sendJson(res, 501, { error: "Restart not available" });
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
deps.sendJson(res, 200, { ok: true, message: "Restarting..." });
|
|
267
|
-
setImmediate(() => deps.core.requestRestart());
|
|
268
|
-
});
|
|
269
|
-
router.get("/api/adapters", async (_req, res) => {
|
|
270
|
-
const adapters = Array.from(deps.core.adapters.entries()).map(([name]) => ({
|
|
271
|
-
name,
|
|
272
|
-
type: "built-in"
|
|
273
|
-
}));
|
|
274
|
-
deps.sendJson(res, 200, { adapters });
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// src/plugins/api-server/routes/sessions.ts
|
|
279
|
-
var log = createChildLogger({ module: "api-server" });
|
|
280
|
-
function registerSessionRoutes(router, deps) {
|
|
281
|
-
router.post("/api/sessions/adopt", async (req, res) => {
|
|
282
|
-
const body = await deps.readBody(req);
|
|
283
|
-
if (body === null) {
|
|
284
|
-
return deps.sendJson(res, 413, { error: "Request body too large" });
|
|
285
|
-
}
|
|
286
|
-
if (!body) {
|
|
287
|
-
return deps.sendJson(res, 400, {
|
|
288
|
-
error: "bad_request",
|
|
289
|
-
message: "Empty request body"
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
let parsed;
|
|
293
|
-
try {
|
|
294
|
-
parsed = JSON.parse(body);
|
|
295
|
-
} catch {
|
|
296
|
-
return deps.sendJson(res, 400, {
|
|
297
|
-
error: "bad_request",
|
|
298
|
-
message: "Invalid JSON"
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
const { agent, agentSessionId, cwd, channel } = parsed;
|
|
302
|
-
if (!agent || !agentSessionId) {
|
|
303
|
-
return deps.sendJson(res, 400, {
|
|
304
|
-
error: "bad_request",
|
|
305
|
-
message: "Missing required fields: agent, agentSessionId"
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
const result = await deps.core.adoptSession(
|
|
309
|
-
agent,
|
|
310
|
-
agentSessionId,
|
|
311
|
-
cwd ?? process.cwd(),
|
|
312
|
-
channel
|
|
313
|
-
);
|
|
314
|
-
if (result.ok) {
|
|
315
|
-
return deps.sendJson(res, 200, result);
|
|
316
|
-
} else {
|
|
317
|
-
const status = result.error === "session_limit" ? 429 : result.error === "agent_not_supported" ? 400 : 500;
|
|
318
|
-
return deps.sendJson(res, status, result);
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
router.post("/api/sessions", async (req, res) => {
|
|
322
|
-
const body = await deps.readBody(req);
|
|
323
|
-
let agent;
|
|
324
|
-
let workspace;
|
|
325
|
-
let channel;
|
|
326
|
-
if (body) {
|
|
327
|
-
try {
|
|
328
|
-
const parsed = JSON.parse(body);
|
|
329
|
-
agent = parsed.agent;
|
|
330
|
-
workspace = parsed.workspace;
|
|
331
|
-
channel = parsed.channel;
|
|
332
|
-
} catch {
|
|
333
|
-
deps.sendJson(res, 400, { error: "Invalid JSON body" });
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
const config = deps.core.configManager.get();
|
|
338
|
-
const activeSessions = deps.core.sessionManager.listSessions().filter((s) => s.status === "active" || s.status === "initializing");
|
|
339
|
-
if (activeSessions.length >= config.security.maxConcurrentSessions) {
|
|
340
|
-
deps.sendJson(res, 429, {
|
|
341
|
-
error: `Max concurrent sessions (${config.security.maxConcurrentSessions}) reached. Cancel a session first.`
|
|
342
|
-
});
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
let adapterId = null;
|
|
346
|
-
let adapter = null;
|
|
347
|
-
if (channel) {
|
|
348
|
-
if (!deps.core.adapters.has(channel)) {
|
|
349
|
-
const available = Array.from(deps.core.adapters.keys()).join(", ") || "none";
|
|
350
|
-
deps.sendJson(res, 400, {
|
|
351
|
-
error: `Adapter '${channel}' is not connected. Available: ${available}`
|
|
352
|
-
});
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
adapterId = channel;
|
|
356
|
-
adapter = deps.core.adapters.get(channel) ?? null;
|
|
357
|
-
} else {
|
|
358
|
-
const firstEntry = deps.core.adapters.entries().next().value;
|
|
359
|
-
if (firstEntry) {
|
|
360
|
-
[adapterId, adapter] = firstEntry;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
const channelId = adapterId ?? "api";
|
|
364
|
-
const resolvedAgent = agent || config.defaultAgent;
|
|
365
|
-
const agentDef = deps.core.agentCatalog.resolve(resolvedAgent);
|
|
366
|
-
const resolvedWorkspace = deps.core.configManager.resolveWorkspace(
|
|
367
|
-
workspace || agentDef?.workingDirectory
|
|
368
|
-
);
|
|
369
|
-
const session = await deps.core.createSession({
|
|
370
|
-
channelId,
|
|
371
|
-
agentName: resolvedAgent,
|
|
372
|
-
workingDirectory: resolvedWorkspace,
|
|
373
|
-
createThread: !!adapter,
|
|
374
|
-
initialName: `\u{1F504} ${resolvedAgent} \u2014 New Session`
|
|
375
|
-
});
|
|
376
|
-
if (!adapter) {
|
|
377
|
-
session.agentInstance.onPermissionRequest = async (request) => {
|
|
378
|
-
const allowOption = request.options.find((o) => o.isAllow);
|
|
379
|
-
log.debug(
|
|
380
|
-
{
|
|
381
|
-
sessionId: session.id,
|
|
382
|
-
permissionId: request.id,
|
|
383
|
-
option: allowOption?.id
|
|
384
|
-
},
|
|
385
|
-
"Auto-approving permission for API session"
|
|
386
|
-
);
|
|
387
|
-
return allowOption?.id ?? request.options[0]?.id ?? "";
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
session.warmup().catch(
|
|
391
|
-
(err) => log.warn({ err, sessionId: session.id }, "API session warmup failed")
|
|
392
|
-
);
|
|
393
|
-
deps.sendJson(res, 200, {
|
|
394
|
-
sessionId: session.id,
|
|
395
|
-
agent: session.agentName,
|
|
396
|
-
status: session.status,
|
|
397
|
-
workspace: session.workingDirectory
|
|
398
|
-
});
|
|
399
|
-
});
|
|
400
|
-
router.post("/api/sessions/:sessionId/prompt", async (req, res, params) => {
|
|
401
|
-
const sessionId = decodeURIComponent(params.sessionId);
|
|
402
|
-
const session = deps.core.sessionManager.getSession(sessionId);
|
|
403
|
-
if (!session) {
|
|
404
|
-
deps.sendJson(res, 404, { error: `Session "${sessionId}" not found` });
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
if (session.status === "cancelled" || session.status === "finished" || session.status === "error") {
|
|
408
|
-
deps.sendJson(res, 400, { error: `Session is ${session.status}` });
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
const body = await deps.readBody(req);
|
|
412
|
-
let prompt;
|
|
413
|
-
if (body) {
|
|
414
|
-
try {
|
|
415
|
-
const parsed = JSON.parse(body);
|
|
416
|
-
prompt = parsed.prompt;
|
|
417
|
-
} catch {
|
|
418
|
-
deps.sendJson(res, 400, { error: "Invalid JSON body" });
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
if (!prompt) {
|
|
423
|
-
deps.sendJson(res, 400, { error: "Missing prompt" });
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
session.enqueuePrompt(prompt).catch(() => {
|
|
427
|
-
});
|
|
428
|
-
deps.sendJson(res, 200, {
|
|
429
|
-
ok: true,
|
|
430
|
-
sessionId,
|
|
431
|
-
queueDepth: session.queueDepth
|
|
432
|
-
});
|
|
433
|
-
});
|
|
434
|
-
router.post(
|
|
435
|
-
"/api/sessions/:sessionId/permission",
|
|
436
|
-
async (req, res, params) => {
|
|
437
|
-
const sessionId = decodeURIComponent(params.sessionId);
|
|
438
|
-
const session = deps.core.sessionManager.getSession(sessionId);
|
|
439
|
-
if (!session) {
|
|
440
|
-
deps.sendJson(res, 404, { error: `Session "${sessionId}" not found` });
|
|
441
|
-
return;
|
|
442
|
-
}
|
|
443
|
-
const body = await deps.readBody(req);
|
|
444
|
-
let permissionId;
|
|
445
|
-
let optionId;
|
|
446
|
-
if (body) {
|
|
447
|
-
try {
|
|
448
|
-
const parsed = JSON.parse(body);
|
|
449
|
-
permissionId = parsed.permissionId;
|
|
450
|
-
optionId = parsed.optionId;
|
|
451
|
-
} catch {
|
|
452
|
-
deps.sendJson(res, 400, { error: "Invalid JSON body" });
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
if (!permissionId || !optionId) {
|
|
457
|
-
deps.sendJson(res, 400, {
|
|
458
|
-
error: "Missing permissionId or optionId"
|
|
459
|
-
});
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
if (!session.permissionGate.isPending || session.permissionGate.requestId !== permissionId) {
|
|
463
|
-
deps.sendJson(res, 400, {
|
|
464
|
-
error: "No matching pending permission request"
|
|
465
|
-
});
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
session.permissionGate.resolve(optionId);
|
|
469
|
-
deps.sendJson(res, 200, { ok: true });
|
|
470
|
-
}
|
|
471
|
-
);
|
|
472
|
-
router.patch(
|
|
473
|
-
"/api/sessions/:sessionId/dangerous",
|
|
474
|
-
async (req, res, params) => {
|
|
475
|
-
const sessionId = decodeURIComponent(params.sessionId);
|
|
476
|
-
const session = deps.core.sessionManager.getSession(sessionId);
|
|
477
|
-
if (!session) {
|
|
478
|
-
deps.sendJson(res, 404, { error: `Session "${sessionId}" not found` });
|
|
479
|
-
return;
|
|
480
|
-
}
|
|
481
|
-
const body = await deps.readBody(req);
|
|
482
|
-
let enabled;
|
|
483
|
-
if (body) {
|
|
484
|
-
try {
|
|
485
|
-
const parsed = JSON.parse(body);
|
|
486
|
-
enabled = parsed.enabled;
|
|
487
|
-
} catch {
|
|
488
|
-
deps.sendJson(res, 400, { error: "Invalid JSON body" });
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
if (typeof enabled !== "boolean") {
|
|
493
|
-
deps.sendJson(res, 400, { error: "Missing enabled boolean" });
|
|
494
|
-
return;
|
|
495
|
-
}
|
|
496
|
-
session.dangerousMode = enabled;
|
|
497
|
-
await deps.core.sessionManager.patchRecord(sessionId, {
|
|
498
|
-
dangerousMode: enabled
|
|
499
|
-
});
|
|
500
|
-
deps.sendJson(res, 200, { ok: true, dangerousMode: enabled });
|
|
501
|
-
}
|
|
502
|
-
);
|
|
503
|
-
router.get("/api/sessions/:sessionId", async (_req, res, params) => {
|
|
504
|
-
const sessionId = decodeURIComponent(params.sessionId);
|
|
505
|
-
const session = deps.core.sessionManager.getSession(sessionId);
|
|
506
|
-
if (!session) {
|
|
507
|
-
deps.sendJson(res, 404, { error: `Session "${sessionId}" not found` });
|
|
508
|
-
return;
|
|
509
|
-
}
|
|
510
|
-
deps.sendJson(res, 200, {
|
|
511
|
-
session: {
|
|
512
|
-
id: session.id,
|
|
513
|
-
agent: session.agentName,
|
|
514
|
-
status: session.status,
|
|
515
|
-
name: session.name ?? null,
|
|
516
|
-
workspace: session.workingDirectory,
|
|
517
|
-
createdAt: session.createdAt.toISOString(),
|
|
518
|
-
dangerousMode: session.dangerousMode,
|
|
519
|
-
queueDepth: session.queueDepth,
|
|
520
|
-
promptRunning: session.promptRunning,
|
|
521
|
-
threadId: session.threadId,
|
|
522
|
-
channelId: session.channelId,
|
|
523
|
-
agentSessionId: session.agentSessionId
|
|
524
|
-
}
|
|
525
|
-
});
|
|
526
|
-
});
|
|
527
|
-
router.post("/api/sessions/:sessionId/archive", async (_req, res, params) => {
|
|
528
|
-
const sessionId = decodeURIComponent(params.sessionId);
|
|
529
|
-
const result = await deps.core.archiveSession(sessionId);
|
|
530
|
-
if (result.ok) {
|
|
531
|
-
deps.sendJson(res, 200, result);
|
|
532
|
-
} else {
|
|
533
|
-
deps.sendJson(res, 400, result);
|
|
534
|
-
}
|
|
535
|
-
});
|
|
536
|
-
router.delete("/api/sessions/:sessionId", async (_req, res, params) => {
|
|
537
|
-
const sessionId = decodeURIComponent(params.sessionId);
|
|
538
|
-
const session = deps.core.sessionManager.getSession(sessionId);
|
|
539
|
-
if (!session) {
|
|
540
|
-
deps.sendJson(res, 404, { error: `Session "${sessionId}" not found` });
|
|
541
|
-
return;
|
|
542
|
-
}
|
|
543
|
-
await deps.core.sessionManager.cancelSession(sessionId);
|
|
544
|
-
deps.sendJson(res, 200, { ok: true });
|
|
545
|
-
});
|
|
546
|
-
router.get("/api/sessions", async (_req, res) => {
|
|
547
|
-
const sessions = deps.core.sessionManager.listSessions();
|
|
548
|
-
deps.sendJson(res, 200, {
|
|
549
|
-
sessions: sessions.map((s) => ({
|
|
550
|
-
id: s.id,
|
|
551
|
-
agent: s.agentName,
|
|
552
|
-
status: s.status,
|
|
553
|
-
name: s.name ?? null,
|
|
554
|
-
workspace: s.workingDirectory,
|
|
555
|
-
createdAt: s.createdAt.toISOString(),
|
|
556
|
-
dangerousMode: s.dangerousMode,
|
|
557
|
-
queueDepth: s.queueDepth,
|
|
558
|
-
promptRunning: s.promptRunning,
|
|
559
|
-
lastActiveAt: deps.core.sessionManager.getSessionRecord(s.id)?.lastActiveAt ?? null
|
|
560
|
-
}))
|
|
561
|
-
});
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
// src/plugins/api-server/routes/config.ts
|
|
566
|
-
var SENSITIVE_KEYS = [
|
|
567
|
-
"botToken",
|
|
568
|
-
"token",
|
|
569
|
-
"apiKey",
|
|
570
|
-
"secret",
|
|
571
|
-
"password",
|
|
572
|
-
"webhookSecret"
|
|
573
|
-
];
|
|
574
|
-
function redactConfig(config) {
|
|
575
|
-
const redacted = structuredClone(config);
|
|
576
|
-
redactDeep(redacted);
|
|
577
|
-
return redacted;
|
|
578
|
-
}
|
|
579
|
-
function redactDeep(obj) {
|
|
580
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
581
|
-
if (SENSITIVE_KEYS.includes(key) && typeof value === "string") {
|
|
582
|
-
obj[key] = "***";
|
|
583
|
-
} else if (Array.isArray(value)) {
|
|
584
|
-
for (const item of value) {
|
|
585
|
-
if (item && typeof item === "object")
|
|
586
|
-
redactDeep(item);
|
|
587
|
-
}
|
|
588
|
-
} else if (value && typeof value === "object") {
|
|
589
|
-
redactDeep(value);
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
function registerConfigRoutes(router, deps) {
|
|
594
|
-
router.get("/api/config/editable", async (_req, res) => {
|
|
595
|
-
const { getSafeFields, resolveOptions, getConfigValue } = await import("./config-registry-ZXAIJNYB.js");
|
|
596
|
-
const config = deps.core.configManager.get();
|
|
597
|
-
const safeFields = getSafeFields();
|
|
598
|
-
const fields = safeFields.map((def) => ({
|
|
599
|
-
path: def.path,
|
|
600
|
-
displayName: def.displayName,
|
|
601
|
-
group: def.group,
|
|
602
|
-
type: def.type,
|
|
603
|
-
options: resolveOptions(def, config),
|
|
604
|
-
value: getConfigValue(config, def.path),
|
|
605
|
-
hotReload: def.hotReload
|
|
606
|
-
}));
|
|
607
|
-
deps.sendJson(res, 200, { fields });
|
|
608
|
-
});
|
|
609
|
-
router.get("/api/config", async (_req, res) => {
|
|
610
|
-
const config = deps.core.configManager.get();
|
|
611
|
-
deps.sendJson(res, 200, { config: redactConfig(config) });
|
|
612
|
-
});
|
|
613
|
-
router.patch("/api/config", async (req, res) => {
|
|
614
|
-
const body = await deps.readBody(req);
|
|
615
|
-
let configPath;
|
|
616
|
-
let value;
|
|
617
|
-
if (body) {
|
|
618
|
-
try {
|
|
619
|
-
const parsed = JSON.parse(body);
|
|
620
|
-
configPath = parsed.path;
|
|
621
|
-
value = parsed.value;
|
|
622
|
-
} catch {
|
|
623
|
-
deps.sendJson(res, 400, { error: "Invalid JSON body" });
|
|
624
|
-
return;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
if (!configPath) {
|
|
628
|
-
deps.sendJson(res, 400, { error: "Missing path" });
|
|
629
|
-
return;
|
|
630
|
-
}
|
|
631
|
-
const BLOCKED_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
632
|
-
const parts = configPath.split(".");
|
|
633
|
-
if (parts.some((p) => BLOCKED_KEYS.has(p))) {
|
|
634
|
-
deps.sendJson(res, 400, { error: "Invalid config path" });
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
637
|
-
const { getFieldDef } = await import("./config-registry-ZXAIJNYB.js");
|
|
638
|
-
const fieldDef = getFieldDef(configPath);
|
|
639
|
-
if (!fieldDef || fieldDef.scope !== "safe") {
|
|
640
|
-
deps.sendJson(res, 403, {
|
|
641
|
-
error: "This config field cannot be modified via the API"
|
|
642
|
-
});
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
645
|
-
const currentConfig = deps.core.configManager.get();
|
|
646
|
-
const cloned = structuredClone(currentConfig);
|
|
647
|
-
let target = cloned;
|
|
648
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
649
|
-
const part = parts[i];
|
|
650
|
-
if (target[part] && typeof target[part] === "object" && !Array.isArray(target[part])) {
|
|
651
|
-
target = target[part];
|
|
652
|
-
} else if (target[part] === void 0 || target[part] === null) {
|
|
653
|
-
target[part] = {};
|
|
654
|
-
target = target[part];
|
|
655
|
-
} else {
|
|
656
|
-
deps.sendJson(res, 400, { error: "Invalid config path" });
|
|
657
|
-
return;
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
const lastKey = parts[parts.length - 1];
|
|
661
|
-
target[lastKey] = value;
|
|
662
|
-
const { ConfigSchema } = await import("./config-KN6NKKPF.js");
|
|
663
|
-
const result = ConfigSchema.safeParse(cloned);
|
|
664
|
-
if (!result.success) {
|
|
665
|
-
deps.sendJson(res, 400, {
|
|
666
|
-
error: "Validation failed",
|
|
667
|
-
details: result.error.issues.map((i) => ({
|
|
668
|
-
path: i.path.join("."),
|
|
669
|
-
message: i.message
|
|
670
|
-
}))
|
|
671
|
-
});
|
|
672
|
-
return;
|
|
673
|
-
}
|
|
674
|
-
const updates = {};
|
|
675
|
-
let updateTarget = updates;
|
|
676
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
677
|
-
updateTarget[parts[i]] = {};
|
|
678
|
-
updateTarget = updateTarget[parts[i]];
|
|
679
|
-
}
|
|
680
|
-
updateTarget[lastKey] = value;
|
|
681
|
-
await deps.core.configManager.save(updates, configPath);
|
|
682
|
-
const { isHotReloadable } = await import("./config-registry-ZXAIJNYB.js");
|
|
683
|
-
const needsRestart = !isHotReloadable(configPath);
|
|
684
|
-
deps.sendJson(res, 200, {
|
|
685
|
-
ok: true,
|
|
686
|
-
needsRestart,
|
|
687
|
-
config: redactConfig(deps.core.configManager.get())
|
|
688
|
-
});
|
|
689
|
-
});
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
// src/plugins/api-server/routes/topics.ts
|
|
693
|
-
function registerTopicRoutes(router, deps) {
|
|
694
|
-
router.get("/api/topics", async (req, res) => {
|
|
695
|
-
if (!deps.topicManager) {
|
|
696
|
-
deps.sendJson(res, 501, { error: "Topic management not available" });
|
|
697
|
-
return;
|
|
698
|
-
}
|
|
699
|
-
const url = req.url || "";
|
|
700
|
-
const params = new URL(url, "http://localhost").searchParams;
|
|
701
|
-
const statusParam = params.get("status");
|
|
702
|
-
const filter = statusParam ? { statuses: statusParam.split(",") } : void 0;
|
|
703
|
-
const topics = deps.topicManager.listTopics(filter);
|
|
704
|
-
deps.sendJson(res, 200, { topics });
|
|
705
|
-
});
|
|
706
|
-
router.post("/api/topics/cleanup", async (req, res) => {
|
|
707
|
-
if (!deps.topicManager) {
|
|
708
|
-
deps.sendJson(res, 501, { error: "Topic management not available" });
|
|
709
|
-
return;
|
|
710
|
-
}
|
|
711
|
-
const body = await deps.readBody(req);
|
|
712
|
-
let statuses;
|
|
713
|
-
if (body) {
|
|
714
|
-
try {
|
|
715
|
-
statuses = JSON.parse(body).statuses;
|
|
716
|
-
} catch {
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
const result = await deps.topicManager.cleanup(statuses);
|
|
720
|
-
deps.sendJson(res, 200, result);
|
|
721
|
-
});
|
|
722
|
-
router.delete("/api/topics/:sessionId", async (req, res, params) => {
|
|
723
|
-
if (!deps.topicManager) {
|
|
724
|
-
deps.sendJson(res, 501, { error: "Topic management not available" });
|
|
725
|
-
return;
|
|
726
|
-
}
|
|
727
|
-
const sessionId = decodeURIComponent(params.sessionId);
|
|
728
|
-
const url = req.url || "";
|
|
729
|
-
const urlParams = new URL(url, "http://localhost").searchParams;
|
|
730
|
-
const force = urlParams.get("force") === "true";
|
|
731
|
-
const result = await deps.topicManager.deleteTopic(
|
|
732
|
-
sessionId,
|
|
733
|
-
force ? { confirmed: true } : void 0
|
|
734
|
-
);
|
|
735
|
-
if (result.ok) {
|
|
736
|
-
deps.sendJson(res, 200, result);
|
|
737
|
-
} else if (result.needsConfirmation) {
|
|
738
|
-
deps.sendJson(res, 409, {
|
|
739
|
-
error: "Session is active",
|
|
740
|
-
needsConfirmation: true,
|
|
741
|
-
session: result.session
|
|
742
|
-
});
|
|
743
|
-
} else if (result.error === "Cannot delete system topic") {
|
|
744
|
-
deps.sendJson(res, 403, { error: result.error });
|
|
745
|
-
} else {
|
|
746
|
-
deps.sendJson(res, 404, { error: result.error ?? "Not found" });
|
|
747
|
-
}
|
|
748
|
-
});
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
// src/plugins/api-server/routes/tunnel.ts
|
|
752
|
-
function registerTunnelRoutes(router, deps) {
|
|
753
|
-
router.get("/api/tunnel", async (_req, res) => {
|
|
754
|
-
const tunnel = deps.core.tunnelService;
|
|
755
|
-
if (tunnel) {
|
|
756
|
-
deps.sendJson(res, 200, {
|
|
757
|
-
enabled: true,
|
|
758
|
-
url: tunnel.getPublicUrl(),
|
|
759
|
-
provider: deps.core.configManager.get().tunnel.provider
|
|
760
|
-
});
|
|
761
|
-
} else {
|
|
762
|
-
deps.sendJson(res, 200, { enabled: false });
|
|
763
|
-
}
|
|
764
|
-
});
|
|
765
|
-
router.get("/api/tunnel/list", async (_req, res) => {
|
|
766
|
-
const tunnel = deps.core.tunnelService;
|
|
767
|
-
if (!tunnel) {
|
|
768
|
-
deps.sendJson(res, 200, []);
|
|
769
|
-
return;
|
|
770
|
-
}
|
|
771
|
-
deps.sendJson(res, 200, tunnel.listTunnels());
|
|
772
|
-
});
|
|
773
|
-
router.post("/api/tunnel", async (req, res) => {
|
|
774
|
-
const tunnel = deps.core.tunnelService;
|
|
775
|
-
if (!tunnel) {
|
|
776
|
-
deps.sendJson(res, 400, { error: "Tunnel service is not enabled" });
|
|
777
|
-
return;
|
|
778
|
-
}
|
|
779
|
-
const body = await deps.readBody(req);
|
|
780
|
-
if (body === null) {
|
|
781
|
-
deps.sendJson(res, 413, { error: "Request body too large" });
|
|
782
|
-
return;
|
|
783
|
-
}
|
|
784
|
-
if (!body) {
|
|
785
|
-
deps.sendJson(res, 400, { error: "Missing request body" });
|
|
786
|
-
return;
|
|
787
|
-
}
|
|
788
|
-
try {
|
|
789
|
-
const { port, label, sessionId } = JSON.parse(body);
|
|
790
|
-
if (!port || typeof port !== "number") {
|
|
791
|
-
deps.sendJson(res, 400, {
|
|
792
|
-
error: "port is required and must be a number"
|
|
793
|
-
});
|
|
794
|
-
return;
|
|
795
|
-
}
|
|
796
|
-
const entry = await tunnel.addTunnel(port, { label, sessionId });
|
|
797
|
-
deps.sendJson(res, 200, entry);
|
|
798
|
-
} catch (err) {
|
|
799
|
-
deps.sendJson(res, 400, { error: err.message });
|
|
800
|
-
}
|
|
801
|
-
});
|
|
802
|
-
router.delete("/api/tunnel/:port", async (_req, res, params) => {
|
|
803
|
-
const tunnel = deps.core.tunnelService;
|
|
804
|
-
if (!tunnel) {
|
|
805
|
-
deps.sendJson(res, 400, { error: "Tunnel service is not enabled" });
|
|
806
|
-
return;
|
|
807
|
-
}
|
|
808
|
-
const port = parseInt(params.port, 10);
|
|
809
|
-
try {
|
|
810
|
-
await tunnel.stopTunnel(port);
|
|
811
|
-
deps.sendJson(res, 200, { ok: true });
|
|
812
|
-
} catch (err) {
|
|
813
|
-
deps.sendJson(res, 400, { error: err.message });
|
|
814
|
-
}
|
|
815
|
-
});
|
|
816
|
-
router.delete("/api/tunnel", async (_req, res) => {
|
|
817
|
-
const tunnel = deps.core.tunnelService;
|
|
818
|
-
if (!tunnel) {
|
|
819
|
-
deps.sendJson(res, 400, { error: "Tunnel service is not enabled" });
|
|
820
|
-
return;
|
|
821
|
-
}
|
|
822
|
-
const count = tunnel.listTunnels().length;
|
|
823
|
-
await tunnel.stopAllUser();
|
|
824
|
-
deps.sendJson(res, 200, { ok: true, stopped: count });
|
|
825
|
-
});
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
// src/plugins/api-server/routes/agents.ts
|
|
829
|
-
function registerAgentRoutes(router, deps) {
|
|
830
|
-
router.get("/api/agents", async (_req, res) => {
|
|
831
|
-
const agents = deps.core.agentManager.getAvailableAgents();
|
|
832
|
-
const defaultAgent = deps.core.configManager.get().defaultAgent;
|
|
833
|
-
const agentsWithCaps = agents.map((a) => ({
|
|
834
|
-
...a,
|
|
835
|
-
capabilities: getAgentCapabilities(a.name)
|
|
836
|
-
}));
|
|
837
|
-
deps.sendJson(res, 200, { agents: agentsWithCaps, default: defaultAgent });
|
|
838
|
-
});
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
// src/plugins/api-server/routes/notify.ts
|
|
842
|
-
function registerNotifyRoutes(router, deps) {
|
|
843
|
-
router.post("/api/notify", async (req, res) => {
|
|
844
|
-
const body = await deps.readBody(req);
|
|
845
|
-
let message;
|
|
846
|
-
if (body) {
|
|
847
|
-
try {
|
|
848
|
-
const parsed = JSON.parse(body);
|
|
849
|
-
message = parsed.message;
|
|
850
|
-
} catch {
|
|
851
|
-
deps.sendJson(res, 400, { error: "Invalid JSON body" });
|
|
852
|
-
return;
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
if (!message) {
|
|
856
|
-
deps.sendJson(res, 400, { error: "Missing message" });
|
|
857
|
-
return;
|
|
858
|
-
}
|
|
859
|
-
await deps.core.notificationManager.notifyAll({
|
|
860
|
-
sessionId: "system",
|
|
861
|
-
type: "completed",
|
|
862
|
-
summary: message
|
|
863
|
-
});
|
|
864
|
-
deps.sendJson(res, 200, { ok: true });
|
|
865
|
-
});
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
// src/plugins/api-server/api-server.ts
|
|
869
|
-
var log2 = createChildLogger({ module: "api-server" });
|
|
870
|
-
var DEFAULT_PORT_FILE = path2.join(os.homedir(), ".openacp", "api.port");
|
|
871
|
-
var cachedVersion;
|
|
872
|
-
function getVersion() {
|
|
873
|
-
if (cachedVersion) return cachedVersion;
|
|
874
|
-
try {
|
|
875
|
-
const __filename = fileURLToPath2(import.meta.url);
|
|
876
|
-
const pkgPath = path2.resolve(
|
|
877
|
-
path2.dirname(__filename),
|
|
878
|
-
"../../../package.json"
|
|
879
|
-
);
|
|
880
|
-
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
|
|
881
|
-
cachedVersion = pkg.version ?? "0.0.0-dev";
|
|
882
|
-
} catch {
|
|
883
|
-
cachedVersion = "0.0.0-dev";
|
|
884
|
-
}
|
|
885
|
-
return cachedVersion;
|
|
886
|
-
}
|
|
887
|
-
var ApiServer = class {
|
|
888
|
-
constructor(core, config, portFilePath, topicManager, secretFilePath, uiDir) {
|
|
889
|
-
this.core = core;
|
|
890
|
-
this.config = config;
|
|
891
|
-
this.topicManager = topicManager;
|
|
892
|
-
this.portFilePath = portFilePath ?? DEFAULT_PORT_FILE;
|
|
893
|
-
this.secretFilePath = secretFilePath ?? path2.join(os.homedir(), ".openacp", "api-secret");
|
|
894
|
-
this.staticServer = new StaticServer(uiDir);
|
|
895
|
-
this.sseManager = new SSEManager(
|
|
896
|
-
core.eventBus,
|
|
897
|
-
() => {
|
|
898
|
-
const sessions = this.core.sessionManager.listSessions();
|
|
899
|
-
return {
|
|
900
|
-
active: sessions.filter(
|
|
901
|
-
(s) => s.status === "active" || s.status === "initializing"
|
|
902
|
-
).length,
|
|
903
|
-
total: sessions.length
|
|
904
|
-
};
|
|
905
|
-
},
|
|
906
|
-
this.startedAt
|
|
907
|
-
);
|
|
908
|
-
this.router = new Router();
|
|
909
|
-
const deps = {
|
|
910
|
-
core: this.core,
|
|
911
|
-
topicManager: this.topicManager,
|
|
912
|
-
startedAt: this.startedAt,
|
|
913
|
-
getVersion,
|
|
914
|
-
sendJson: this.sendJson.bind(this),
|
|
915
|
-
readBody: this.readBody.bind(this)
|
|
916
|
-
};
|
|
917
|
-
registerHealthRoutes(this.router, deps);
|
|
918
|
-
registerSessionRoutes(this.router, deps);
|
|
919
|
-
registerConfigRoutes(this.router, deps);
|
|
920
|
-
registerTopicRoutes(this.router, deps);
|
|
921
|
-
registerTunnelRoutes(this.router, deps);
|
|
922
|
-
registerAgentRoutes(this.router, deps);
|
|
923
|
-
registerNotifyRoutes(this.router, deps);
|
|
924
|
-
}
|
|
925
|
-
server = null;
|
|
926
|
-
actualPort = 0;
|
|
927
|
-
portFilePath;
|
|
928
|
-
startedAt = Date.now();
|
|
929
|
-
secret = "";
|
|
930
|
-
secretFilePath;
|
|
931
|
-
sseManager;
|
|
932
|
-
staticServer;
|
|
933
|
-
router;
|
|
934
|
-
async start() {
|
|
935
|
-
this.loadOrCreateSecret();
|
|
936
|
-
this.server = http.createServer((req, res) => this.handleRequest(req, res));
|
|
937
|
-
await new Promise((resolve3, reject) => {
|
|
938
|
-
this.server.on("error", (err) => {
|
|
939
|
-
if (err.code === "EADDRINUSE") {
|
|
940
|
-
log2.warn(
|
|
941
|
-
{ port: this.config.port },
|
|
942
|
-
"API port in use, continuing without API server"
|
|
943
|
-
);
|
|
944
|
-
this.server = null;
|
|
945
|
-
resolve3();
|
|
946
|
-
} else {
|
|
947
|
-
reject(err);
|
|
948
|
-
}
|
|
949
|
-
});
|
|
950
|
-
this.server.listen(this.config.port, this.config.host, () => {
|
|
951
|
-
const addr = this.server.address();
|
|
952
|
-
if (addr && typeof addr === "object") {
|
|
953
|
-
this.actualPort = addr.port;
|
|
954
|
-
}
|
|
955
|
-
this.writePortFile();
|
|
956
|
-
log2.info(
|
|
957
|
-
{ host: this.config.host, port: this.actualPort },
|
|
958
|
-
"API server listening"
|
|
959
|
-
);
|
|
960
|
-
this.sseManager.setup();
|
|
961
|
-
if (this.config.host !== "127.0.0.1" && this.config.host !== "localhost") {
|
|
962
|
-
log2.warn(
|
|
963
|
-
"API server binding to non-localhost. Ensure api-secret file is secured."
|
|
964
|
-
);
|
|
965
|
-
}
|
|
966
|
-
resolve3();
|
|
967
|
-
});
|
|
968
|
-
});
|
|
969
|
-
}
|
|
970
|
-
async stop() {
|
|
971
|
-
this.sseManager.stop();
|
|
972
|
-
this.removePortFile();
|
|
973
|
-
if (this.server) {
|
|
974
|
-
await new Promise((resolve3) => {
|
|
975
|
-
this.server.close(() => resolve3());
|
|
976
|
-
});
|
|
977
|
-
this.server = null;
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
getPort() {
|
|
981
|
-
return this.actualPort;
|
|
982
|
-
}
|
|
983
|
-
getSecret() {
|
|
984
|
-
return this.secret;
|
|
985
|
-
}
|
|
986
|
-
writePortFile() {
|
|
987
|
-
const dir = path2.dirname(this.portFilePath);
|
|
988
|
-
fs2.mkdirSync(dir, { recursive: true });
|
|
989
|
-
fs2.writeFileSync(this.portFilePath, String(this.actualPort));
|
|
990
|
-
}
|
|
991
|
-
removePortFile() {
|
|
992
|
-
try {
|
|
993
|
-
fs2.unlinkSync(this.portFilePath);
|
|
994
|
-
} catch {
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
loadOrCreateSecret() {
|
|
998
|
-
const dir = path2.dirname(this.secretFilePath);
|
|
999
|
-
fs2.mkdirSync(dir, { recursive: true });
|
|
1000
|
-
try {
|
|
1001
|
-
this.secret = fs2.readFileSync(this.secretFilePath, "utf-8").trim();
|
|
1002
|
-
if (this.secret) {
|
|
1003
|
-
try {
|
|
1004
|
-
const stat = fs2.statSync(this.secretFilePath);
|
|
1005
|
-
const mode = stat.mode & 511;
|
|
1006
|
-
if (mode & 63) {
|
|
1007
|
-
log2.warn(
|
|
1008
|
-
{ path: this.secretFilePath, mode: "0" + mode.toString(8) },
|
|
1009
|
-
"API secret file has insecure permissions (should be 0600). Run: chmod 600 %s",
|
|
1010
|
-
this.secretFilePath
|
|
1011
|
-
);
|
|
1012
|
-
}
|
|
1013
|
-
} catch {
|
|
1014
|
-
}
|
|
1015
|
-
return;
|
|
1016
|
-
}
|
|
1017
|
-
} catch {
|
|
1018
|
-
}
|
|
1019
|
-
this.secret = crypto.randomBytes(32).toString("hex");
|
|
1020
|
-
fs2.writeFileSync(this.secretFilePath, this.secret, { mode: 384 });
|
|
1021
|
-
}
|
|
1022
|
-
authenticate(req, allowQueryParam = false) {
|
|
1023
|
-
const authHeader = req.headers.authorization;
|
|
1024
|
-
if (authHeader?.startsWith("Bearer ")) {
|
|
1025
|
-
const token = authHeader.slice(7);
|
|
1026
|
-
if (token.length === this.secret.length && crypto.timingSafeEqual(
|
|
1027
|
-
Buffer.from(token, "utf-8"),
|
|
1028
|
-
Buffer.from(this.secret, "utf-8")
|
|
1029
|
-
)) {
|
|
1030
|
-
return true;
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
if (allowQueryParam) {
|
|
1034
|
-
const parsedUrl = new URL(req.url || "", "http://localhost");
|
|
1035
|
-
const qToken = parsedUrl.searchParams.get("token");
|
|
1036
|
-
if (qToken && qToken.length === this.secret.length && crypto.timingSafeEqual(
|
|
1037
|
-
Buffer.from(qToken, "utf-8"),
|
|
1038
|
-
Buffer.from(this.secret, "utf-8")
|
|
1039
|
-
)) {
|
|
1040
|
-
return true;
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
return false;
|
|
1044
|
-
}
|
|
1045
|
-
async handleRequest(req, res) {
|
|
1046
|
-
const method = req.method?.toUpperCase();
|
|
1047
|
-
const url = req.url || "";
|
|
1048
|
-
if (url.startsWith("/api/")) {
|
|
1049
|
-
const isExempt = method === "GET" && (url === "/api/health" || url === "/api/version" || url.startsWith("/api/events"));
|
|
1050
|
-
if (!isExempt && !this.authenticate(req)) {
|
|
1051
|
-
this.sendJson(res, 401, { error: "Unauthorized" });
|
|
1052
|
-
return;
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
try {
|
|
1056
|
-
if (method === "GET" && url.startsWith("/api/events")) {
|
|
1057
|
-
if (!this.authenticate(req, true)) {
|
|
1058
|
-
this.sendJson(res, 401, { error: "Unauthorized" });
|
|
1059
|
-
return;
|
|
1060
|
-
}
|
|
1061
|
-
this.sseManager.handleRequest(req, res);
|
|
1062
|
-
return;
|
|
1063
|
-
}
|
|
1064
|
-
if (url.startsWith("/api/")) {
|
|
1065
|
-
const match = this.router.match(method, url);
|
|
1066
|
-
if (match) {
|
|
1067
|
-
await match.handler(req, res, match.params);
|
|
1068
|
-
} else {
|
|
1069
|
-
this.sendJson(res, 404, { error: "Not found" });
|
|
1070
|
-
}
|
|
1071
|
-
return;
|
|
1072
|
-
}
|
|
1073
|
-
if (!this.staticServer.serve(req, res)) {
|
|
1074
|
-
this.sendJson(res, 404, { error: "Not found" });
|
|
1075
|
-
}
|
|
1076
|
-
} catch (err) {
|
|
1077
|
-
log2.error({ err }, "API request error");
|
|
1078
|
-
this.sendJson(res, 500, { error: "Internal server error" });
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
sendJson(res, status, data) {
|
|
1082
|
-
res.writeHead(status, { "Content-Type": "application/json" });
|
|
1083
|
-
res.end(JSON.stringify(data));
|
|
1084
|
-
}
|
|
1085
|
-
readBody(req) {
|
|
1086
|
-
const MAX_BODY_SIZE = 1024 * 1024;
|
|
1087
|
-
return new Promise((resolve3) => {
|
|
1088
|
-
let data = "";
|
|
1089
|
-
let size = 0;
|
|
1090
|
-
let destroyed = false;
|
|
1091
|
-
req.on("data", (chunk) => {
|
|
1092
|
-
size += chunk.length;
|
|
1093
|
-
if (size > MAX_BODY_SIZE && !destroyed) {
|
|
1094
|
-
destroyed = true;
|
|
1095
|
-
req.destroy();
|
|
1096
|
-
resolve3(null);
|
|
1097
|
-
return;
|
|
1098
|
-
}
|
|
1099
|
-
if (!destroyed) data += chunk;
|
|
1100
|
-
});
|
|
1101
|
-
req.on("end", () => {
|
|
1102
|
-
if (!destroyed) resolve3(data);
|
|
1103
|
-
});
|
|
1104
|
-
req.on("error", () => {
|
|
1105
|
-
if (!destroyed) resolve3("");
|
|
1106
|
-
});
|
|
1107
|
-
});
|
|
1108
|
-
}
|
|
1109
|
-
};
|
|
1110
|
-
|
|
1111
|
-
export {
|
|
1112
|
-
SSEManager,
|
|
1113
|
-
StaticServer,
|
|
1114
|
-
ApiServer
|
|
1115
|
-
};
|
|
1116
|
-
//# sourceMappingURL=chunk-BTJHGSLM.js.map
|