@n1creator/openacp-cli 2026.712.7 → 2026.712.9
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 +88 -8
- package/dist/cli.js +3112 -1016
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +158 -7
- package/dist/index.js +1810 -327
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
package/dist/index.js
CHANGED
|
@@ -8,6 +8,111 @@ var __export = (target, all) => {
|
|
|
8
8
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
// src/core/security/network-redaction.ts
|
|
12
|
+
function sanitizeProxyDebugNamespaces(value) {
|
|
13
|
+
if (!value) return value;
|
|
14
|
+
const entries = value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
15
|
+
const safeEntries = entries.filter((item) => item.startsWith("-") || !UNSAFE_PROXY_DEBUG.test(item));
|
|
16
|
+
if (safeEntries.some((item) => !item.startsWith("-") && item.includes("*"))) {
|
|
17
|
+
for (const exclusion of PROXY_DEBUG_EXCLUSIONS) if (!safeEntries.includes(exclusion)) safeEntries.push(exclusion);
|
|
18
|
+
}
|
|
19
|
+
const safe = safeEntries.join(",");
|
|
20
|
+
return safe || void 0;
|
|
21
|
+
}
|
|
22
|
+
function redactNetworkSecrets(input2) {
|
|
23
|
+
return input2.replace(/(https?:\/\/api\.telegram\.org\/bot)[^/?#\s]+/gi, "$1<redacted>").replace(/(\/bot)[0-9]+:[A-Za-z0-9_-]+/g, "$1<redacted>").replace(/\b([a-z][a-z0-9+.-]*:\/\/)[^/@\s]+@/gi, "$1<redacted>@").replace(new RegExp(`([?&](?:${SENSITIVE_QUERY_KEYS})=)[^&#\\s]*`, "gi"), "$1<redacted>").replace(/\b(authorization|proxy-authorization)\s*[:=]\s*(?:Bearer|Basic)\s+[^\s,;}"']+/gi, "$1: <redacted>").replace(/\b(x-api-key|api-key|cookie|set-cookie)\s*[:=]\s*[^\r\n,;}]+/gi, "$1: <redacted>");
|
|
24
|
+
}
|
|
25
|
+
function normalizedKey(key) {
|
|
26
|
+
return key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
27
|
+
}
|
|
28
|
+
function isSensitiveObjectKey(key) {
|
|
29
|
+
const normalized = normalizedKey(key);
|
|
30
|
+
return SENSITIVE_OBJECT_KEYS.has(normalized) || /(?:token|secret|password|passwd|apikey|authorization|cookie)$/.test(normalized);
|
|
31
|
+
}
|
|
32
|
+
function sanitizeNetworkLogValue(value, seen = /* @__PURE__ */ new WeakMap()) {
|
|
33
|
+
if (typeof value === "string") return redactNetworkSecrets(value);
|
|
34
|
+
if (value instanceof Error) {
|
|
35
|
+
const sanitized = new Error(redactNetworkSecrets(value.message));
|
|
36
|
+
sanitized.name = value.name;
|
|
37
|
+
if (value.stack) sanitized.stack = redactNetworkSecrets(value.stack);
|
|
38
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
39
|
+
;
|
|
40
|
+
sanitized[key] = isSensitiveObjectKey(key) ? "<redacted>" : sanitizeNetworkLogValue(nested, seen);
|
|
41
|
+
}
|
|
42
|
+
return sanitized;
|
|
43
|
+
}
|
|
44
|
+
if (!value || typeof value !== "object") return value;
|
|
45
|
+
if (Buffer.isBuffer(value) || value instanceof Uint8Array || value instanceof Date) return value;
|
|
46
|
+
if (seen.has(value)) return seen.get(value);
|
|
47
|
+
if (Array.isArray(value)) {
|
|
48
|
+
const result2 = [];
|
|
49
|
+
seen.set(value, result2);
|
|
50
|
+
for (const item of value) result2.push(sanitizeNetworkLogValue(item, seen));
|
|
51
|
+
return result2;
|
|
52
|
+
}
|
|
53
|
+
const result = {};
|
|
54
|
+
seen.set(value, result);
|
|
55
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
56
|
+
result[key] = isSensitiveObjectKey(key) ? "<redacted>" : sanitizeNetworkLogValue(nested, seen);
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
var SENSITIVE_QUERY_KEYS, UNSAFE_PROXY_DEBUG, PROXY_DEBUG_EXCLUSIONS, SENSITIVE_OBJECT_KEYS;
|
|
61
|
+
var init_network_redaction = __esm({
|
|
62
|
+
"src/core/security/network-redaction.ts"() {
|
|
63
|
+
"use strict";
|
|
64
|
+
SENSITIVE_QUERY_KEYS = [
|
|
65
|
+
"access_token",
|
|
66
|
+
"access-token",
|
|
67
|
+
"token",
|
|
68
|
+
"api_key",
|
|
69
|
+
"api-key",
|
|
70
|
+
"apikey",
|
|
71
|
+
"key",
|
|
72
|
+
"secret",
|
|
73
|
+
"password",
|
|
74
|
+
"passwd",
|
|
75
|
+
"auth",
|
|
76
|
+
"authorization",
|
|
77
|
+
"signature",
|
|
78
|
+
"sig",
|
|
79
|
+
"bot_token",
|
|
80
|
+
"bot-token"
|
|
81
|
+
].join("|");
|
|
82
|
+
UNSAFE_PROXY_DEBUG = /^\*?(?:proxy-agent|proxy|https-proxy-agent|http-proxy-agent|socks-proxy-agent)(?::|\*|$)/i;
|
|
83
|
+
PROXY_DEBUG_EXCLUSIONS = [
|
|
84
|
+
"-proxy",
|
|
85
|
+
"-proxy:*",
|
|
86
|
+
"-proxy-agent",
|
|
87
|
+
"-proxy-agent:*",
|
|
88
|
+
"-https-proxy-agent",
|
|
89
|
+
"-https-proxy-agent:*",
|
|
90
|
+
"-http-proxy-agent",
|
|
91
|
+
"-http-proxy-agent:*",
|
|
92
|
+
"-socks-proxy-agent",
|
|
93
|
+
"-socks-proxy-agent:*"
|
|
94
|
+
];
|
|
95
|
+
SENSITIVE_OBJECT_KEYS = /* @__PURE__ */ new Set([
|
|
96
|
+
"authorization",
|
|
97
|
+
"proxyauthorization",
|
|
98
|
+
"xapikey",
|
|
99
|
+
"apikey",
|
|
100
|
+
"token",
|
|
101
|
+
"xgoogapikey",
|
|
102
|
+
"xauthtoken",
|
|
103
|
+
"xtelegrambottoken",
|
|
104
|
+
"authentication",
|
|
105
|
+
"accesstoken",
|
|
106
|
+
"password",
|
|
107
|
+
"passwd",
|
|
108
|
+
"secret",
|
|
109
|
+
"bottoken",
|
|
110
|
+
"cookie",
|
|
111
|
+
"setcookie"
|
|
112
|
+
]);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
11
116
|
// src/core/utils/log.ts
|
|
12
117
|
var log_exports = {};
|
|
13
118
|
__export(log_exports, {
|
|
@@ -124,7 +229,7 @@ function initLogger(config) {
|
|
|
124
229
|
]
|
|
125
230
|
});
|
|
126
231
|
currentTransport = transports;
|
|
127
|
-
rootLogger = pino({ level: config.level }, transports);
|
|
232
|
+
rootLogger = pino({ level: config.level, hooks: networkSecurityHooks }, transports);
|
|
128
233
|
initialized = true;
|
|
129
234
|
Object.assign(log, wrapVariadic(rootLogger));
|
|
130
235
|
return rootLogger;
|
|
@@ -135,7 +240,7 @@ function setLogLevel(level) {
|
|
|
135
240
|
function createChildLogger(context) {
|
|
136
241
|
return new Proxy({}, {
|
|
137
242
|
get(_target, prop, receiver) {
|
|
138
|
-
const child = rootLogger.child(context);
|
|
243
|
+
const child = rootLogger.child(sanitizeNetworkLogValue(context));
|
|
139
244
|
const value = Reflect.get(child, prop, receiver);
|
|
140
245
|
return typeof value === "function" ? value.bind(child) : value;
|
|
141
246
|
}
|
|
@@ -149,7 +254,7 @@ function createSessionLogger(sessionId, parentLogger) {
|
|
|
149
254
|
try {
|
|
150
255
|
const sessionLogPath = path.join(sessionLogDir, `${sessionId}.log`);
|
|
151
256
|
const dest = pino.destination(sessionLogPath);
|
|
152
|
-
const sessionFileLogger = pino({ level: parentLogger.level }, dest).child({ sessionId });
|
|
257
|
+
const sessionFileLogger = pino({ level: parentLogger.level, hooks: networkSecurityHooks }, dest).child({ sessionId });
|
|
153
258
|
const combinedChild = parentLogger.child({ sessionId });
|
|
154
259
|
const originalInfo = combinedChild.info.bind(combinedChild);
|
|
155
260
|
const originalWarn = combinedChild.warn.bind(combinedChild);
|
|
@@ -183,16 +288,34 @@ function createSessionLogger(sessionId, parentLogger) {
|
|
|
183
288
|
return parentLogger.child({ sessionId });
|
|
184
289
|
}
|
|
185
290
|
}
|
|
186
|
-
function closeSessionLogger(logger) {
|
|
291
|
+
async function closeSessionLogger(logger) {
|
|
187
292
|
const dest = logger.__sessionDest;
|
|
188
|
-
if (dest
|
|
189
|
-
|
|
190
|
-
|
|
293
|
+
if (!dest) return;
|
|
294
|
+
await new Promise((resolve7) => {
|
|
295
|
+
let settled = false;
|
|
296
|
+
const done = () => {
|
|
297
|
+
if (!settled) {
|
|
298
|
+
settled = true;
|
|
299
|
+
resolve7();
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
const timeout = setTimeout(done, 3e3);
|
|
303
|
+
dest.once?.("close", () => {
|
|
304
|
+
clearTimeout(timeout);
|
|
305
|
+
done();
|
|
306
|
+
});
|
|
307
|
+
if (typeof dest.end === "function") dest.end();
|
|
308
|
+
else {
|
|
309
|
+
dest.destroy?.();
|
|
310
|
+
clearTimeout(timeout);
|
|
311
|
+
done();
|
|
312
|
+
}
|
|
313
|
+
});
|
|
191
314
|
}
|
|
192
315
|
async function shutdownLogger() {
|
|
193
316
|
if (!initialized) return;
|
|
194
317
|
const transport = currentTransport;
|
|
195
|
-
rootLogger = pino({ level: "debug" });
|
|
318
|
+
rootLogger = pino({ level: "debug", hooks: networkSecurityHooks });
|
|
196
319
|
Object.assign(log, wrapVariadic(rootLogger));
|
|
197
320
|
currentTransport = void 0;
|
|
198
321
|
logDir = void 0;
|
|
@@ -229,12 +352,19 @@ async function cleanupOldSessionLogs(retentionDays) {
|
|
|
229
352
|
} catch {
|
|
230
353
|
}
|
|
231
354
|
}
|
|
232
|
-
var rootLogger, initialized, logDir, currentTransport, log, muteCount, savedLevel;
|
|
355
|
+
var networkSecurityHooks, rootLogger, initialized, logDir, currentTransport, log, muteCount, savedLevel;
|
|
233
356
|
var init_log = __esm({
|
|
234
357
|
"src/core/utils/log.ts"() {
|
|
235
358
|
"use strict";
|
|
359
|
+
init_network_redaction();
|
|
360
|
+
networkSecurityHooks = {
|
|
361
|
+
logMethod(inputArgs, method) {
|
|
362
|
+
method.apply(this, inputArgs.map((arg) => sanitizeNetworkLogValue(arg)));
|
|
363
|
+
}
|
|
364
|
+
};
|
|
236
365
|
rootLogger = pino({
|
|
237
366
|
level: "debug",
|
|
367
|
+
hooks: networkSecurityHooks,
|
|
238
368
|
transport: { target: "pino-pretty", options: { colorize: true, translateTime: "SYS:standard", destination: 2 } }
|
|
239
369
|
});
|
|
240
370
|
initialized = false;
|
|
@@ -328,22 +458,22 @@ __export(config_registry_exports, {
|
|
|
328
458
|
resolveOptions: () => resolveOptions,
|
|
329
459
|
setFieldValueAsync: () => setFieldValueAsync
|
|
330
460
|
});
|
|
331
|
-
function getFieldDef(
|
|
332
|
-
return CONFIG_REGISTRY.find((f) => f.path ===
|
|
461
|
+
function getFieldDef(path37) {
|
|
462
|
+
return CONFIG_REGISTRY.find((f) => f.path === path37);
|
|
333
463
|
}
|
|
334
464
|
function getSafeFields() {
|
|
335
465
|
return CONFIG_REGISTRY.filter((f) => f.scope === "safe");
|
|
336
466
|
}
|
|
337
|
-
function isHotReloadable(
|
|
338
|
-
const def = getFieldDef(
|
|
467
|
+
function isHotReloadable(path37) {
|
|
468
|
+
const def = getFieldDef(path37);
|
|
339
469
|
return def?.hotReload ?? false;
|
|
340
470
|
}
|
|
341
471
|
function resolveOptions(def, config) {
|
|
342
472
|
if (!def.options) return void 0;
|
|
343
473
|
return typeof def.options === "function" ? def.options(config) : def.options;
|
|
344
474
|
}
|
|
345
|
-
function getConfigValue(config,
|
|
346
|
-
const parts =
|
|
475
|
+
function getConfigValue(config, path37) {
|
|
476
|
+
const parts = path37.split(".");
|
|
347
477
|
let current = config;
|
|
348
478
|
for (const part of parts) {
|
|
349
479
|
if (current && typeof current === "object" && part in current) {
|
|
@@ -1376,7 +1506,7 @@ function stripPythonPackageVersion(pkg) {
|
|
|
1376
1506
|
const at = pkg.indexOf("@");
|
|
1377
1507
|
return at === -1 ? pkg : pkg.slice(0, at);
|
|
1378
1508
|
}
|
|
1379
|
-
async function installAgent(agent, store, progress, agentsDir) {
|
|
1509
|
+
async function installAgent(agent, store, progress, agentsDir, scopedFetch = globalThis.fetch) {
|
|
1380
1510
|
const agentKey = getAgentAlias(agent.id);
|
|
1381
1511
|
await progress?.onStart(agent.id, agent.name);
|
|
1382
1512
|
const dist = resolveDistribution(agent);
|
|
@@ -1396,7 +1526,7 @@ Install it with: pip install uv`;
|
|
|
1396
1526
|
let binaryPath;
|
|
1397
1527
|
if (dist.type === "binary") {
|
|
1398
1528
|
try {
|
|
1399
|
-
binaryPath = await downloadAndExtract(agent.id, dist.archive, progress, agentsDir);
|
|
1529
|
+
binaryPath = await downloadAndExtract(agent.id, dist.archive, progress, agentsDir, scopedFetch);
|
|
1400
1530
|
} catch (err) {
|
|
1401
1531
|
const msg = `Failed to download ${agent.name}. Please try again or install manually.`;
|
|
1402
1532
|
await progress?.onError(msg);
|
|
@@ -1412,12 +1542,12 @@ Install it with: pip install uv`;
|
|
|
1412
1542
|
await progress?.onSuccess(agent.name);
|
|
1413
1543
|
return { ok: true, agentKey, setupSteps: setupSteps.length > 0 ? setupSteps : void 0 };
|
|
1414
1544
|
}
|
|
1415
|
-
async function downloadAndExtract(agentId, archiveUrl, progress, agentsDir) {
|
|
1545
|
+
async function downloadAndExtract(agentId, archiveUrl, progress, agentsDir, scopedFetch = globalThis.fetch) {
|
|
1416
1546
|
const destDir = path10.join(agentsDir ?? DEFAULT_AGENTS_DIR, agentId);
|
|
1417
1547
|
fs11.mkdirSync(destDir, { recursive: true });
|
|
1418
1548
|
await progress?.onStep("Downloading...");
|
|
1419
1549
|
log11.info({ agentId, url: archiveUrl }, "Downloading agent binary");
|
|
1420
|
-
const response = await
|
|
1550
|
+
const response = await scopedFetch(archiveUrl);
|
|
1421
1551
|
if (!response.ok) {
|
|
1422
1552
|
throw new Error(`Download failed: ${response.status} ${response.statusText}`);
|
|
1423
1553
|
}
|
|
@@ -1437,20 +1567,39 @@ async function readResponseWithProgress(response, contentLength, progress) {
|
|
|
1437
1567
|
const arrayBuffer = await response.arrayBuffer();
|
|
1438
1568
|
return Buffer.from(arrayBuffer);
|
|
1439
1569
|
}
|
|
1440
|
-
const reader = response.body.getReader();
|
|
1441
1570
|
const chunks = [];
|
|
1442
1571
|
let received = 0;
|
|
1443
|
-
|
|
1444
|
-
const { done, value } = await reader.read();
|
|
1445
|
-
if (done) break;
|
|
1572
|
+
const consume = async (value) => {
|
|
1446
1573
|
chunks.push(value);
|
|
1447
1574
|
received += value.length;
|
|
1448
|
-
if (received > MAX_DOWNLOAD_SIZE) {
|
|
1449
|
-
|
|
1575
|
+
if (received > MAX_DOWNLOAD_SIZE) throw new Error(`Download exceeds size limit of ${MAX_DOWNLOAD_SIZE} bytes`);
|
|
1576
|
+
if (contentLength > 0) await progress?.onDownloadProgress(Math.min(100, Math.round(received / contentLength * 100)));
|
|
1577
|
+
};
|
|
1578
|
+
const body = response.body;
|
|
1579
|
+
if (typeof body.getReader === "function") {
|
|
1580
|
+
const reader = body.getReader();
|
|
1581
|
+
try {
|
|
1582
|
+
while (true) {
|
|
1583
|
+
const { done, value } = await reader.read();
|
|
1584
|
+
if (done) break;
|
|
1585
|
+
await consume(value);
|
|
1586
|
+
}
|
|
1587
|
+
} catch (error) {
|
|
1588
|
+
await reader.cancel?.(error).catch(() => {
|
|
1589
|
+
});
|
|
1590
|
+
throw error;
|
|
1591
|
+
} finally {
|
|
1592
|
+
reader.releaseLock?.();
|
|
1450
1593
|
}
|
|
1451
|
-
|
|
1452
|
-
|
|
1594
|
+
} else if (body[Symbol.asyncIterator]) {
|
|
1595
|
+
try {
|
|
1596
|
+
for await (const value of body) await consume(new Uint8Array(value));
|
|
1597
|
+
} catch (error) {
|
|
1598
|
+
body.destroy?.(error instanceof Error ? error : void 0);
|
|
1599
|
+
throw error;
|
|
1453
1600
|
}
|
|
1601
|
+
} else {
|
|
1602
|
+
return Buffer.from(await response.arrayBuffer());
|
|
1454
1603
|
}
|
|
1455
1604
|
return Buffer.concat(chunks);
|
|
1456
1605
|
}
|
|
@@ -1551,12 +1700,16 @@ var init_groq = __esm({
|
|
|
1551
1700
|
"use strict";
|
|
1552
1701
|
GROQ_API_URL = "https://api.groq.com/openai/v1/audio/transcriptions";
|
|
1553
1702
|
GroqSTT = class {
|
|
1554
|
-
constructor(apiKey, defaultModel = "whisper-large-v3-turbo") {
|
|
1703
|
+
constructor(apiKey, defaultModel = "whisper-large-v3-turbo", scopedFetch = globalThis.fetch, getScopedFetch) {
|
|
1555
1704
|
this.apiKey = apiKey;
|
|
1556
1705
|
this.defaultModel = defaultModel;
|
|
1706
|
+
this.scopedFetch = scopedFetch;
|
|
1707
|
+
this.getScopedFetch = getScopedFetch;
|
|
1557
1708
|
}
|
|
1558
1709
|
apiKey;
|
|
1559
1710
|
defaultModel;
|
|
1711
|
+
scopedFetch;
|
|
1712
|
+
getScopedFetch;
|
|
1560
1713
|
name = "groq";
|
|
1561
1714
|
/**
|
|
1562
1715
|
* Transcribes audio using the Groq Whisper API.
|
|
@@ -1573,7 +1726,7 @@ var init_groq = __esm({
|
|
|
1573
1726
|
if (options?.language) {
|
|
1574
1727
|
form.append("language", options.language);
|
|
1575
1728
|
}
|
|
1576
|
-
const resp = await
|
|
1729
|
+
const resp = await (this.getScopedFetch?.() ?? this.scopedFetch)(GROQ_API_URL, {
|
|
1577
1730
|
method: "POST",
|
|
1578
1731
|
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
1579
1732
|
body: form
|
|
@@ -1693,8 +1846,1065 @@ var init_native_stt = __esm({
|
|
|
1693
1846
|
}
|
|
1694
1847
|
});
|
|
1695
1848
|
|
|
1849
|
+
// src/core/network/proxy-types.ts
|
|
1850
|
+
var PROXY_PROTOCOLS;
|
|
1851
|
+
var init_proxy_types = __esm({
|
|
1852
|
+
"src/core/network/proxy-types.ts"() {
|
|
1853
|
+
"use strict";
|
|
1854
|
+
PROXY_PROTOCOLS = ["http", "https", "socks5", "socks5h"];
|
|
1855
|
+
}
|
|
1856
|
+
});
|
|
1857
|
+
|
|
1858
|
+
// src/core/network/proxy-store.ts
|
|
1859
|
+
import fs15 from "fs";
|
|
1860
|
+
import path15 from "path";
|
|
1861
|
+
import net from "net";
|
|
1862
|
+
function atomicWrite(file, value, mode) {
|
|
1863
|
+
fs15.mkdirSync(path15.dirname(file), { recursive: true, mode: 448 });
|
|
1864
|
+
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
1865
|
+
fs15.writeFileSync(tmp, `${JSON.stringify(value, null, 2)}
|
|
1866
|
+
`, { mode, flag: "wx" });
|
|
1867
|
+
fs15.chmodSync(tmp, mode);
|
|
1868
|
+
const fd = fs15.openSync(tmp, "r");
|
|
1869
|
+
fs15.fsyncSync(fd);
|
|
1870
|
+
fs15.closeSync(fd);
|
|
1871
|
+
fs15.renameSync(tmp, file);
|
|
1872
|
+
try {
|
|
1873
|
+
const dir = fs15.openSync(path15.dirname(file), "r");
|
|
1874
|
+
fs15.fsyncSync(dir);
|
|
1875
|
+
fs15.closeSync(dir);
|
|
1876
|
+
} catch {
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
function isRoute(value) {
|
|
1880
|
+
return value === "direct" || value === "inherit" || typeof value === "string" && /^profile:[a-z0-9][a-z0-9._-]{0,63}$/i.test(value);
|
|
1881
|
+
}
|
|
1882
|
+
function isProxyScope(value) {
|
|
1883
|
+
return typeof value === "string" && /^[a-z0-9][a-z0-9_-]*(?:\.[a-z0-9][a-z0-9_-]*)+$/i.test(value) && value.length <= 160;
|
|
1884
|
+
}
|
|
1885
|
+
function canonicalStoredHost(value) {
|
|
1886
|
+
if (typeof value !== "string" || !value || /[\u0000-\u001f\u007f\s/@?#]/.test(value)) throw new Error("invalid profile host");
|
|
1887
|
+
if (value.startsWith("[") && value.endsWith("]") && net.isIP(value.slice(1, -1)) === 6) return value.slice(1, -1).toLowerCase();
|
|
1888
|
+
if (net.isIP(value) === 4 || net.isIP(value) === 6) return value.toLowerCase();
|
|
1889
|
+
const host = value.toLowerCase().replace(/\.$/, "");
|
|
1890
|
+
if (host.length > 253 || !host.split(".").every((label) => /^(?!-)[a-z0-9-]{1,63}(?<!-)$/.test(label))) throw new Error("invalid profile host");
|
|
1891
|
+
return host;
|
|
1892
|
+
}
|
|
1893
|
+
function validateConfig(value) {
|
|
1894
|
+
if (!value || typeof value !== "object") throw new Error("root must be an object");
|
|
1895
|
+
const v = value;
|
|
1896
|
+
if (v.version === 1) {
|
|
1897
|
+
v.version = PROXY_STORE_VERSION;
|
|
1898
|
+
v.revision = 0;
|
|
1899
|
+
v.persistedScopes = Object.keys(v.routing?.routes ?? {});
|
|
1900
|
+
}
|
|
1901
|
+
if (v.version !== PROXY_STORE_VERSION) throw new Error(`unsupported version ${String(v.version)}`);
|
|
1902
|
+
if (!Number.isSafeInteger(v.revision) || v.revision < 0) throw new Error("revision must be a non-negative integer");
|
|
1903
|
+
if (!Array.isArray(v.profiles) || !v.routing || typeof v.routing !== "object" || !isRoute(v.routing.global) || !v.routing.routes || typeof v.routing.routes !== "object" || Array.isArray(v.routing.routes)) throw new Error("invalid profiles/routing shape");
|
|
1904
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1905
|
+
for (const p of v.profiles) {
|
|
1906
|
+
if (!p || typeof p !== "object" || !/^[a-z0-9][a-z0-9._-]{0,63}$/i.test(p.id) || ids.has(p.id) || !["http", "https", "socks5", "socks5h"].includes(p.protocol) || !Number.isInteger(p.port) || p.port < 1 || p.port > 65535) throw new Error("invalid profile");
|
|
1907
|
+
ids.add(p.id);
|
|
1908
|
+
p.host = canonicalStoredHost(p.host);
|
|
1909
|
+
if (typeof p.name !== "string" || !p.name.trim() || p.name.length > 100 || typeof p.failClosed !== "boolean" || typeof p.hasCredentials !== "boolean") throw new Error("invalid profile fields");
|
|
1910
|
+
if (!Array.isArray(p.noProxy) || p.noProxy.length > 256 || p.noProxy.some((item) => typeof item !== "string" || !item.trim() || /[\u0000-\u001f\u007f\s]/.test(item))) throw new Error("invalid noProxy");
|
|
1911
|
+
}
|
|
1912
|
+
for (const [scope, route] of Object.entries(v.routing.routes)) {
|
|
1913
|
+
if (!isProxyScope(scope) || !isRoute(route)) throw new Error(`invalid route for ${scope}`);
|
|
1914
|
+
}
|
|
1915
|
+
for (const route of [v.routing.global, ...Object.values(v.routing.routes)]) {
|
|
1916
|
+
if (route.startsWith("profile:") && !ids.has(route.slice(8))) throw new Error(`route references missing profile ${route.slice(8)}`);
|
|
1917
|
+
}
|
|
1918
|
+
if (!Array.isArray(v.persistedScopes) || v.persistedScopes.some((s) => !isProxyScope(s)) || new Set(v.persistedScopes).size !== v.persistedScopes.length) throw new Error("invalid persisted scopes");
|
|
1919
|
+
return structuredClone(v);
|
|
1920
|
+
}
|
|
1921
|
+
function validateSecrets(value) {
|
|
1922
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("secrets root must be an object");
|
|
1923
|
+
for (const [id, record] of Object.entries(value)) {
|
|
1924
|
+
if (!/^[a-z0-9][a-z0-9._-]{0,63}$/i.test(id)) throw new Error("invalid secret profile id");
|
|
1925
|
+
if (!record || typeof record !== "object") throw new Error("invalid secret record");
|
|
1926
|
+
const r = record;
|
|
1927
|
+
if (r.username !== void 0 && (typeof r.username !== "string" || r.username.length > 4096 || /[\r\n\u0000]/.test(r.username))) throw new Error("invalid secret username");
|
|
1928
|
+
if (r.password !== void 0 && (typeof r.password !== "string" || r.password.length > 4096 || /[\r\n\u0000]/.test(r.password))) throw new Error("invalid secret password");
|
|
1929
|
+
}
|
|
1930
|
+
return structuredClone(value);
|
|
1931
|
+
}
|
|
1932
|
+
function validateCredentialConsistency(config, secrets) {
|
|
1933
|
+
const profiles = new Map(config.profiles.map((profile) => [profile.id, profile]));
|
|
1934
|
+
for (const id of Object.keys(secrets)) {
|
|
1935
|
+
if (!profiles.has(id)) throw new Error(`credentials reference missing profile ${id}`);
|
|
1936
|
+
}
|
|
1937
|
+
for (const profile of config.profiles) {
|
|
1938
|
+
const secret = secrets[profile.id];
|
|
1939
|
+
const present = Boolean(secret?.username || secret?.password);
|
|
1940
|
+
if (profile.hasCredentials !== present) throw new Error(`hasCredentials mismatch for profile ${profile.id}`);
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
var PROXY_STORE_VERSION, DEFAULT_CONFIG2, ProxyStoreError, ProxyStoreCorruptError, ProxyRevisionConflictError, ProxyStore;
|
|
1944
|
+
var init_proxy_store = __esm({
|
|
1945
|
+
"src/core/network/proxy-store.ts"() {
|
|
1946
|
+
"use strict";
|
|
1947
|
+
PROXY_STORE_VERSION = 2;
|
|
1948
|
+
DEFAULT_CONFIG2 = {
|
|
1949
|
+
version: PROXY_STORE_VERSION,
|
|
1950
|
+
revision: 0,
|
|
1951
|
+
profiles: [],
|
|
1952
|
+
routing: { global: "inherit", routes: {} },
|
|
1953
|
+
persistedScopes: []
|
|
1954
|
+
};
|
|
1955
|
+
ProxyStoreError = class extends Error {
|
|
1956
|
+
constructor(code, message, details) {
|
|
1957
|
+
super(message);
|
|
1958
|
+
this.code = code;
|
|
1959
|
+
this.details = details;
|
|
1960
|
+
this.name = "ProxyStoreError";
|
|
1961
|
+
}
|
|
1962
|
+
code;
|
|
1963
|
+
details;
|
|
1964
|
+
};
|
|
1965
|
+
ProxyStoreCorruptError = class extends ProxyStoreError {
|
|
1966
|
+
constructor(file, reason, quarantine, lkgAvailable = false) {
|
|
1967
|
+
super("PROXY_STORE_CORRUPT", `Proxy policy store is invalid (${path15.basename(file)}): ${reason}`, { file, quarantine, lkgAvailable });
|
|
1968
|
+
this.name = "ProxyStoreCorruptError";
|
|
1969
|
+
}
|
|
1970
|
+
};
|
|
1971
|
+
ProxyRevisionConflictError = class extends ProxyStoreError {
|
|
1972
|
+
constructor(expected, actual) {
|
|
1973
|
+
super("PROXY_REVISION_CONFLICT", `Proxy policy changed concurrently (expected revision ${expected}, current ${actual})`, { expected, actual });
|
|
1974
|
+
this.name = "ProxyRevisionConflictError";
|
|
1975
|
+
}
|
|
1976
|
+
};
|
|
1977
|
+
ProxyStore = class {
|
|
1978
|
+
configPath;
|
|
1979
|
+
secretsPath;
|
|
1980
|
+
journalPath;
|
|
1981
|
+
lkgPath;
|
|
1982
|
+
lockPath;
|
|
1983
|
+
constructor(instanceRoot) {
|
|
1984
|
+
this.configPath = path15.join(instanceRoot, "proxy.json");
|
|
1985
|
+
this.secretsPath = path15.join(instanceRoot, "proxy-secrets.json");
|
|
1986
|
+
this.journalPath = path15.join(instanceRoot, "proxy-transaction.json");
|
|
1987
|
+
this.lkgPath = path15.join(instanceRoot, "proxy-lkg.json");
|
|
1988
|
+
this.lockPath = path15.join(instanceRoot, "proxy.lock");
|
|
1989
|
+
const lock = this.acquireLock();
|
|
1990
|
+
try {
|
|
1991
|
+
this.cleanupOrphans(instanceRoot);
|
|
1992
|
+
this.recoverJournal();
|
|
1993
|
+
} finally {
|
|
1994
|
+
this.releaseLock(lock);
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
cleanupOrphans(root) {
|
|
1998
|
+
try {
|
|
1999
|
+
const names = fs15.readdirSync(root);
|
|
2000
|
+
for (const name of names) if (/^proxy.*\.tmp$/.test(name)) fs15.rmSync(path15.join(root, name), { force: true });
|
|
2001
|
+
const quarantines = names.filter((n) => n.includes(".corrupt.")).sort().reverse();
|
|
2002
|
+
for (const name of quarantines.slice(3)) fs15.rmSync(path15.join(root, name), { force: true });
|
|
2003
|
+
} catch {
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
quarantine(file) {
|
|
2007
|
+
try {
|
|
2008
|
+
const target = `${file}.corrupt.${Date.now()}`;
|
|
2009
|
+
fs15.copyFileSync(file, target, fs15.constants.COPYFILE_EXCL);
|
|
2010
|
+
fs15.chmodSync(target, 384);
|
|
2011
|
+
return target;
|
|
2012
|
+
} catch {
|
|
2013
|
+
return void 0;
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
readConfigFile(file = this.configPath) {
|
|
2017
|
+
try {
|
|
2018
|
+
return validateConfig(JSON.parse(fs15.readFileSync(file, "utf8")));
|
|
2019
|
+
} catch (error) {
|
|
2020
|
+
if (error.code === "ENOENT" && file === this.configPath) return structuredClone(DEFAULT_CONFIG2);
|
|
2021
|
+
const q = file === this.configPath ? this.quarantine(file) : void 0;
|
|
2022
|
+
throw new ProxyStoreCorruptError(file, error.message, q, fs15.existsSync(this.lkgPath));
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
readSecretsFile() {
|
|
2026
|
+
try {
|
|
2027
|
+
const stat = fs15.statSync(this.secretsPath);
|
|
2028
|
+
if ((stat.mode & 63) !== 0) throw new Error("secret file mode must be 0600");
|
|
2029
|
+
return validateSecrets(JSON.parse(fs15.readFileSync(this.secretsPath, "utf8")));
|
|
2030
|
+
} catch (error) {
|
|
2031
|
+
if (error.code === "ENOENT") return {};
|
|
2032
|
+
const q = this.quarantine(this.secretsPath);
|
|
2033
|
+
throw new ProxyStoreCorruptError(this.secretsPath, error.message, q, fs15.existsSync(this.lkgPath));
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
recoverJournal() {
|
|
2037
|
+
if (!fs15.existsSync(this.journalPath)) return;
|
|
2038
|
+
try {
|
|
2039
|
+
const tx = JSON.parse(fs15.readFileSync(this.journalPath, "utf8"));
|
|
2040
|
+
if (tx.version !== 1) throw new Error("unsupported transaction version");
|
|
2041
|
+
const config = validateConfig(tx.config);
|
|
2042
|
+
const secrets = validateSecrets(tx.secrets);
|
|
2043
|
+
validateCredentialConsistency(config, secrets);
|
|
2044
|
+
atomicWrite(this.configPath, config, 384);
|
|
2045
|
+
atomicWrite(this.secretsPath, secrets, 384);
|
|
2046
|
+
atomicWrite(this.lkgPath, tx, 384);
|
|
2047
|
+
fs15.unlinkSync(this.journalPath);
|
|
2048
|
+
} catch (error) {
|
|
2049
|
+
throw new ProxyStoreCorruptError(this.journalPath, error.message, this.quarantine(this.journalPath), fs15.existsSync(this.lkgPath));
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
load() {
|
|
2053
|
+
const config = this.readConfigFile();
|
|
2054
|
+
try {
|
|
2055
|
+
validateCredentialConsistency(config, this.readSecretsFile());
|
|
2056
|
+
} catch (error) {
|
|
2057
|
+
if (error instanceof ProxyStoreCorruptError) throw error;
|
|
2058
|
+
throw new ProxyStoreCorruptError(this.secretsPath, error.message, this.quarantine(this.secretsPath), fs15.existsSync(this.lkgPath));
|
|
2059
|
+
}
|
|
2060
|
+
return config;
|
|
2061
|
+
}
|
|
2062
|
+
getSecrets() {
|
|
2063
|
+
const config = this.readConfigFile();
|
|
2064
|
+
const secrets = this.readSecretsFile();
|
|
2065
|
+
try {
|
|
2066
|
+
validateCredentialConsistency(config, secrets);
|
|
2067
|
+
} catch (error) {
|
|
2068
|
+
throw new ProxyStoreCorruptError(this.secretsPath, error.message, this.quarantine(this.secretsPath), fs15.existsSync(this.lkgPath));
|
|
2069
|
+
}
|
|
2070
|
+
return secrets;
|
|
2071
|
+
}
|
|
2072
|
+
getSecret(id) {
|
|
2073
|
+
return this.getSecrets()[id];
|
|
2074
|
+
}
|
|
2075
|
+
commit(config, secrets, expectedRevision) {
|
|
2076
|
+
const lock = this.acquireLock();
|
|
2077
|
+
try {
|
|
2078
|
+
this.recoverJournal();
|
|
2079
|
+
const actual = this.load().revision;
|
|
2080
|
+
if (actual !== expectedRevision) throw new ProxyRevisionConflictError(expectedRevision, actual);
|
|
2081
|
+
const next = validateConfig({ ...config, version: PROXY_STORE_VERSION, revision: actual + 1 });
|
|
2082
|
+
const cleanSecrets = validateSecrets(secrets);
|
|
2083
|
+
validateCredentialConsistency(next, cleanSecrets);
|
|
2084
|
+
const tx = { version: 1, config: next, secrets: cleanSecrets };
|
|
2085
|
+
atomicWrite(this.journalPath, tx, 384);
|
|
2086
|
+
atomicWrite(this.configPath, next, 384);
|
|
2087
|
+
atomicWrite(this.secretsPath, cleanSecrets, 384);
|
|
2088
|
+
atomicWrite(this.lkgPath, tx, 384);
|
|
2089
|
+
fs15.unlinkSync(this.journalPath);
|
|
2090
|
+
return next;
|
|
2091
|
+
} finally {
|
|
2092
|
+
this.releaseLock(lock);
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
releaseLock(fd) {
|
|
2096
|
+
try {
|
|
2097
|
+
fs15.closeSync(fd);
|
|
2098
|
+
} catch {
|
|
2099
|
+
}
|
|
2100
|
+
try {
|
|
2101
|
+
fs15.unlinkSync(this.lockPath);
|
|
2102
|
+
} catch {
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
acquireLock() {
|
|
2106
|
+
fs15.mkdirSync(path15.dirname(this.lockPath), { recursive: true, mode: 448 });
|
|
2107
|
+
for (let attempt = 0; attempt < 200; attempt++) {
|
|
2108
|
+
try {
|
|
2109
|
+
const fd = fs15.openSync(this.lockPath, "wx", 384);
|
|
2110
|
+
fs15.writeFileSync(fd, JSON.stringify({ pid: process.pid, createdAt: Date.now() }));
|
|
2111
|
+
fs15.fsyncSync(fd);
|
|
2112
|
+
return fd;
|
|
2113
|
+
} catch (error) {
|
|
2114
|
+
if (error.code !== "EEXIST") throw error;
|
|
2115
|
+
if (this.removeStaleLock()) continue;
|
|
2116
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
throw new ProxyStoreError("PROXY_STORE_BUSY", "Proxy policy store is locked by another process");
|
|
2120
|
+
}
|
|
2121
|
+
removeStaleLock() {
|
|
2122
|
+
try {
|
|
2123
|
+
const lock = JSON.parse(fs15.readFileSync(this.lockPath, "utf8"));
|
|
2124
|
+
const old = !Number.isFinite(lock.createdAt) || Date.now() - Number(lock.createdAt) > 3e4;
|
|
2125
|
+
let dead = false;
|
|
2126
|
+
if (Number.isInteger(lock.pid)) {
|
|
2127
|
+
try {
|
|
2128
|
+
process.kill(lock.pid, 0);
|
|
2129
|
+
} catch (error) {
|
|
2130
|
+
dead = error.code === "ESRCH";
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
if (dead || old && !Number.isInteger(lock.pid)) {
|
|
2134
|
+
fs15.unlinkSync(this.lockPath);
|
|
2135
|
+
return true;
|
|
2136
|
+
}
|
|
2137
|
+
} catch {
|
|
2138
|
+
try {
|
|
2139
|
+
if (Date.now() - fs15.statSync(this.lockPath).mtimeMs > 3e4) {
|
|
2140
|
+
fs15.unlinkSync(this.lockPath);
|
|
2141
|
+
return true;
|
|
2142
|
+
}
|
|
2143
|
+
} catch {
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
return false;
|
|
2147
|
+
}
|
|
2148
|
+
loadLastKnownGood() {
|
|
2149
|
+
try {
|
|
2150
|
+
return validateConfig(JSON.parse(fs15.readFileSync(this.lkgPath, "utf8")).config);
|
|
2151
|
+
} catch {
|
|
2152
|
+
return void 0;
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
};
|
|
2156
|
+
}
|
|
2157
|
+
});
|
|
2158
|
+
|
|
2159
|
+
// src/core/network/proxy-service.ts
|
|
2160
|
+
import { ProxyAgent } from "proxy-agent";
|
|
2161
|
+
import nodeFetch from "node-fetch";
|
|
2162
|
+
import fs16 from "fs";
|
|
2163
|
+
import net2 from "net";
|
|
2164
|
+
import { Readable } from "stream";
|
|
2165
|
+
import debug from "debug";
|
|
2166
|
+
function validateId(value, label) {
|
|
2167
|
+
if (!/^[a-z0-9][a-z0-9._-]{0,63}$/i.test(value)) throw new ProxyValidationError(`Invalid ${label}: ${value}`);
|
|
2168
|
+
}
|
|
2169
|
+
function canonicalHost(input2) {
|
|
2170
|
+
if (!input2 || /[\u0000-\u001f\u007f\s/@?#]/.test(input2)) throw new ProxyValidationError("Proxy host must be a DNS name, IPv4, or bracketed IPv6 without URL components");
|
|
2171
|
+
if (input2.includes(":")) {
|
|
2172
|
+
if (!(input2.startsWith("[") && input2.endsWith("]")) || net2.isIP(input2.slice(1, -1)) !== 6) throw new ProxyValidationError("IPv6 proxy hosts must be bracketed and must not include a port");
|
|
2173
|
+
return input2.slice(1, -1).toLowerCase();
|
|
2174
|
+
}
|
|
2175
|
+
if (net2.isIP(input2) === 4) return input2;
|
|
2176
|
+
const host = input2.toLowerCase().replace(/\.$/, "");
|
|
2177
|
+
if (host.length > 253 || !host.split(".").every((label) => /^(?!-)[a-z0-9-]{1,63}(?<!-)$/.test(label))) throw new ProxyValidationError("Invalid proxy DNS host");
|
|
2178
|
+
return host;
|
|
2179
|
+
}
|
|
2180
|
+
function categoryDefault(scope) {
|
|
2181
|
+
const dot = scope.indexOf(".");
|
|
2182
|
+
return dot > 0 ? `${scope.slice(0, dot)}.default` : void 0;
|
|
2183
|
+
}
|
|
2184
|
+
function routeProfileId(route) {
|
|
2185
|
+
return route.startsWith("profile:") ? route.slice("profile:".length) : void 0;
|
|
2186
|
+
}
|
|
2187
|
+
function proxyUrl(profile, secret) {
|
|
2188
|
+
const host = net2.isIP(profile.host) === 6 ? `[${profile.host}]` : profile.host;
|
|
2189
|
+
const url = new URL(`${profile.protocol}://${host}:${profile.port}`);
|
|
2190
|
+
const username = secret?.username;
|
|
2191
|
+
if (username) url.username = username;
|
|
2192
|
+
if (secret?.password) url.password = secret.password;
|
|
2193
|
+
return url.toString();
|
|
2194
|
+
}
|
|
2195
|
+
function shouldBypassProxy(target, noProxy) {
|
|
2196
|
+
const url = new URL(target);
|
|
2197
|
+
const host = url.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
2198
|
+
const hostAndPort = `${net2.isIP(host) === 6 ? `[${host}]` : host}:${url.port || (url.protocol === "https:" ? "443" : "80")}`;
|
|
2199
|
+
return noProxy.some((raw) => {
|
|
2200
|
+
const pattern = raw.trim().toLowerCase();
|
|
2201
|
+
if (!pattern) return false;
|
|
2202
|
+
if (pattern === "*") return true;
|
|
2203
|
+
if (pattern.replace(/^\[|\]$/g, "") === host) return true;
|
|
2204
|
+
if (pattern.startsWith("[") && pattern.includes("]:") || pattern.includes(":") && net2.isIP(pattern) !== 6) return hostAndPort === pattern;
|
|
2205
|
+
const normalized = pattern.startsWith("*.") ? pattern.slice(1) : pattern;
|
|
2206
|
+
return host === normalized.replace(/^\./, "") || normalized.startsWith(".") && host.endsWith(normalized);
|
|
2207
|
+
});
|
|
2208
|
+
}
|
|
2209
|
+
function safeError(error) {
|
|
2210
|
+
const message = error instanceof Error ? error.message : "Proxy request failed";
|
|
2211
|
+
return new Error(redactNetworkSecrets(message));
|
|
2212
|
+
}
|
|
2213
|
+
function normalizeResponse(response) {
|
|
2214
|
+
if (response instanceof Response) return response;
|
|
2215
|
+
const body = response.body ? Readable.toWeb(response.body) : null;
|
|
2216
|
+
return copyResponse(response, body);
|
|
2217
|
+
}
|
|
2218
|
+
async function fetchThroughAgent(input2, init, agent) {
|
|
2219
|
+
if (typeof ReadableStream !== "undefined" && init?.body instanceof ReadableStream) {
|
|
2220
|
+
throw new ProxyValidationError("Web ReadableStream request bodies are not supported by scoped fetch; use string, URLSearchParams, Blob, or FormData");
|
|
2221
|
+
}
|
|
2222
|
+
if (typeof Request !== "undefined" && input2 instanceof Request) {
|
|
2223
|
+
const inherited = {
|
|
2224
|
+
method: input2.method,
|
|
2225
|
+
headers: Object.fromEntries(input2.headers.entries()),
|
|
2226
|
+
redirect: input2.redirect,
|
|
2227
|
+
signal: input2.signal
|
|
2228
|
+
};
|
|
2229
|
+
if (!init?.body && input2.method !== "GET" && input2.method !== "HEAD") inherited.body = Buffer.from(await input2.clone().arrayBuffer());
|
|
2230
|
+
return nodeFetch(input2.url, { ...inherited, ...init, agent });
|
|
2231
|
+
}
|
|
2232
|
+
return nodeFetch(input2, { ...init, agent });
|
|
2233
|
+
}
|
|
2234
|
+
function copyResponse(response, body) {
|
|
2235
|
+
const noBody = response.status === 101 || response.status === 204 || response.status === 205 || response.status === 304;
|
|
2236
|
+
const copy = new Response(noBody ? null : body, { status: response.status, statusText: response.statusText, headers: response.headers });
|
|
2237
|
+
for (const key of ["url", "redirected", "type"]) {
|
|
2238
|
+
try {
|
|
2239
|
+
Object.defineProperty(copy, key, { value: response[key], configurable: true });
|
|
2240
|
+
} catch {
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
return copy;
|
|
2244
|
+
}
|
|
2245
|
+
var enabledDebug, PROXY_ENV_KEYS, ProxyValidationError, ProxyUnknownScopeError, ProxyProfileInUseError, ProxyRouteTestError, ProxyProfileTestError, ProxyService;
|
|
2246
|
+
var init_proxy_service = __esm({
|
|
2247
|
+
"src/core/network/proxy-service.ts"() {
|
|
2248
|
+
"use strict";
|
|
2249
|
+
init_proxy_types();
|
|
2250
|
+
init_proxy_store();
|
|
2251
|
+
init_proxy_store();
|
|
2252
|
+
init_network_redaction();
|
|
2253
|
+
enabledDebug = debug.disable();
|
|
2254
|
+
debug.enable(sanitizeProxyDebugNamespaces(enabledDebug) ?? "");
|
|
2255
|
+
PROXY_ENV_KEYS = [
|
|
2256
|
+
"HTTP_PROXY",
|
|
2257
|
+
"HTTPS_PROXY",
|
|
2258
|
+
"ALL_PROXY",
|
|
2259
|
+
"NO_PROXY",
|
|
2260
|
+
"http_proxy",
|
|
2261
|
+
"https_proxy",
|
|
2262
|
+
"all_proxy",
|
|
2263
|
+
"no_proxy",
|
|
2264
|
+
"NODE_USE_ENV_PROXY"
|
|
2265
|
+
];
|
|
2266
|
+
ProxyValidationError = class extends Error {
|
|
2267
|
+
constructor(message, code = "PROXY_VALIDATION_ERROR") {
|
|
2268
|
+
super(message);
|
|
2269
|
+
this.code = code;
|
|
2270
|
+
this.name = "ProxyValidationError";
|
|
2271
|
+
}
|
|
2272
|
+
code;
|
|
2273
|
+
};
|
|
2274
|
+
ProxyUnknownScopeError = class extends ProxyValidationError {
|
|
2275
|
+
constructor(scope) {
|
|
2276
|
+
super(`Proxy scope "${scope}" is not registered`, "PROXY_UNKNOWN_SCOPE");
|
|
2277
|
+
}
|
|
2278
|
+
};
|
|
2279
|
+
ProxyProfileInUseError = class extends ProxyValidationError {
|
|
2280
|
+
constructor(id) {
|
|
2281
|
+
super(`Proxy profile "${id}" is still used by routing`, "PROXY_PROFILE_IN_USE");
|
|
2282
|
+
}
|
|
2283
|
+
};
|
|
2284
|
+
ProxyRouteTestError = class extends Error {
|
|
2285
|
+
code = "PROXY_ROUTE_TEST_FAILED";
|
|
2286
|
+
constructor(scope, cause) {
|
|
2287
|
+
const reason = safeError(cause).message;
|
|
2288
|
+
super(`Proxy route test failed for ${scope}; route was not changed: ${reason}`);
|
|
2289
|
+
this.name = "ProxyRouteTestError";
|
|
2290
|
+
}
|
|
2291
|
+
};
|
|
2292
|
+
ProxyProfileTestError = class extends Error {
|
|
2293
|
+
code = "PROXY_PROFILE_TEST_FAILED";
|
|
2294
|
+
constructor(profileId, cause) {
|
|
2295
|
+
const reason = safeError(cause).message;
|
|
2296
|
+
super(`Proxy profile test failed for ${profileId}; profile was not changed: ${reason}`);
|
|
2297
|
+
this.name = "ProxyProfileTestError";
|
|
2298
|
+
}
|
|
2299
|
+
};
|
|
2300
|
+
ProxyService = class {
|
|
2301
|
+
constructor(instanceRoot, retiredLeaseTimeoutMs = 5 * 6e4, allowedNodeEnvironmentFlags = process.allowedNodeEnvironmentFlags) {
|
|
2302
|
+
this.retiredLeaseTimeoutMs = retiredLeaseTimeoutMs;
|
|
2303
|
+
this.allowedNodeEnvironmentFlags = allowedNodeEnvironmentFlags;
|
|
2304
|
+
this.store = new ProxyStore(instanceRoot);
|
|
2305
|
+
try {
|
|
2306
|
+
const config = this.store.load();
|
|
2307
|
+
for (const scope of [...config.persistedScopes, ...Object.keys(config.routing.routes)]) this.scopes.add(scope);
|
|
2308
|
+
} catch {
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
retiredLeaseTimeoutMs;
|
|
2312
|
+
allowedNodeEnvironmentFlags;
|
|
2313
|
+
store;
|
|
2314
|
+
scopes = /* @__PURE__ */ new Set([
|
|
2315
|
+
"channels.default",
|
|
2316
|
+
"channels.telegram",
|
|
2317
|
+
"agents.default",
|
|
2318
|
+
"agents.codex",
|
|
2319
|
+
"agents.cursor",
|
|
2320
|
+
"services.default",
|
|
2321
|
+
"services.npmUpdate",
|
|
2322
|
+
"services.agentRegistry",
|
|
2323
|
+
"services.pluginInstaller",
|
|
2324
|
+
"services.speechDownloads",
|
|
2325
|
+
"plugins.default"
|
|
2326
|
+
]);
|
|
2327
|
+
testers = /* @__PURE__ */ new Map();
|
|
2328
|
+
listeners = /* @__PURE__ */ new Set();
|
|
2329
|
+
transports = /* @__PURE__ */ new Map();
|
|
2330
|
+
facades = /* @__PURE__ */ new Map();
|
|
2331
|
+
mutationQueue = Promise.resolve();
|
|
2332
|
+
policyGeneration = 0;
|
|
2333
|
+
maxTransports = 128;
|
|
2334
|
+
serialize(operation) {
|
|
2335
|
+
const next = this.mutationQueue.then(operation, operation);
|
|
2336
|
+
this.mutationQueue = next.then(() => void 0, () => void 0);
|
|
2337
|
+
return next;
|
|
2338
|
+
}
|
|
2339
|
+
getPolicyGeneration() {
|
|
2340
|
+
return this.policyGeneration;
|
|
2341
|
+
}
|
|
2342
|
+
invalidatePolicyBeforeCommit(config, affectedProfile, changedScope) {
|
|
2343
|
+
const agentAffected = changedScope === "global" || changedScope === "agents.default" || Boolean(changedScope?.startsWith("agents.")) || Boolean(affectedProfile && Object.entries(config.routing.routes).some(([scope, route]) => scope.startsWith("agents.") && route === `profile:${affectedProfile}`)) || Boolean(affectedProfile && config.routing.global === `profile:${affectedProfile}`);
|
|
2344
|
+
if (agentAffected) this.policyGeneration++;
|
|
2345
|
+
return agentAffected;
|
|
2346
|
+
}
|
|
2347
|
+
registerScope(scope) {
|
|
2348
|
+
if (!isProxyScope(scope)) throw new ProxyValidationError(`Invalid proxy scope: ${scope}`);
|
|
2349
|
+
this.scopes.add(scope);
|
|
2350
|
+
this.persistScope(scope);
|
|
2351
|
+
return () => this.scopes.delete(scope);
|
|
2352
|
+
}
|
|
2353
|
+
registerRouteTester(scope, tester) {
|
|
2354
|
+
this.registerScope(scope);
|
|
2355
|
+
this.testers.set(scope, tester);
|
|
2356
|
+
return () => this.testers.delete(scope);
|
|
2357
|
+
}
|
|
2358
|
+
/**
|
|
2359
|
+
* Scope discovery is part of the durable policy schema, not only an in-memory
|
|
2360
|
+
* UI concern. Persisting registrations lets an operator configure a plugin or
|
|
2361
|
+
* agent while it is temporarily disabled and keeps the category list stable
|
|
2362
|
+
* across daemon restarts.
|
|
2363
|
+
*/
|
|
2364
|
+
persistScope(scope) {
|
|
2365
|
+
const config = this.store.load();
|
|
2366
|
+
if (config.persistedScopes.includes(scope)) return;
|
|
2367
|
+
config.persistedScopes = [...config.persistedScopes, scope].sort();
|
|
2368
|
+
this.store.commit(config, this.store.getSecrets(), config.revision);
|
|
2369
|
+
}
|
|
2370
|
+
onRouteChanged(listener) {
|
|
2371
|
+
this.listeners.add(listener);
|
|
2372
|
+
return () => this.listeners.delete(listener);
|
|
2373
|
+
}
|
|
2374
|
+
listProfiles() {
|
|
2375
|
+
return this.store.load().profiles.map((profile) => ({ ...profile }));
|
|
2376
|
+
}
|
|
2377
|
+
getProfile(id) {
|
|
2378
|
+
return this.listProfiles().find((profile) => profile.id === id);
|
|
2379
|
+
}
|
|
2380
|
+
saveProfile(input2) {
|
|
2381
|
+
const config = this.store.load();
|
|
2382
|
+
const secrets = this.store.getSecrets();
|
|
2383
|
+
const { profile, nextSecrets } = this.buildProfile(input2, config, secrets);
|
|
2384
|
+
const affectedScopes = this.scopesUsingProfile(config, input2.id);
|
|
2385
|
+
this.invalidatePolicyBeforeCommit(config, input2.id);
|
|
2386
|
+
config.profiles = [...config.profiles.filter((p) => p.id !== profile.id), profile];
|
|
2387
|
+
config.persistedScopes = [.../* @__PURE__ */ new Set([...config.persistedScopes, ...Object.keys(config.routing.routes)])];
|
|
2388
|
+
this.store.commit(config, nextSecrets, config.revision);
|
|
2389
|
+
this.retireScopes(affectedScopes);
|
|
2390
|
+
return profile;
|
|
2391
|
+
}
|
|
2392
|
+
buildProfile(input2, config, secrets) {
|
|
2393
|
+
validateId(input2.id, "profile id");
|
|
2394
|
+
if (!PROXY_PROTOCOLS.includes(input2.protocol)) throw new ProxyValidationError(`Unsupported proxy protocol: ${input2.protocol}`);
|
|
2395
|
+
if (!Number.isInteger(input2.port) || input2.port < 1 || input2.port > 65535) throw new ProxyValidationError("Proxy port must be between 1 and 65535");
|
|
2396
|
+
const host = canonicalHost(input2.host);
|
|
2397
|
+
const existing = config.profiles.find((p) => p.id === input2.id);
|
|
2398
|
+
const nextSecrets = structuredClone(secrets);
|
|
2399
|
+
if (input2.username !== void 0 || input2.password !== void 0) nextSecrets[input2.id] = { username: input2.username ?? nextSecrets[input2.id]?.username, password: input2.password ?? nextSecrets[input2.id]?.password };
|
|
2400
|
+
const secret = nextSecrets[input2.id];
|
|
2401
|
+
return { profile: {
|
|
2402
|
+
id: input2.id,
|
|
2403
|
+
name: input2.name ?? existing?.name ?? input2.id,
|
|
2404
|
+
protocol: input2.protocol,
|
|
2405
|
+
host,
|
|
2406
|
+
port: input2.port,
|
|
2407
|
+
noProxy: input2.noProxy ?? existing?.noProxy ?? ["localhost", "127.0.0.1", "::1"],
|
|
2408
|
+
failClosed: input2.failClosed ?? existing?.failClosed ?? true,
|
|
2409
|
+
hasCredentials: Boolean(secret?.username || secret?.password)
|
|
2410
|
+
}, nextSecrets };
|
|
2411
|
+
}
|
|
2412
|
+
async saveProfileSafely(input2, expectedRevision) {
|
|
2413
|
+
return this.serialize(async () => {
|
|
2414
|
+
const config = this.store.load();
|
|
2415
|
+
const secrets = this.store.getSecrets();
|
|
2416
|
+
if (expectedRevision !== void 0 && config.revision !== expectedRevision) throw new ProxyRevisionConflictError(expectedRevision, config.revision);
|
|
2417
|
+
const { profile: candidate, nextSecrets } = this.buildProfile(input2, config, secrets);
|
|
2418
|
+
const affectedScopes = this.scopesUsingProfile(config, input2.id);
|
|
2419
|
+
const candidateSecret = nextSecrets[input2.id];
|
|
2420
|
+
for (const [scope, tester] of this.testers) {
|
|
2421
|
+
if (this.resolve(scope).profile?.id === input2.id) {
|
|
2422
|
+
const fetcher = this.createProfileFetch(candidate, candidateSecret);
|
|
2423
|
+
try {
|
|
2424
|
+
await tester(fetcher);
|
|
2425
|
+
} catch (error) {
|
|
2426
|
+
throw new ProxyProfileTestError(input2.id, error);
|
|
2427
|
+
} finally {
|
|
2428
|
+
fetcher.destroy?.();
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
this.invalidatePolicyBeforeCommit(config, input2.id);
|
|
2433
|
+
config.profiles = [...config.profiles.filter((p) => p.id !== candidate.id), candidate];
|
|
2434
|
+
const saved = this.store.commit(config, nextSecrets, config.revision).profiles.find((p) => p.id === candidate.id);
|
|
2435
|
+
this.retireScopes(affectedScopes);
|
|
2436
|
+
return saved;
|
|
2437
|
+
});
|
|
2438
|
+
}
|
|
2439
|
+
/** Import a conventional proxy env file without ever returning or logging its credential URL. */
|
|
2440
|
+
importEnvFile(id, envFile, name) {
|
|
2441
|
+
return this.saveProfile(this.parseEnvFile(id, envFile, name));
|
|
2442
|
+
}
|
|
2443
|
+
parseEnvFile(id, envFile, name) {
|
|
2444
|
+
const stat = fs16.statSync(envFile);
|
|
2445
|
+
if (!stat.isFile()) throw new Error("Proxy env path is not a file");
|
|
2446
|
+
if ((stat.mode & 63) !== 0) throw new Error("Proxy env file must have mode 0600");
|
|
2447
|
+
const values = {};
|
|
2448
|
+
for (const rawLine of fs16.readFileSync(envFile, "utf8").split(/\r?\n/)) {
|
|
2449
|
+
const line = rawLine.trim();
|
|
2450
|
+
if (!line || line.startsWith("#")) continue;
|
|
2451
|
+
const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(line);
|
|
2452
|
+
if (!match) continue;
|
|
2453
|
+
let value = match[2].trim();
|
|
2454
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
2455
|
+
values[match[1]] = value;
|
|
2456
|
+
}
|
|
2457
|
+
const httpUrl = values.HTTP_PROXY ?? values.http_proxy;
|
|
2458
|
+
const httpsUrl = values.HTTPS_PROXY ?? values.https_proxy;
|
|
2459
|
+
if (httpUrl && httpsUrl && httpUrl !== httpsUrl) throw new ProxyValidationError("HTTP_PROXY and HTTPS_PROXY differ; use one canonical proxy URL or create separate profiles");
|
|
2460
|
+
const rawUrl = httpsUrl ?? httpUrl ?? values.ALL_PROXY ?? values.all_proxy;
|
|
2461
|
+
if (!rawUrl) throw new Error("No proxy URL found in env file");
|
|
2462
|
+
let url;
|
|
2463
|
+
try {
|
|
2464
|
+
url = new URL(rawUrl);
|
|
2465
|
+
} catch {
|
|
2466
|
+
throw new ProxyValidationError("Proxy env contains an invalid URL");
|
|
2467
|
+
}
|
|
2468
|
+
const protocol = url.protocol.slice(0, -1);
|
|
2469
|
+
if (!PROXY_PROTOCOLS.includes(protocol)) throw new ProxyValidationError(`Unsupported proxy protocol: ${protocol}`);
|
|
2470
|
+
if (url.pathname && url.pathname !== "/" || url.search || url.hash) throw new ProxyValidationError("Proxy URL must not contain path, query, or fragment");
|
|
2471
|
+
const noProxy = values.NO_PROXY ?? values.no_proxy;
|
|
2472
|
+
return {
|
|
2473
|
+
id,
|
|
2474
|
+
name,
|
|
2475
|
+
protocol,
|
|
2476
|
+
// WHATWG URL keeps IPv6 hostnames bracketed. Preserve that canonical
|
|
2477
|
+
// input form here; buildProfile performs the single host normalization.
|
|
2478
|
+
host: url.hostname,
|
|
2479
|
+
port: Number(url.port || (protocol.startsWith("socks") ? 1080 : protocol === "https" ? 443 : 80)),
|
|
2480
|
+
username: decodeURIComponent(url.username),
|
|
2481
|
+
password: decodeURIComponent(url.password),
|
|
2482
|
+
noProxy: noProxy ? noProxy.split(",").map((item) => item.trim()).filter(Boolean) : void 0
|
|
2483
|
+
};
|
|
2484
|
+
}
|
|
2485
|
+
async importEnvFileSafely(id, envFile, name, expectedRevision) {
|
|
2486
|
+
return this.saveProfileSafely(this.parseEnvFile(id, envFile, name), expectedRevision);
|
|
2487
|
+
}
|
|
2488
|
+
async deleteProfile(id, expectedRevision) {
|
|
2489
|
+
return this.serialize(async () => {
|
|
2490
|
+
const config = this.store.load();
|
|
2491
|
+
const secrets = this.store.getSecrets();
|
|
2492
|
+
if (expectedRevision !== void 0 && config.revision !== expectedRevision) throw new ProxyRevisionConflictError(expectedRevision, config.revision);
|
|
2493
|
+
if (!config.profiles.some((p) => p.id === id)) throw new ProxyValidationError(`Proxy profile "${id}" does not exist`);
|
|
2494
|
+
if (Object.values(config.routing.routes).includes(`profile:${id}`) || config.routing.global === `profile:${id}`) throw new ProxyProfileInUseError(id);
|
|
2495
|
+
config.profiles = config.profiles.filter((p) => p.id !== id);
|
|
2496
|
+
delete secrets[id];
|
|
2497
|
+
this.store.commit(config, secrets, config.revision);
|
|
2498
|
+
});
|
|
2499
|
+
}
|
|
2500
|
+
resolve(scope, routeOverride) {
|
|
2501
|
+
const config = this.store.load();
|
|
2502
|
+
return this.resolveFromConfig(scope, config, routeOverride);
|
|
2503
|
+
}
|
|
2504
|
+
resolveFromConfig(scope, config, routeOverride) {
|
|
2505
|
+
const exact = routeOverride ?? config.routing.routes[scope];
|
|
2506
|
+
const category = categoryDefault(scope);
|
|
2507
|
+
const route = exact ?? (category ? config.routing.routes[category] : void 0) ?? config.routing.global;
|
|
2508
|
+
const resolvedFrom = routeOverride ? "candidate" : exact ? scope : category && config.routing.routes[category] ? category : "global";
|
|
2509
|
+
const id = routeProfileId(route);
|
|
2510
|
+
const profile = id ? config.profiles.find((item) => item.id === id) : void 0;
|
|
2511
|
+
if (id && !profile) throw new Error(`Proxy profile "${id}" does not exist`);
|
|
2512
|
+
return { scope, route, resolvedFrom, profile };
|
|
2513
|
+
}
|
|
2514
|
+
async setRoute(scope, route, expectedRevision) {
|
|
2515
|
+
return this.serialize(async () => {
|
|
2516
|
+
if (scope !== "global" && !this.getKnownScopes().includes(scope)) throw new ProxyUnknownScopeError(scope);
|
|
2517
|
+
this.validateRoute(route);
|
|
2518
|
+
const config = this.store.load();
|
|
2519
|
+
if (expectedRevision !== void 0 && config.revision !== expectedRevision) throw new ProxyRevisionConflictError(expectedRevision, config.revision);
|
|
2520
|
+
const candidate = structuredClone(config);
|
|
2521
|
+
if (scope === "global") candidate.routing.global = route;
|
|
2522
|
+
else candidate.routing.routes[scope] = route;
|
|
2523
|
+
const affectedScopes = this.changedResolutionScopes(config, candidate);
|
|
2524
|
+
try {
|
|
2525
|
+
await this.testCandidateRoutes(config, candidate);
|
|
2526
|
+
} catch (error) {
|
|
2527
|
+
throw new ProxyRouteTestError(scope, error);
|
|
2528
|
+
}
|
|
2529
|
+
const affectsAgents = this.invalidatePolicyBeforeCommit(config, void 0, scope);
|
|
2530
|
+
candidate.persistedScopes = [.../* @__PURE__ */ new Set([...candidate.persistedScopes, ...scope === "global" ? [] : [scope]])];
|
|
2531
|
+
this.store.commit(candidate, this.store.getSecrets(), config.revision);
|
|
2532
|
+
this.retireScopes(affectedScopes);
|
|
2533
|
+
for (const listener of this.listeners) await listener(scope, route);
|
|
2534
|
+
return {
|
|
2535
|
+
scope,
|
|
2536
|
+
route,
|
|
2537
|
+
warmPoolInvalidated: affectsAgents,
|
|
2538
|
+
activeAgentProcessesUnaffected: affectsAgents
|
|
2539
|
+
};
|
|
2540
|
+
});
|
|
2541
|
+
}
|
|
2542
|
+
async clearRoute(scope, expectedRevision) {
|
|
2543
|
+
return this.serialize(async () => {
|
|
2544
|
+
const config = this.store.load();
|
|
2545
|
+
if (expectedRevision !== void 0 && config.revision !== expectedRevision) throw new ProxyRevisionConflictError(expectedRevision, config.revision);
|
|
2546
|
+
const candidate = structuredClone(config);
|
|
2547
|
+
if (scope === "global") candidate.routing.global = "inherit";
|
|
2548
|
+
else {
|
|
2549
|
+
if (!this.getKnownScopes().includes(scope)) throw new ProxyUnknownScopeError(scope);
|
|
2550
|
+
delete candidate.routing.routes[scope];
|
|
2551
|
+
}
|
|
2552
|
+
const affectedScopes = this.changedResolutionScopes(config, candidate);
|
|
2553
|
+
try {
|
|
2554
|
+
await this.testCandidateRoutes(config, candidate);
|
|
2555
|
+
} catch (error) {
|
|
2556
|
+
throw new ProxyRouteTestError(scope, error);
|
|
2557
|
+
}
|
|
2558
|
+
this.invalidatePolicyBeforeCommit(config, void 0, scope);
|
|
2559
|
+
this.store.commit(candidate, this.store.getSecrets(), config.revision);
|
|
2560
|
+
this.retireScopes(affectedScopes);
|
|
2561
|
+
for (const listener of this.listeners) await listener(scope, this.resolve(scope).route);
|
|
2562
|
+
});
|
|
2563
|
+
}
|
|
2564
|
+
getKnownScopes() {
|
|
2565
|
+
const config = this.store.load();
|
|
2566
|
+
return [.../* @__PURE__ */ new Set([...this.scopes, ...config.persistedScopes, ...Object.keys(config.routing.routes)])].sort();
|
|
2567
|
+
}
|
|
2568
|
+
createFetch(scope, routeOverride) {
|
|
2569
|
+
if (routeOverride) return this.createTransport(scope, routeOverride);
|
|
2570
|
+
if (!this.getKnownScopes().includes(scope)) throw new ProxyUnknownScopeError(scope);
|
|
2571
|
+
const existing = this.facades.get(scope);
|
|
2572
|
+
if (existing) return existing;
|
|
2573
|
+
const facade = (async (input2, init) => {
|
|
2574
|
+
const entry = this.acquireTransport(scope);
|
|
2575
|
+
try {
|
|
2576
|
+
const response = await entry.fetcher(input2, init);
|
|
2577
|
+
return this.releaseWithResponse(response, entry);
|
|
2578
|
+
} catch (error) {
|
|
2579
|
+
this.releaseTransport(entry);
|
|
2580
|
+
throw error;
|
|
2581
|
+
}
|
|
2582
|
+
});
|
|
2583
|
+
this.facades.set(scope, facade);
|
|
2584
|
+
return facade;
|
|
2585
|
+
}
|
|
2586
|
+
createTransport(scope, routeOverride) {
|
|
2587
|
+
const resolution = this.resolve(scope, routeOverride);
|
|
2588
|
+
const cacheKey = JSON.stringify({
|
|
2589
|
+
route: resolution.route,
|
|
2590
|
+
profile: resolution.profile,
|
|
2591
|
+
inherited: resolution.route === "inherit" ? [process.env.HTTP_PROXY, process.env.HTTPS_PROXY, process.env.ALL_PROXY, process.env.NO_PROXY] : void 0
|
|
2592
|
+
});
|
|
2593
|
+
if (!routeOverride) {
|
|
2594
|
+
const cached = this.transports.get(scope);
|
|
2595
|
+
if (cached?.key === cacheKey) return cached.fetcher;
|
|
2596
|
+
}
|
|
2597
|
+
if (resolution.route === "direct") {
|
|
2598
|
+
const fetcher2 = this.createDirectFetch();
|
|
2599
|
+
if (!routeOverride) this.cacheTransport(scope, cacheKey, fetcher2);
|
|
2600
|
+
return fetcher2;
|
|
2601
|
+
}
|
|
2602
|
+
let agent;
|
|
2603
|
+
if (resolution.route === "inherit") {
|
|
2604
|
+
const hasInheritedProxy = process.env.HTTPS_PROXY ?? process.env.https_proxy ?? process.env.HTTP_PROXY ?? process.env.http_proxy ?? process.env.ALL_PROXY ?? process.env.all_proxy;
|
|
2605
|
+
if (!hasInheritedProxy) {
|
|
2606
|
+
const fetcher2 = this.createDirectFetch();
|
|
2607
|
+
if (!routeOverride) this.cacheTransport(scope, cacheKey, fetcher2);
|
|
2608
|
+
return fetcher2;
|
|
2609
|
+
}
|
|
2610
|
+
agent = new ProxyAgent();
|
|
2611
|
+
} else if (resolution.profile) {
|
|
2612
|
+
const fetcher2 = this.createProfileFetch(resolution.profile, this.store.getSecret(resolution.profile.id));
|
|
2613
|
+
if (!routeOverride) this.cacheTransport(scope, cacheKey, fetcher2);
|
|
2614
|
+
return fetcher2;
|
|
2615
|
+
} else {
|
|
2616
|
+
return globalThis.fetch.bind(globalThis);
|
|
2617
|
+
}
|
|
2618
|
+
const fetcher = (async (input2, init) => {
|
|
2619
|
+
try {
|
|
2620
|
+
const response = await fetchThroughAgent(input2, init, agent);
|
|
2621
|
+
return normalizeResponse(response);
|
|
2622
|
+
} catch (error) {
|
|
2623
|
+
if (resolution.profile?.failClosed !== false) throw safeError(error);
|
|
2624
|
+
return globalThis.fetch(input2, init);
|
|
2625
|
+
}
|
|
2626
|
+
});
|
|
2627
|
+
fetcher.destroy = () => agent.destroy?.();
|
|
2628
|
+
if (!routeOverride) this.cacheTransport(scope, cacheKey, fetcher);
|
|
2629
|
+
return fetcher;
|
|
2630
|
+
}
|
|
2631
|
+
cacheTransport(scope, key, fetcher) {
|
|
2632
|
+
const old = this.transports.get(scope);
|
|
2633
|
+
if (old && old.fetcher !== fetcher) this.retireTransport(old);
|
|
2634
|
+
this.transports.set(scope, { key, fetcher, active: 0, retired: false, destroyed: false, leases: /* @__PURE__ */ new Set() });
|
|
2635
|
+
while (this.transports.size > this.maxTransports) {
|
|
2636
|
+
const oldest = this.transports.keys().next().value;
|
|
2637
|
+
if (!oldest) break;
|
|
2638
|
+
const entry = this.transports.get(oldest);
|
|
2639
|
+
if (entry) this.retireTransport(entry);
|
|
2640
|
+
this.transports.delete(oldest);
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
buildAgentEnv(agentName, inherited) {
|
|
2644
|
+
return this.buildChildEnv(`agents.${agentName}`, inherited);
|
|
2645
|
+
}
|
|
2646
|
+
buildChildEnv(scope, inherited) {
|
|
2647
|
+
const resolution = this.resolve(scope);
|
|
2648
|
+
const env = { ...inherited };
|
|
2649
|
+
if (env.DEBUG) {
|
|
2650
|
+
const safeDebug = sanitizeProxyDebugNamespaces(env.DEBUG);
|
|
2651
|
+
if (safeDebug) env.DEBUG = safeDebug;
|
|
2652
|
+
else delete env.DEBUG;
|
|
2653
|
+
}
|
|
2654
|
+
if (resolution.route === "inherit") return env;
|
|
2655
|
+
for (const key of PROXY_ENV_KEYS) delete env[key];
|
|
2656
|
+
if (env.NODE_OPTIONS) {
|
|
2657
|
+
env.NODE_OPTIONS = env.NODE_OPTIONS.split(/\s+/).filter((flag) => flag !== "--use-env-proxy").join(" ");
|
|
2658
|
+
if (!env.NODE_OPTIONS) delete env.NODE_OPTIONS;
|
|
2659
|
+
}
|
|
2660
|
+
if (resolution.route === "direct") {
|
|
2661
|
+
env.NODE_USE_ENV_PROXY = "0";
|
|
2662
|
+
return env;
|
|
2663
|
+
}
|
|
2664
|
+
const profile = resolution.profile;
|
|
2665
|
+
const url = proxyUrl(profile, this.store.getSecret(profile.id));
|
|
2666
|
+
const noProxy = profile.noProxy.join(",");
|
|
2667
|
+
if (profile.protocol === "http" || profile.protocol === "https") {
|
|
2668
|
+
env.HTTP_PROXY = env.http_proxy = url;
|
|
2669
|
+
env.HTTPS_PROXY = env.https_proxy = url;
|
|
2670
|
+
env.NODE_USE_ENV_PROXY = "1";
|
|
2671
|
+
if (this.allowedNodeEnvironmentFlags.has("--use-env-proxy")) {
|
|
2672
|
+
env.NODE_OPTIONS = [env.NODE_OPTIONS, "--use-env-proxy"].filter(Boolean).join(" ");
|
|
2673
|
+
}
|
|
2674
|
+
} else {
|
|
2675
|
+
env.ALL_PROXY = env.all_proxy = url;
|
|
2676
|
+
}
|
|
2677
|
+
env.NO_PROXY = env.no_proxy = noProxy;
|
|
2678
|
+
return env;
|
|
2679
|
+
}
|
|
2680
|
+
async test(scope, targetUrl = "https://api.ipify.org?format=json") {
|
|
2681
|
+
if (!this.getKnownScopes().includes(scope)) throw new ProxyUnknownScopeError(scope);
|
|
2682
|
+
let response;
|
|
2683
|
+
try {
|
|
2684
|
+
response = await this.createFetch(scope)(targetUrl, { signal: AbortSignal.timeout(1e4) });
|
|
2685
|
+
return { ok: response.ok, status: response.status };
|
|
2686
|
+
} catch (error) {
|
|
2687
|
+
return { ok: false, error: error instanceof Error ? redactNetworkSecrets(error.message) : "Proxy test failed" };
|
|
2688
|
+
} finally {
|
|
2689
|
+
try {
|
|
2690
|
+
await response?.body?.cancel();
|
|
2691
|
+
} catch {
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
async testProfile(id, targetUrl) {
|
|
2696
|
+
if (!this.getProfile(id)) return { ok: false, error: `Proxy profile "${id}" does not exist` };
|
|
2697
|
+
let fetcher;
|
|
2698
|
+
let response;
|
|
2699
|
+
try {
|
|
2700
|
+
fetcher = this.createTransport("services.proxyTest", `profile:${id}`);
|
|
2701
|
+
response = await fetcher(targetUrl ?? "https://api.ipify.org?format=json", {
|
|
2702
|
+
signal: AbortSignal.timeout(1e4)
|
|
2703
|
+
});
|
|
2704
|
+
return { ok: response.ok, status: response.status };
|
|
2705
|
+
} catch (error) {
|
|
2706
|
+
return { ok: false, error: error instanceof Error ? redactNetworkSecrets(error.message) : "Proxy test failed" };
|
|
2707
|
+
} finally {
|
|
2708
|
+
try {
|
|
2709
|
+
await response?.body?.cancel();
|
|
2710
|
+
} catch {
|
|
2711
|
+
}
|
|
2712
|
+
;
|
|
2713
|
+
fetcher?.destroy?.();
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
status() {
|
|
2717
|
+
const config = this.store.load();
|
|
2718
|
+
const scopes = this.getKnownScopes();
|
|
2719
|
+
const daemonVariables = PROXY_ENV_KEYS.filter((key) => {
|
|
2720
|
+
const value = process.env[key];
|
|
2721
|
+
return !["NO_PROXY", "no_proxy", "NODE_USE_ENV_PROXY"].includes(key) && value !== void 0 && value !== "";
|
|
2722
|
+
});
|
|
2723
|
+
return {
|
|
2724
|
+
revision: config.revision,
|
|
2725
|
+
profiles: this.listProfiles(),
|
|
2726
|
+
routing: config.routing,
|
|
2727
|
+
scopes,
|
|
2728
|
+
diagnostics: scopes.map((scope) => {
|
|
2729
|
+
const resolution = this.resolve(scope);
|
|
2730
|
+
const child = scope.startsWith("agents.") || scope.startsWith("services.");
|
|
2731
|
+
const socks = child && Boolean(resolution.profile?.protocol.startsWith("socks"));
|
|
2732
|
+
return {
|
|
2733
|
+
scope,
|
|
2734
|
+
route: resolution.route,
|
|
2735
|
+
resolvedFrom: resolution.resolvedFrom,
|
|
2736
|
+
childProcessSupport: child ? socks ? "best-effort-socks-env" : "native-env" : "not-applicable",
|
|
2737
|
+
warning: socks ? "SOCKS support depends on the child process honoring ALL_PROXY; npm and arbitrary ACP clients may require HTTP/HTTPS profiles." : void 0
|
|
2738
|
+
};
|
|
2739
|
+
}),
|
|
2740
|
+
environment: {
|
|
2741
|
+
daemonWideProxyActive: daemonVariables.length > 0,
|
|
2742
|
+
compatibilityMode: daemonVariables.length > 0,
|
|
2743
|
+
variables: daemonVariables,
|
|
2744
|
+
message: daemonVariables.length > 0 ? "Daemon-wide proxy environment is active. Scoped direct transports remain isolated, but category isolation is in compatibility mode until legacy wrapper/drop-in proxy injection is removed." : "No daemon-wide proxy environment detected; native scoped routing is authoritative."
|
|
2745
|
+
}
|
|
2746
|
+
};
|
|
2747
|
+
}
|
|
2748
|
+
validateRoute(route) {
|
|
2749
|
+
if (route === "direct" || route === "inherit") return;
|
|
2750
|
+
const id = routeProfileId(route);
|
|
2751
|
+
if (!id || !this.getProfile(id)) throw new ProxyValidationError(`Proxy profile "${id ?? route}" does not exist`);
|
|
2752
|
+
}
|
|
2753
|
+
async testCandidateRoutes(current, candidate) {
|
|
2754
|
+
for (const [scope, tester] of this.testers) {
|
|
2755
|
+
const before = this.resolveFromConfig(scope, current);
|
|
2756
|
+
const after = this.resolveFromConfig(scope, candidate);
|
|
2757
|
+
if (before.route !== after.route || before.profile?.id !== after.profile?.id) {
|
|
2758
|
+
const fetcher = this.createTransport(scope, after.route);
|
|
2759
|
+
try {
|
|
2760
|
+
await tester(fetcher);
|
|
2761
|
+
} finally {
|
|
2762
|
+
fetcher.destroy?.();
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
createProfileFetch(profile, secret) {
|
|
2768
|
+
const url = proxyUrl(profile, secret);
|
|
2769
|
+
const agent = new ProxyAgent({
|
|
2770
|
+
getProxyForUrl: (target) => shouldBypassProxy(target, profile.noProxy) ? "" : url
|
|
2771
|
+
});
|
|
2772
|
+
const directFallback = profile.failClosed === false ? this.createDirectFetch() : void 0;
|
|
2773
|
+
const fetcher = (async (input2, init) => {
|
|
2774
|
+
try {
|
|
2775
|
+
const response = await fetchThroughAgent(input2, init, agent);
|
|
2776
|
+
return normalizeResponse(response);
|
|
2777
|
+
} catch (error) {
|
|
2778
|
+
if (profile.failClosed !== false) throw safeError(error);
|
|
2779
|
+
return directFallback(input2, init);
|
|
2780
|
+
}
|
|
2781
|
+
});
|
|
2782
|
+
fetcher.destroy = () => {
|
|
2783
|
+
agent.destroy?.();
|
|
2784
|
+
directFallback?.destroy?.();
|
|
2785
|
+
};
|
|
2786
|
+
return fetcher;
|
|
2787
|
+
}
|
|
2788
|
+
/** Direct transport that ignores daemon-wide env proxy flags without global mutation. */
|
|
2789
|
+
createDirectFetch() {
|
|
2790
|
+
const agent = new ProxyAgent({ getProxyForUrl: () => "" });
|
|
2791
|
+
const fetcher = (async (input2, init) => {
|
|
2792
|
+
const response = await fetchThroughAgent(input2, init, agent);
|
|
2793
|
+
return normalizeResponse(response);
|
|
2794
|
+
});
|
|
2795
|
+
fetcher.destroy = () => agent.destroy?.();
|
|
2796
|
+
return fetcher;
|
|
2797
|
+
}
|
|
2798
|
+
acquireTransport(scope) {
|
|
2799
|
+
this.createTransport(scope);
|
|
2800
|
+
const entry = this.transports.get(scope);
|
|
2801
|
+
entry.active++;
|
|
2802
|
+
return entry;
|
|
2803
|
+
}
|
|
2804
|
+
releaseTransport(entry) {
|
|
2805
|
+
entry.active = Math.max(0, entry.active - 1);
|
|
2806
|
+
if (entry.retired && entry.active === 0) this.destroyTransport(entry);
|
|
2807
|
+
}
|
|
2808
|
+
retireTransport(entry) {
|
|
2809
|
+
entry.retired = true;
|
|
2810
|
+
if (entry.active === 0) {
|
|
2811
|
+
this.destroyTransport(entry);
|
|
2812
|
+
return;
|
|
2813
|
+
}
|
|
2814
|
+
if (!entry.retirementTimer) {
|
|
2815
|
+
entry.retirementTimer = setTimeout(() => {
|
|
2816
|
+
const reason = new Error("Retired proxy transport response exceeded its maximum lease");
|
|
2817
|
+
for (const abort of [...entry.leases]) abort(reason);
|
|
2818
|
+
this.destroyTransport(entry);
|
|
2819
|
+
}, this.retiredLeaseTimeoutMs);
|
|
2820
|
+
entry.retirementTimer.unref?.();
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
destroyTransport(entry) {
|
|
2824
|
+
if (entry.destroyed) return;
|
|
2825
|
+
entry.destroyed = true;
|
|
2826
|
+
if (entry.retirementTimer) clearTimeout(entry.retirementTimer);
|
|
2827
|
+
entry.fetcher.destroy?.();
|
|
2828
|
+
}
|
|
2829
|
+
retireScopes(scopes) {
|
|
2830
|
+
for (const scope of scopes) {
|
|
2831
|
+
const entry = this.transports.get(scope);
|
|
2832
|
+
if (!entry) continue;
|
|
2833
|
+
this.transports.delete(scope);
|
|
2834
|
+
this.retireTransport(entry);
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
scopesUsingProfile(config, profileId) {
|
|
2838
|
+
return this.allScopes(config).filter((scope) => this.resolveFromConfig(scope, config).profile?.id === profileId);
|
|
2839
|
+
}
|
|
2840
|
+
changedResolutionScopes(before, after) {
|
|
2841
|
+
return this.allScopes(before, after).filter((scope) => {
|
|
2842
|
+
const a = this.resolveFromConfig(scope, before);
|
|
2843
|
+
const b = this.resolveFromConfig(scope, after);
|
|
2844
|
+
return JSON.stringify([a.route, a.profile]) !== JSON.stringify([b.route, b.profile]);
|
|
2845
|
+
});
|
|
2846
|
+
}
|
|
2847
|
+
allScopes(...configs) {
|
|
2848
|
+
return [.../* @__PURE__ */ new Set([...this.scopes, ...configs.flatMap((config) => [...config.persistedScopes, ...Object.keys(config.routing.routes)])])];
|
|
2849
|
+
}
|
|
2850
|
+
releaseWithResponse(response, entry) {
|
|
2851
|
+
const release = () => this.releaseTransport(entry);
|
|
2852
|
+
if (!response.body) {
|
|
2853
|
+
release();
|
|
2854
|
+
return response;
|
|
2855
|
+
}
|
|
2856
|
+
const reader = response.body.getReader();
|
|
2857
|
+
let released = false;
|
|
2858
|
+
let bodyController;
|
|
2859
|
+
const done = () => {
|
|
2860
|
+
if (!released) {
|
|
2861
|
+
released = true;
|
|
2862
|
+
entry.leases.delete(abort);
|
|
2863
|
+
release();
|
|
2864
|
+
}
|
|
2865
|
+
};
|
|
2866
|
+
const abort = (reason) => {
|
|
2867
|
+
if (released) return;
|
|
2868
|
+
try {
|
|
2869
|
+
bodyController?.error(reason);
|
|
2870
|
+
} catch {
|
|
2871
|
+
}
|
|
2872
|
+
void reader.cancel(reason).then(done, done);
|
|
2873
|
+
};
|
|
2874
|
+
entry.leases.add(abort);
|
|
2875
|
+
if (entry.destroyed) abort(new Error("Retired proxy transport response exceeded its maximum lease"));
|
|
2876
|
+
const body = new ReadableStream({
|
|
2877
|
+
start(controller) {
|
|
2878
|
+
bodyController = controller;
|
|
2879
|
+
},
|
|
2880
|
+
async pull(controller) {
|
|
2881
|
+
try {
|
|
2882
|
+
const chunk = await reader.read();
|
|
2883
|
+
if (chunk.done) {
|
|
2884
|
+
done();
|
|
2885
|
+
controller.close();
|
|
2886
|
+
} else controller.enqueue(chunk.value);
|
|
2887
|
+
} catch (error) {
|
|
2888
|
+
done();
|
|
2889
|
+
controller.error(error);
|
|
2890
|
+
}
|
|
2891
|
+
},
|
|
2892
|
+
async cancel(reason) {
|
|
2893
|
+
try {
|
|
2894
|
+
await reader.cancel(reason);
|
|
2895
|
+
} finally {
|
|
2896
|
+
done();
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
});
|
|
2900
|
+
return copyResponse(response, body);
|
|
2901
|
+
}
|
|
2902
|
+
};
|
|
2903
|
+
}
|
|
2904
|
+
});
|
|
2905
|
+
|
|
1696
2906
|
// src/core/doctor/checks/config.ts
|
|
1697
|
-
import * as
|
|
2907
|
+
import * as fs17 from "fs";
|
|
1698
2908
|
var configCheck;
|
|
1699
2909
|
var init_config2 = __esm({
|
|
1700
2910
|
"src/core/doctor/checks/config.ts"() {
|
|
@@ -1706,14 +2916,14 @@ var init_config2 = __esm({
|
|
|
1706
2916
|
order: 1,
|
|
1707
2917
|
async run(ctx) {
|
|
1708
2918
|
const results = [];
|
|
1709
|
-
if (!
|
|
2919
|
+
if (!fs17.existsSync(ctx.configPath)) {
|
|
1710
2920
|
results.push({ status: "fail", message: "Config file not found" });
|
|
1711
2921
|
return results;
|
|
1712
2922
|
}
|
|
1713
2923
|
results.push({ status: "pass", message: "Config file exists" });
|
|
1714
2924
|
let raw;
|
|
1715
2925
|
try {
|
|
1716
|
-
raw = JSON.parse(
|
|
2926
|
+
raw = JSON.parse(fs17.readFileSync(ctx.configPath, "utf-8"));
|
|
1717
2927
|
} catch (err) {
|
|
1718
2928
|
results.push({
|
|
1719
2929
|
status: "fail",
|
|
@@ -1732,7 +2942,7 @@ var init_config2 = __esm({
|
|
|
1732
2942
|
fixRisk: "safe",
|
|
1733
2943
|
fix: async () => {
|
|
1734
2944
|
applyMigrations(raw);
|
|
1735
|
-
|
|
2945
|
+
fs17.writeFileSync(ctx.configPath, JSON.stringify(raw, null, 2));
|
|
1736
2946
|
return { success: true, message: "applied migrations" };
|
|
1737
2947
|
}
|
|
1738
2948
|
});
|
|
@@ -1756,8 +2966,8 @@ var init_config2 = __esm({
|
|
|
1756
2966
|
|
|
1757
2967
|
// src/core/doctor/checks/agents.ts
|
|
1758
2968
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
1759
|
-
import * as
|
|
1760
|
-
import * as
|
|
2969
|
+
import * as fs18 from "fs";
|
|
2970
|
+
import * as path17 from "path";
|
|
1761
2971
|
function commandExists2(cmd) {
|
|
1762
2972
|
try {
|
|
1763
2973
|
execFileSync3("which", [cmd], { stdio: "pipe" });
|
|
@@ -1766,9 +2976,9 @@ function commandExists2(cmd) {
|
|
|
1766
2976
|
}
|
|
1767
2977
|
let dir = process.cwd();
|
|
1768
2978
|
while (true) {
|
|
1769
|
-
const binPath =
|
|
1770
|
-
if (
|
|
1771
|
-
const parent =
|
|
2979
|
+
const binPath = path17.join(dir, "node_modules", ".bin", cmd);
|
|
2980
|
+
if (fs18.existsSync(binPath)) return true;
|
|
2981
|
+
const parent = path17.dirname(dir);
|
|
1772
2982
|
if (parent === dir) break;
|
|
1773
2983
|
dir = parent;
|
|
1774
2984
|
}
|
|
@@ -1790,9 +3000,9 @@ var init_agents = __esm({
|
|
|
1790
3000
|
const defaultAgent = ctx.config.defaultAgent;
|
|
1791
3001
|
let agents = {};
|
|
1792
3002
|
try {
|
|
1793
|
-
const agentsPath =
|
|
1794
|
-
if (
|
|
1795
|
-
const data = JSON.parse(
|
|
3003
|
+
const agentsPath = path17.join(ctx.dataDir, "agents.json");
|
|
3004
|
+
if (fs18.existsSync(agentsPath)) {
|
|
3005
|
+
const data = JSON.parse(fs18.readFileSync(agentsPath, "utf-8"));
|
|
1796
3006
|
agents = data.installed ?? {};
|
|
1797
3007
|
}
|
|
1798
3008
|
} catch {
|
|
@@ -1830,8 +3040,8 @@ var settings_manager_exports = {};
|
|
|
1830
3040
|
__export(settings_manager_exports, {
|
|
1831
3041
|
SettingsManager: () => SettingsManager
|
|
1832
3042
|
});
|
|
1833
|
-
import
|
|
1834
|
-
import
|
|
3043
|
+
import fs19 from "fs";
|
|
3044
|
+
import path18 from "path";
|
|
1835
3045
|
var SettingsManager, SettingsAPIImpl;
|
|
1836
3046
|
var init_settings_manager = __esm({
|
|
1837
3047
|
"src/core/plugin/settings-manager.ts"() {
|
|
@@ -1854,7 +3064,7 @@ var init_settings_manager = __esm({
|
|
|
1854
3064
|
async loadSettings(pluginName) {
|
|
1855
3065
|
const settingsPath = this.getSettingsPath(pluginName);
|
|
1856
3066
|
try {
|
|
1857
|
-
const content =
|
|
3067
|
+
const content = fs19.readFileSync(settingsPath, "utf-8");
|
|
1858
3068
|
return JSON.parse(content);
|
|
1859
3069
|
} catch {
|
|
1860
3070
|
return {};
|
|
@@ -1874,7 +3084,7 @@ var init_settings_manager = __esm({
|
|
|
1874
3084
|
}
|
|
1875
3085
|
/** Resolve the absolute path to a plugin's settings.json file. */
|
|
1876
3086
|
getSettingsPath(pluginName) {
|
|
1877
|
-
return
|
|
3087
|
+
return path18.join(this.basePath, pluginName, "settings.json");
|
|
1878
3088
|
}
|
|
1879
3089
|
async getPluginSettings(pluginName) {
|
|
1880
3090
|
return this.loadSettings(pluginName);
|
|
@@ -1895,7 +3105,7 @@ var init_settings_manager = __esm({
|
|
|
1895
3105
|
readFile() {
|
|
1896
3106
|
if (this.cache !== null) return this.cache;
|
|
1897
3107
|
try {
|
|
1898
|
-
const content =
|
|
3108
|
+
const content = fs19.readFileSync(this.settingsPath, "utf-8");
|
|
1899
3109
|
this.cache = JSON.parse(content);
|
|
1900
3110
|
return this.cache;
|
|
1901
3111
|
} catch {
|
|
@@ -1904,9 +3114,9 @@ var init_settings_manager = __esm({
|
|
|
1904
3114
|
}
|
|
1905
3115
|
}
|
|
1906
3116
|
writeFile(data) {
|
|
1907
|
-
const dir =
|
|
1908
|
-
|
|
1909
|
-
|
|
3117
|
+
const dir = path18.dirname(this.settingsPath);
|
|
3118
|
+
fs19.mkdirSync(dir, { recursive: true });
|
|
3119
|
+
fs19.writeFileSync(this.settingsPath, JSON.stringify(data, null, 2));
|
|
1910
3120
|
this.cache = data;
|
|
1911
3121
|
}
|
|
1912
3122
|
async get(key) {
|
|
@@ -1941,11 +3151,12 @@ var init_settings_manager = __esm({
|
|
|
1941
3151
|
});
|
|
1942
3152
|
|
|
1943
3153
|
// src/core/doctor/checks/telegram.ts
|
|
1944
|
-
import * as
|
|
3154
|
+
import * as path19 from "path";
|
|
1945
3155
|
var BOT_TOKEN_REGEX, telegramCheck;
|
|
1946
3156
|
var init_telegram = __esm({
|
|
1947
3157
|
"src/core/doctor/checks/telegram.ts"() {
|
|
1948
3158
|
"use strict";
|
|
3159
|
+
init_network_redaction();
|
|
1949
3160
|
BOT_TOKEN_REGEX = /^\d+:[A-Za-z0-9_-]{35,}$/;
|
|
1950
3161
|
telegramCheck = {
|
|
1951
3162
|
name: "Telegram",
|
|
@@ -1957,7 +3168,7 @@ var init_telegram = __esm({
|
|
|
1957
3168
|
return results;
|
|
1958
3169
|
}
|
|
1959
3170
|
const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
|
|
1960
|
-
const sm = new SettingsManager2(
|
|
3171
|
+
const sm = new SettingsManager2(path19.join(ctx.pluginsDir, "data"));
|
|
1961
3172
|
const ps = await sm.loadSettings("@openacp/telegram");
|
|
1962
3173
|
const botToken = ps.botToken;
|
|
1963
3174
|
const chatId = ps.chatId;
|
|
@@ -1970,9 +3181,17 @@ var init_telegram = __esm({
|
|
|
1970
3181
|
return results;
|
|
1971
3182
|
}
|
|
1972
3183
|
results.push({ status: "pass", message: "Bot token format valid" });
|
|
3184
|
+
let telegramFetch;
|
|
3185
|
+
try {
|
|
3186
|
+
telegramFetch = ctx.fetchForScope("channels.telegram");
|
|
3187
|
+
} catch (err) {
|
|
3188
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3189
|
+
results.push({ status: "fail", message: `Cannot initialize Telegram transport: ${redactNetworkSecrets(message)}` });
|
|
3190
|
+
return results;
|
|
3191
|
+
}
|
|
1973
3192
|
let botId;
|
|
1974
3193
|
try {
|
|
1975
|
-
const res = await
|
|
3194
|
+
const res = await telegramFetch(`https://api.telegram.org/bot${botToken}/getMe`);
|
|
1976
3195
|
const data = await res.json();
|
|
1977
3196
|
if (data.ok && data.result) {
|
|
1978
3197
|
botId = data.result.id;
|
|
@@ -1982,7 +3201,8 @@ var init_telegram = __esm({
|
|
|
1982
3201
|
return results;
|
|
1983
3202
|
}
|
|
1984
3203
|
} catch (err) {
|
|
1985
|
-
|
|
3204
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3205
|
+
results.push({ status: "fail", message: `Cannot reach Telegram API: ${redactNetworkSecrets(message)}` });
|
|
1986
3206
|
return results;
|
|
1987
3207
|
}
|
|
1988
3208
|
if (!chatId || chatId === 0) {
|
|
@@ -1990,7 +3210,7 @@ var init_telegram = __esm({
|
|
|
1990
3210
|
return results;
|
|
1991
3211
|
}
|
|
1992
3212
|
try {
|
|
1993
|
-
const res = await
|
|
3213
|
+
const res = await telegramFetch(`https://api.telegram.org/bot${botToken}/getChat`, {
|
|
1994
3214
|
method: "POST",
|
|
1995
3215
|
headers: { "Content-Type": "application/json" },
|
|
1996
3216
|
body: JSON.stringify({ chat_id: chatId })
|
|
@@ -2010,11 +3230,12 @@ var init_telegram = __esm({
|
|
|
2010
3230
|
results.push({ status: "pass", message: `Chat is supergroup with topics ("${data.result.title}")` });
|
|
2011
3231
|
}
|
|
2012
3232
|
} catch (err) {
|
|
2013
|
-
|
|
3233
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3234
|
+
results.push({ status: "fail", message: `Cannot validate chat: ${redactNetworkSecrets(message)}` });
|
|
2014
3235
|
return results;
|
|
2015
3236
|
}
|
|
2016
3237
|
try {
|
|
2017
|
-
const res = await
|
|
3238
|
+
const res = await telegramFetch(`https://api.telegram.org/bot${botToken}/getChatMember`, {
|
|
2018
3239
|
method: "POST",
|
|
2019
3240
|
headers: { "Content-Type": "application/json" },
|
|
2020
3241
|
body: JSON.stringify({ chat_id: chatId, user_id: botId })
|
|
@@ -2031,7 +3252,8 @@ var init_telegram = __esm({
|
|
|
2031
3252
|
});
|
|
2032
3253
|
}
|
|
2033
3254
|
} catch (err) {
|
|
2034
|
-
|
|
3255
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3256
|
+
results.push({ status: "fail", message: `Admin check failed: ${redactNetworkSecrets(message)}` });
|
|
2035
3257
|
}
|
|
2036
3258
|
return results;
|
|
2037
3259
|
}
|
|
@@ -2040,7 +3262,7 @@ var init_telegram = __esm({
|
|
|
2040
3262
|
});
|
|
2041
3263
|
|
|
2042
3264
|
// src/core/doctor/checks/storage.ts
|
|
2043
|
-
import * as
|
|
3265
|
+
import * as fs20 from "fs";
|
|
2044
3266
|
var storageCheck;
|
|
2045
3267
|
var init_storage = __esm({
|
|
2046
3268
|
"src/core/doctor/checks/storage.ts"() {
|
|
@@ -2050,28 +3272,28 @@ var init_storage = __esm({
|
|
|
2050
3272
|
order: 4,
|
|
2051
3273
|
async run(ctx) {
|
|
2052
3274
|
const results = [];
|
|
2053
|
-
if (!
|
|
3275
|
+
if (!fs20.existsSync(ctx.dataDir)) {
|
|
2054
3276
|
results.push({
|
|
2055
3277
|
status: "fail",
|
|
2056
3278
|
message: "Data directory ~/.openacp does not exist",
|
|
2057
3279
|
fixable: true,
|
|
2058
3280
|
fixRisk: "safe",
|
|
2059
3281
|
fix: async () => {
|
|
2060
|
-
|
|
3282
|
+
fs20.mkdirSync(ctx.dataDir, { recursive: true });
|
|
2061
3283
|
return { success: true, message: "created directory" };
|
|
2062
3284
|
}
|
|
2063
3285
|
});
|
|
2064
3286
|
} else {
|
|
2065
3287
|
try {
|
|
2066
|
-
|
|
3288
|
+
fs20.accessSync(ctx.dataDir, fs20.constants.W_OK);
|
|
2067
3289
|
results.push({ status: "pass", message: "Data directory exists and writable" });
|
|
2068
3290
|
} catch {
|
|
2069
3291
|
results.push({ status: "fail", message: "Data directory not writable" });
|
|
2070
3292
|
}
|
|
2071
3293
|
}
|
|
2072
|
-
if (
|
|
3294
|
+
if (fs20.existsSync(ctx.sessionsPath)) {
|
|
2073
3295
|
try {
|
|
2074
|
-
const content =
|
|
3296
|
+
const content = fs20.readFileSync(ctx.sessionsPath, "utf-8");
|
|
2075
3297
|
const data = JSON.parse(content);
|
|
2076
3298
|
if (typeof data === "object" && data !== null && "sessions" in data) {
|
|
2077
3299
|
results.push({ status: "pass", message: "Sessions file valid" });
|
|
@@ -2082,7 +3304,7 @@ var init_storage = __esm({
|
|
|
2082
3304
|
fixable: true,
|
|
2083
3305
|
fixRisk: "risky",
|
|
2084
3306
|
fix: async () => {
|
|
2085
|
-
|
|
3307
|
+
fs20.writeFileSync(ctx.sessionsPath, JSON.stringify({ version: 1, sessions: {} }, null, 2));
|
|
2086
3308
|
return { success: true, message: "reset sessions file" };
|
|
2087
3309
|
}
|
|
2088
3310
|
});
|
|
@@ -2094,7 +3316,7 @@ var init_storage = __esm({
|
|
|
2094
3316
|
fixable: true,
|
|
2095
3317
|
fixRisk: "risky",
|
|
2096
3318
|
fix: async () => {
|
|
2097
|
-
|
|
3319
|
+
fs20.writeFileSync(ctx.sessionsPath, JSON.stringify({ version: 1, sessions: {} }, null, 2));
|
|
2098
3320
|
return { success: true, message: "reset sessions file" };
|
|
2099
3321
|
}
|
|
2100
3322
|
});
|
|
@@ -2102,20 +3324,20 @@ var init_storage = __esm({
|
|
|
2102
3324
|
} else {
|
|
2103
3325
|
results.push({ status: "pass", message: "Sessions file not present yet (created on first session)" });
|
|
2104
3326
|
}
|
|
2105
|
-
if (!
|
|
3327
|
+
if (!fs20.existsSync(ctx.logsDir)) {
|
|
2106
3328
|
results.push({
|
|
2107
3329
|
status: "warn",
|
|
2108
3330
|
message: "Log directory does not exist",
|
|
2109
3331
|
fixable: true,
|
|
2110
3332
|
fixRisk: "safe",
|
|
2111
3333
|
fix: async () => {
|
|
2112
|
-
|
|
3334
|
+
fs20.mkdirSync(ctx.logsDir, { recursive: true });
|
|
2113
3335
|
return { success: true, message: "created log directory" };
|
|
2114
3336
|
}
|
|
2115
3337
|
});
|
|
2116
3338
|
} else {
|
|
2117
3339
|
try {
|
|
2118
|
-
|
|
3340
|
+
fs20.accessSync(ctx.logsDir, fs20.constants.W_OK);
|
|
2119
3341
|
results.push({ status: "pass", message: "Log directory exists and writable" });
|
|
2120
3342
|
} catch {
|
|
2121
3343
|
results.push({ status: "fail", message: "Log directory not writable" });
|
|
@@ -2128,8 +3350,8 @@ var init_storage = __esm({
|
|
|
2128
3350
|
});
|
|
2129
3351
|
|
|
2130
3352
|
// src/core/doctor/checks/workspace.ts
|
|
2131
|
-
import * as
|
|
2132
|
-
import * as
|
|
3353
|
+
import * as fs21 from "fs";
|
|
3354
|
+
import * as path20 from "path";
|
|
2133
3355
|
var workspaceCheck;
|
|
2134
3356
|
var init_workspace = __esm({
|
|
2135
3357
|
"src/core/doctor/checks/workspace.ts"() {
|
|
@@ -2139,21 +3361,21 @@ var init_workspace = __esm({
|
|
|
2139
3361
|
order: 5,
|
|
2140
3362
|
async run(ctx) {
|
|
2141
3363
|
const results = [];
|
|
2142
|
-
const workspace =
|
|
2143
|
-
if (!
|
|
3364
|
+
const workspace = path20.dirname(ctx.dataDir);
|
|
3365
|
+
if (!fs21.existsSync(workspace)) {
|
|
2144
3366
|
results.push({
|
|
2145
3367
|
status: "warn",
|
|
2146
3368
|
message: `Workspace directory does not exist: ${workspace}`,
|
|
2147
3369
|
fixable: true,
|
|
2148
3370
|
fixRisk: "safe",
|
|
2149
3371
|
fix: async () => {
|
|
2150
|
-
|
|
3372
|
+
fs21.mkdirSync(workspace, { recursive: true });
|
|
2151
3373
|
return { success: true, message: "created directory" };
|
|
2152
3374
|
}
|
|
2153
3375
|
});
|
|
2154
3376
|
} else {
|
|
2155
3377
|
try {
|
|
2156
|
-
|
|
3378
|
+
fs21.accessSync(workspace, fs21.constants.W_OK);
|
|
2157
3379
|
results.push({ status: "pass", message: `Workspace directory exists: ${workspace}` });
|
|
2158
3380
|
} catch {
|
|
2159
3381
|
results.push({ status: "fail", message: `Workspace directory not writable: ${workspace}` });
|
|
@@ -2166,8 +3388,8 @@ var init_workspace = __esm({
|
|
|
2166
3388
|
});
|
|
2167
3389
|
|
|
2168
3390
|
// src/core/doctor/checks/plugins.ts
|
|
2169
|
-
import * as
|
|
2170
|
-
import * as
|
|
3391
|
+
import * as fs22 from "fs";
|
|
3392
|
+
import * as path21 from "path";
|
|
2171
3393
|
var pluginsCheck;
|
|
2172
3394
|
var init_plugins = __esm({
|
|
2173
3395
|
"src/core/doctor/checks/plugins.ts"() {
|
|
@@ -2177,16 +3399,16 @@ var init_plugins = __esm({
|
|
|
2177
3399
|
order: 6,
|
|
2178
3400
|
async run(ctx) {
|
|
2179
3401
|
const results = [];
|
|
2180
|
-
if (!
|
|
3402
|
+
if (!fs22.existsSync(ctx.pluginsDir)) {
|
|
2181
3403
|
results.push({
|
|
2182
3404
|
status: "warn",
|
|
2183
3405
|
message: "Plugins directory does not exist",
|
|
2184
3406
|
fixable: true,
|
|
2185
3407
|
fixRisk: "safe",
|
|
2186
3408
|
fix: async () => {
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
3409
|
+
fs22.mkdirSync(ctx.pluginsDir, { recursive: true });
|
|
3410
|
+
fs22.writeFileSync(
|
|
3411
|
+
path21.join(ctx.pluginsDir, "package.json"),
|
|
2190
3412
|
JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
|
|
2191
3413
|
);
|
|
2192
3414
|
return { success: true, message: "initialized plugins directory" };
|
|
@@ -2195,15 +3417,15 @@ var init_plugins = __esm({
|
|
|
2195
3417
|
return results;
|
|
2196
3418
|
}
|
|
2197
3419
|
results.push({ status: "pass", message: "Plugins directory exists" });
|
|
2198
|
-
const pkgPath =
|
|
2199
|
-
if (!
|
|
3420
|
+
const pkgPath = path21.join(ctx.pluginsDir, "package.json");
|
|
3421
|
+
if (!fs22.existsSync(pkgPath)) {
|
|
2200
3422
|
results.push({
|
|
2201
3423
|
status: "warn",
|
|
2202
3424
|
message: "Plugins package.json missing",
|
|
2203
3425
|
fixable: true,
|
|
2204
3426
|
fixRisk: "safe",
|
|
2205
3427
|
fix: async () => {
|
|
2206
|
-
|
|
3428
|
+
fs22.writeFileSync(
|
|
2207
3429
|
pkgPath,
|
|
2208
3430
|
JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
|
|
2209
3431
|
);
|
|
@@ -2213,7 +3435,7 @@ var init_plugins = __esm({
|
|
|
2213
3435
|
return results;
|
|
2214
3436
|
}
|
|
2215
3437
|
try {
|
|
2216
|
-
const pkg = JSON.parse(
|
|
3438
|
+
const pkg = JSON.parse(fs22.readFileSync(pkgPath, "utf-8"));
|
|
2217
3439
|
const deps = pkg.dependencies || {};
|
|
2218
3440
|
const count = Object.keys(deps).length;
|
|
2219
3441
|
results.push({ status: "pass", message: `Plugins package.json valid (${count} plugins)` });
|
|
@@ -2224,7 +3446,7 @@ var init_plugins = __esm({
|
|
|
2224
3446
|
fixable: true,
|
|
2225
3447
|
fixRisk: "risky",
|
|
2226
3448
|
fix: async () => {
|
|
2227
|
-
|
|
3449
|
+
fs22.writeFileSync(
|
|
2228
3450
|
pkgPath,
|
|
2229
3451
|
JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
|
|
2230
3452
|
);
|
|
@@ -2239,8 +3461,8 @@ var init_plugins = __esm({
|
|
|
2239
3461
|
});
|
|
2240
3462
|
|
|
2241
3463
|
// src/core/doctor/checks/daemon.ts
|
|
2242
|
-
import * as
|
|
2243
|
-
import * as
|
|
3464
|
+
import * as fs23 from "fs";
|
|
3465
|
+
import * as net3 from "net";
|
|
2244
3466
|
function isProcessAlive(pid) {
|
|
2245
3467
|
try {
|
|
2246
3468
|
process.kill(pid, 0);
|
|
@@ -2251,7 +3473,7 @@ function isProcessAlive(pid) {
|
|
|
2251
3473
|
}
|
|
2252
3474
|
function checkPortInUse(port) {
|
|
2253
3475
|
return new Promise((resolve7) => {
|
|
2254
|
-
const server =
|
|
3476
|
+
const server = net3.createServer();
|
|
2255
3477
|
server.once("error", () => resolve7(true));
|
|
2256
3478
|
server.once("listening", () => {
|
|
2257
3479
|
server.close();
|
|
@@ -2269,8 +3491,8 @@ var init_daemon = __esm({
|
|
|
2269
3491
|
order: 7,
|
|
2270
3492
|
async run(ctx) {
|
|
2271
3493
|
const results = [];
|
|
2272
|
-
if (
|
|
2273
|
-
const content =
|
|
3494
|
+
if (fs23.existsSync(ctx.pidPath)) {
|
|
3495
|
+
const content = fs23.readFileSync(ctx.pidPath, "utf-8").trim();
|
|
2274
3496
|
const pid = parseInt(content, 10);
|
|
2275
3497
|
if (isNaN(pid)) {
|
|
2276
3498
|
results.push({
|
|
@@ -2279,7 +3501,7 @@ var init_daemon = __esm({
|
|
|
2279
3501
|
fixable: true,
|
|
2280
3502
|
fixRisk: "safe",
|
|
2281
3503
|
fix: async () => {
|
|
2282
|
-
|
|
3504
|
+
fs23.unlinkSync(ctx.pidPath);
|
|
2283
3505
|
return { success: true, message: "removed invalid PID file" };
|
|
2284
3506
|
}
|
|
2285
3507
|
});
|
|
@@ -2290,7 +3512,7 @@ var init_daemon = __esm({
|
|
|
2290
3512
|
fixable: true,
|
|
2291
3513
|
fixRisk: "safe",
|
|
2292
3514
|
fix: async () => {
|
|
2293
|
-
|
|
3515
|
+
fs23.unlinkSync(ctx.pidPath);
|
|
2294
3516
|
return { success: true, message: "removed stale PID file" };
|
|
2295
3517
|
}
|
|
2296
3518
|
});
|
|
@@ -2298,8 +3520,8 @@ var init_daemon = __esm({
|
|
|
2298
3520
|
results.push({ status: "pass", message: `Daemon running (PID ${pid})` });
|
|
2299
3521
|
}
|
|
2300
3522
|
}
|
|
2301
|
-
if (
|
|
2302
|
-
const content =
|
|
3523
|
+
if (fs23.existsSync(ctx.portFilePath)) {
|
|
3524
|
+
const content = fs23.readFileSync(ctx.portFilePath, "utf-8").trim();
|
|
2303
3525
|
const port = parseInt(content, 10);
|
|
2304
3526
|
if (isNaN(port)) {
|
|
2305
3527
|
results.push({
|
|
@@ -2308,7 +3530,7 @@ var init_daemon = __esm({
|
|
|
2308
3530
|
fixable: true,
|
|
2309
3531
|
fixRisk: "safe",
|
|
2310
3532
|
fix: async () => {
|
|
2311
|
-
|
|
3533
|
+
fs23.unlinkSync(ctx.portFilePath);
|
|
2312
3534
|
return { success: true, message: "removed invalid port file" };
|
|
2313
3535
|
}
|
|
2314
3536
|
});
|
|
@@ -2320,8 +3542,8 @@ var init_daemon = __esm({
|
|
|
2320
3542
|
const apiPort = 21420;
|
|
2321
3543
|
const inUse = await checkPortInUse(apiPort);
|
|
2322
3544
|
if (inUse) {
|
|
2323
|
-
if (
|
|
2324
|
-
const pid = parseInt(
|
|
3545
|
+
if (fs23.existsSync(ctx.pidPath)) {
|
|
3546
|
+
const pid = parseInt(fs23.readFileSync(ctx.pidPath, "utf-8").trim(), 10);
|
|
2325
3547
|
if (!isNaN(pid) && isProcessAlive(pid)) {
|
|
2326
3548
|
results.push({ status: "pass", message: `API port ${apiPort} in use by OpenACP daemon` });
|
|
2327
3549
|
} else {
|
|
@@ -2341,17 +3563,17 @@ var init_daemon = __esm({
|
|
|
2341
3563
|
});
|
|
2342
3564
|
|
|
2343
3565
|
// src/core/utils/install-binary.ts
|
|
2344
|
-
import
|
|
2345
|
-
import
|
|
3566
|
+
import fs24 from "fs";
|
|
3567
|
+
import path22 from "path";
|
|
2346
3568
|
import https from "https";
|
|
2347
3569
|
import os5 from "os";
|
|
2348
3570
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
2349
3571
|
function downloadFile(url, dest, maxRedirects = 10) {
|
|
2350
3572
|
return new Promise((resolve7, reject) => {
|
|
2351
|
-
const file =
|
|
3573
|
+
const file = fs24.createWriteStream(dest);
|
|
2352
3574
|
const cleanup = () => {
|
|
2353
3575
|
try {
|
|
2354
|
-
if (
|
|
3576
|
+
if (fs24.existsSync(dest)) fs24.unlinkSync(dest);
|
|
2355
3577
|
} catch {
|
|
2356
3578
|
}
|
|
2357
3579
|
};
|
|
@@ -2417,36 +3639,36 @@ function getDownloadUrl(spec) {
|
|
|
2417
3639
|
async function ensureBinary(spec, binDir) {
|
|
2418
3640
|
const resolvedBinDir = binDir ?? DEFAULT_BIN_DIR;
|
|
2419
3641
|
const binName = IS_WINDOWS ? `${spec.name}.exe` : spec.name;
|
|
2420
|
-
const binPath =
|
|
3642
|
+
const binPath = path22.join(resolvedBinDir, binName);
|
|
2421
3643
|
if (commandExists(spec.name)) {
|
|
2422
3644
|
log18.debug({ name: spec.name }, "Found in PATH");
|
|
2423
3645
|
return spec.name;
|
|
2424
3646
|
}
|
|
2425
|
-
if (
|
|
2426
|
-
if (!IS_WINDOWS)
|
|
3647
|
+
if (fs24.existsSync(binPath)) {
|
|
3648
|
+
if (!IS_WINDOWS) fs24.chmodSync(binPath, "755");
|
|
2427
3649
|
log18.debug({ name: spec.name, path: binPath }, "Found in ~/.openacp/bin");
|
|
2428
3650
|
return binPath;
|
|
2429
3651
|
}
|
|
2430
3652
|
log18.info({ name: spec.name }, "Not found, downloading from GitHub...");
|
|
2431
|
-
|
|
3653
|
+
fs24.mkdirSync(resolvedBinDir, { recursive: true });
|
|
2432
3654
|
const url = getDownloadUrl(spec);
|
|
2433
3655
|
const isArchive = spec.isArchive?.(url) ?? false;
|
|
2434
|
-
const downloadDest = isArchive ?
|
|
3656
|
+
const downloadDest = isArchive ? path22.join(resolvedBinDir, `${spec.name}.tgz`) : binPath;
|
|
2435
3657
|
await downloadFile(url, downloadDest);
|
|
2436
3658
|
if (isArchive) {
|
|
2437
3659
|
const listing = execFileSync4("tar", ["-tf", downloadDest], { stdio: "pipe" }).toString().trim().split("\n").filter(Boolean);
|
|
2438
3660
|
validateTarContents(listing, resolvedBinDir);
|
|
2439
3661
|
execFileSync4("tar", ["-xzf", downloadDest, "-C", resolvedBinDir], { stdio: "pipe" });
|
|
2440
3662
|
try {
|
|
2441
|
-
|
|
3663
|
+
fs24.unlinkSync(downloadDest);
|
|
2442
3664
|
} catch {
|
|
2443
3665
|
}
|
|
2444
3666
|
}
|
|
2445
|
-
if (!
|
|
3667
|
+
if (!fs24.existsSync(binPath)) {
|
|
2446
3668
|
throw new Error(`${spec.name}: binary not found at ${binPath} after download/extraction. The archive structure may have changed.`);
|
|
2447
3669
|
}
|
|
2448
3670
|
if (!IS_WINDOWS) {
|
|
2449
|
-
|
|
3671
|
+
fs24.chmodSync(binPath, "755");
|
|
2450
3672
|
}
|
|
2451
3673
|
log18.info({ name: spec.name, path: binPath }, "Installed successfully");
|
|
2452
3674
|
return binPath;
|
|
@@ -2459,7 +3681,7 @@ var init_install_binary = __esm({
|
|
|
2459
3681
|
init_agent_dependencies();
|
|
2460
3682
|
init_agent_installer();
|
|
2461
3683
|
log18 = createChildLogger({ module: "binary-installer" });
|
|
2462
|
-
DEFAULT_BIN_DIR =
|
|
3684
|
+
DEFAULT_BIN_DIR = path22.join(os5.homedir(), ".openacp", "bin");
|
|
2463
3685
|
IS_WINDOWS = os5.platform() === "win32";
|
|
2464
3686
|
}
|
|
2465
3687
|
});
|
|
@@ -2500,8 +3722,8 @@ var init_install_cloudflared = __esm({
|
|
|
2500
3722
|
});
|
|
2501
3723
|
|
|
2502
3724
|
// src/core/doctor/checks/tunnel.ts
|
|
2503
|
-
import * as
|
|
2504
|
-
import * as
|
|
3725
|
+
import * as fs25 from "fs";
|
|
3726
|
+
import * as path23 from "path";
|
|
2505
3727
|
import * as os6 from "os";
|
|
2506
3728
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
2507
3729
|
var tunnelCheck;
|
|
@@ -2518,7 +3740,7 @@ var init_tunnel = __esm({
|
|
|
2518
3740
|
return results;
|
|
2519
3741
|
}
|
|
2520
3742
|
const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
|
|
2521
|
-
const sm = new SettingsManager2(
|
|
3743
|
+
const sm = new SettingsManager2(path23.join(ctx.pluginsDir, "data"));
|
|
2522
3744
|
const tunnelSettings = await sm.loadSettings("@openacp/tunnel");
|
|
2523
3745
|
const tunnelEnabled = tunnelSettings.enabled ?? false;
|
|
2524
3746
|
const provider = tunnelSettings.provider ?? "cloudflare";
|
|
@@ -2530,9 +3752,9 @@ var init_tunnel = __esm({
|
|
|
2530
3752
|
results.push({ status: "pass", message: `Tunnel provider: ${provider}` });
|
|
2531
3753
|
if (provider === "cloudflare") {
|
|
2532
3754
|
const binName = os6.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
|
|
2533
|
-
const binPath =
|
|
3755
|
+
const binPath = path23.join(ctx.dataDir, "bin", binName);
|
|
2534
3756
|
let found = false;
|
|
2535
|
-
if (
|
|
3757
|
+
if (fs25.existsSync(binPath)) {
|
|
2536
3758
|
found = true;
|
|
2537
3759
|
} else {
|
|
2538
3760
|
try {
|
|
@@ -2572,14 +3794,48 @@ var init_tunnel = __esm({
|
|
|
2572
3794
|
}
|
|
2573
3795
|
});
|
|
2574
3796
|
|
|
3797
|
+
// src/core/doctor/checks/proxy.ts
|
|
3798
|
+
var proxyCheck;
|
|
3799
|
+
var init_proxy = __esm({
|
|
3800
|
+
"src/core/doctor/checks/proxy.ts"() {
|
|
3801
|
+
"use strict";
|
|
3802
|
+
init_proxy_service();
|
|
3803
|
+
init_proxy_store();
|
|
3804
|
+
proxyCheck = {
|
|
3805
|
+
name: "Proxy routing",
|
|
3806
|
+
order: 85,
|
|
3807
|
+
async run(ctx) {
|
|
3808
|
+
try {
|
|
3809
|
+
if (ctx.dataDir) new ProxyStore(ctx.dataDir).load();
|
|
3810
|
+
} catch (error) {
|
|
3811
|
+
if (error instanceof ProxyStoreCorruptError) return [{ status: "fail", message: `${error.message}. Network consumers are fail-closed; inspect quarantine/LKG before recovery.` }];
|
|
3812
|
+
throw error;
|
|
3813
|
+
}
|
|
3814
|
+
const variables = PROXY_ENV_KEYS.filter((key) => {
|
|
3815
|
+
const value = process.env[key];
|
|
3816
|
+
return !["NO_PROXY", "no_proxy", "NODE_USE_ENV_PROXY"].includes(key) && value !== void 0 && value !== "";
|
|
3817
|
+
});
|
|
3818
|
+
if (!variables.length) {
|
|
3819
|
+
return [{ status: "pass", message: "No daemon-wide proxy environment; native scoped routing is authoritative" }];
|
|
3820
|
+
}
|
|
3821
|
+
return [{
|
|
3822
|
+
status: "warn",
|
|
3823
|
+
message: `Daemon-wide proxy environment active (${variables.join(", ")}); scoped routing is in compatibility mode until legacy wrapper/drop-in injection is removed`
|
|
3824
|
+
}];
|
|
3825
|
+
}
|
|
3826
|
+
};
|
|
3827
|
+
}
|
|
3828
|
+
});
|
|
3829
|
+
|
|
2575
3830
|
// src/core/doctor/index.ts
|
|
2576
|
-
import * as
|
|
2577
|
-
import * as
|
|
3831
|
+
import * as fs26 from "fs";
|
|
3832
|
+
import * as path24 from "path";
|
|
2578
3833
|
var ALL_CHECKS, CHECK_TIMEOUT_MS, DoctorEngine;
|
|
2579
3834
|
var init_doctor = __esm({
|
|
2580
3835
|
"src/core/doctor/index.ts"() {
|
|
2581
3836
|
"use strict";
|
|
2582
3837
|
init_config();
|
|
3838
|
+
init_proxy_service();
|
|
2583
3839
|
init_config2();
|
|
2584
3840
|
init_agents();
|
|
2585
3841
|
init_telegram();
|
|
@@ -2588,6 +3844,7 @@ var init_doctor = __esm({
|
|
|
2588
3844
|
init_plugins();
|
|
2589
3845
|
init_daemon();
|
|
2590
3846
|
init_tunnel();
|
|
3847
|
+
init_proxy();
|
|
2591
3848
|
ALL_CHECKS = [
|
|
2592
3849
|
configCheck,
|
|
2593
3850
|
agentsCheck,
|
|
@@ -2596,15 +3853,18 @@ var init_doctor = __esm({
|
|
|
2596
3853
|
workspaceCheck,
|
|
2597
3854
|
pluginsCheck,
|
|
2598
3855
|
daemonCheck,
|
|
2599
|
-
tunnelCheck
|
|
3856
|
+
tunnelCheck,
|
|
3857
|
+
proxyCheck
|
|
2600
3858
|
];
|
|
2601
3859
|
CHECK_TIMEOUT_MS = 1e4;
|
|
2602
3860
|
DoctorEngine = class {
|
|
2603
3861
|
dryRun;
|
|
2604
3862
|
dataDir;
|
|
3863
|
+
proxyService;
|
|
2605
3864
|
constructor(options) {
|
|
2606
3865
|
this.dryRun = options?.dryRun ?? false;
|
|
2607
3866
|
this.dataDir = options.dataDir;
|
|
3867
|
+
this.proxyService = options?.proxyService ?? new ProxyService(this.dataDir);
|
|
2608
3868
|
}
|
|
2609
3869
|
/**
|
|
2610
3870
|
* Executes all checks and returns an aggregated report.
|
|
@@ -2662,28 +3922,29 @@ var init_doctor = __esm({
|
|
|
2662
3922
|
/** Constructs the shared context used by all checks — loads config if available. */
|
|
2663
3923
|
async buildContext() {
|
|
2664
3924
|
const dataDir = this.dataDir;
|
|
2665
|
-
const configPath = process.env.OPENACP_CONFIG_PATH ||
|
|
3925
|
+
const configPath = process.env.OPENACP_CONFIG_PATH || path24.join(dataDir, "config.json");
|
|
2666
3926
|
let config = null;
|
|
2667
3927
|
let rawConfig = null;
|
|
2668
3928
|
try {
|
|
2669
|
-
const content =
|
|
3929
|
+
const content = fs26.readFileSync(configPath, "utf-8");
|
|
2670
3930
|
rawConfig = JSON.parse(content);
|
|
2671
3931
|
const cm = new ConfigManager(configPath);
|
|
2672
3932
|
await cm.load();
|
|
2673
3933
|
config = cm.get();
|
|
2674
3934
|
} catch {
|
|
2675
3935
|
}
|
|
2676
|
-
const logsDir = config ? expandHome2(config.logging.logDir) :
|
|
3936
|
+
const logsDir = config ? expandHome2(config.logging.logDir) : path24.join(dataDir, "logs");
|
|
2677
3937
|
return {
|
|
2678
3938
|
config,
|
|
2679
3939
|
rawConfig,
|
|
2680
3940
|
configPath,
|
|
2681
3941
|
dataDir,
|
|
2682
|
-
sessionsPath:
|
|
2683
|
-
pidPath:
|
|
2684
|
-
portFilePath:
|
|
2685
|
-
pluginsDir:
|
|
2686
|
-
logsDir
|
|
3942
|
+
sessionsPath: path24.join(dataDir, "sessions.json"),
|
|
3943
|
+
pidPath: path24.join(dataDir, "openacp.pid"),
|
|
3944
|
+
portFilePath: path24.join(dataDir, "api.port"),
|
|
3945
|
+
pluginsDir: path24.join(dataDir, "plugins"),
|
|
3946
|
+
logsDir,
|
|
3947
|
+
fetchForScope: (scope) => this.proxyService.createFetch(scope)
|
|
2687
3948
|
};
|
|
2688
3949
|
}
|
|
2689
3950
|
};
|
|
@@ -2691,11 +3952,11 @@ var init_doctor = __esm({
|
|
|
2691
3952
|
});
|
|
2692
3953
|
|
|
2693
3954
|
// src/core/instance/instance-context.ts
|
|
2694
|
-
import
|
|
2695
|
-
import
|
|
3955
|
+
import path26 from "path";
|
|
3956
|
+
import fs28 from "fs";
|
|
2696
3957
|
import os8 from "os";
|
|
2697
3958
|
function getGlobalRoot() {
|
|
2698
|
-
return
|
|
3959
|
+
return path26.join(os8.homedir(), ".openacp");
|
|
2699
3960
|
}
|
|
2700
3961
|
var init_instance_context = __esm({
|
|
2701
3962
|
"src/core/instance/instance-context.ts"() {
|
|
@@ -2704,12 +3965,12 @@ var init_instance_context = __esm({
|
|
|
2704
3965
|
});
|
|
2705
3966
|
|
|
2706
3967
|
// src/core/instance/instance-registry.ts
|
|
2707
|
-
import
|
|
2708
|
-
import
|
|
3968
|
+
import fs29 from "fs";
|
|
3969
|
+
import path27 from "path";
|
|
2709
3970
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2710
3971
|
function readIdFromConfig(instanceRoot) {
|
|
2711
3972
|
try {
|
|
2712
|
-
const raw = JSON.parse(
|
|
3973
|
+
const raw = JSON.parse(fs29.readFileSync(path27.join(instanceRoot, "config.json"), "utf-8"));
|
|
2713
3974
|
return typeof raw.id === "string" && raw.id ? raw.id : null;
|
|
2714
3975
|
} catch {
|
|
2715
3976
|
return null;
|
|
@@ -2728,7 +3989,7 @@ var init_instance_registry = __esm({
|
|
|
2728
3989
|
/** Load the registry from disk. If the file is missing or corrupt, starts fresh. */
|
|
2729
3990
|
load() {
|
|
2730
3991
|
try {
|
|
2731
|
-
const raw =
|
|
3992
|
+
const raw = fs29.readFileSync(this.registryPath, "utf-8");
|
|
2732
3993
|
const parsed = JSON.parse(raw);
|
|
2733
3994
|
if (parsed.version === 1 && parsed.instances) {
|
|
2734
3995
|
this.data = parsed;
|
|
@@ -2755,9 +4016,9 @@ var init_instance_registry = __esm({
|
|
|
2755
4016
|
}
|
|
2756
4017
|
/** Persist the registry to disk, creating parent directories if needed. */
|
|
2757
4018
|
save() {
|
|
2758
|
-
const dir =
|
|
2759
|
-
|
|
2760
|
-
|
|
4019
|
+
const dir = path27.dirname(this.registryPath);
|
|
4020
|
+
fs29.mkdirSync(dir, { recursive: true });
|
|
4021
|
+
fs29.writeFileSync(this.registryPath, JSON.stringify(this.data, null, 2));
|
|
2761
4022
|
}
|
|
2762
4023
|
/** Add or update an instance entry in the registry. Does not persist — call save() after. */
|
|
2763
4024
|
register(id, root) {
|
|
@@ -2824,15 +4085,15 @@ __export(plugin_installer_exports, {
|
|
|
2824
4085
|
});
|
|
2825
4086
|
import { execFile as execFile2 } from "child_process";
|
|
2826
4087
|
import { promisify as promisify2 } from "util";
|
|
2827
|
-
import * as
|
|
2828
|
-
import * as
|
|
4088
|
+
import * as fs31 from "fs/promises";
|
|
4089
|
+
import * as path29 from "path";
|
|
2829
4090
|
import { pathToFileURL } from "url";
|
|
2830
4091
|
async function importFromDir(packageName, dir) {
|
|
2831
|
-
const pkgDir =
|
|
2832
|
-
const pkgJsonPath =
|
|
4092
|
+
const pkgDir = path29.join(dir, "node_modules", ...packageName.split("/"));
|
|
4093
|
+
const pkgJsonPath = path29.join(pkgDir, "package.json");
|
|
2833
4094
|
let pkgJson;
|
|
2834
4095
|
try {
|
|
2835
|
-
pkgJson = JSON.parse(await
|
|
4096
|
+
pkgJson = JSON.parse(await fs31.readFile(pkgJsonPath, "utf-8"));
|
|
2836
4097
|
} catch (err) {
|
|
2837
4098
|
throw new Error(`Cannot read package.json for "${packageName}" at ${pkgJsonPath}: ${err.message}`);
|
|
2838
4099
|
}
|
|
@@ -2845,15 +4106,15 @@ async function importFromDir(packageName, dir) {
|
|
|
2845
4106
|
} else {
|
|
2846
4107
|
entry = pkgJson.main ?? "index.js";
|
|
2847
4108
|
}
|
|
2848
|
-
const entryPath =
|
|
4109
|
+
const entryPath = path29.join(pkgDir, entry);
|
|
2849
4110
|
try {
|
|
2850
|
-
await
|
|
4111
|
+
await fs31.access(entryPath);
|
|
2851
4112
|
} catch {
|
|
2852
4113
|
throw new Error(`Entry point "${entry}" not found for "${packageName}" at ${entryPath}`);
|
|
2853
4114
|
}
|
|
2854
4115
|
return import(pathToFileURL(entryPath).href);
|
|
2855
4116
|
}
|
|
2856
|
-
async function installNpmPlugin(packageName, pluginsDir) {
|
|
4117
|
+
async function installNpmPlugin(packageName, pluginsDir, childEnv) {
|
|
2857
4118
|
if (!VALID_NPM_NAME.test(packageName)) {
|
|
2858
4119
|
throw new Error(`Invalid package name: "${packageName}". Must be a valid npm package name.`);
|
|
2859
4120
|
}
|
|
@@ -2863,7 +4124,8 @@ async function installNpmPlugin(packageName, pluginsDir) {
|
|
|
2863
4124
|
} catch {
|
|
2864
4125
|
}
|
|
2865
4126
|
await execFileAsync2("npm", ["install", packageName, "--prefix", dir, "--save", "--ignore-scripts"], {
|
|
2866
|
-
timeout: 6e4
|
|
4127
|
+
timeout: 6e4,
|
|
4128
|
+
env: childEnv
|
|
2867
4129
|
});
|
|
2868
4130
|
return await importFromDir(packageName, dir);
|
|
2869
4131
|
}
|
|
@@ -2939,10 +4201,10 @@ var install_context_exports = {};
|
|
|
2939
4201
|
__export(install_context_exports, {
|
|
2940
4202
|
createInstallContext: () => createInstallContext
|
|
2941
4203
|
});
|
|
2942
|
-
import
|
|
4204
|
+
import path30 from "path";
|
|
2943
4205
|
function createInstallContext(opts) {
|
|
2944
4206
|
const { pluginName, settingsManager, basePath, instanceRoot } = opts;
|
|
2945
|
-
const dataDir =
|
|
4207
|
+
const dataDir = path30.join(basePath, pluginName, "data");
|
|
2946
4208
|
return {
|
|
2947
4209
|
pluginName,
|
|
2948
4210
|
terminal: createTerminalIO(),
|
|
@@ -2967,16 +4229,17 @@ __export(api_client_exports, {
|
|
|
2967
4229
|
readApiPort: () => readApiPort,
|
|
2968
4230
|
readApiSecret: () => readApiSecret,
|
|
2969
4231
|
removeStalePortFile: () => removeStalePortFile,
|
|
4232
|
+
waitForApiReady: () => waitForApiReady,
|
|
2970
4233
|
waitForPortFile: () => waitForPortFile
|
|
2971
4234
|
});
|
|
2972
|
-
import * as
|
|
2973
|
-
import * as
|
|
4235
|
+
import * as fs32 from "fs";
|
|
4236
|
+
import * as path31 from "path";
|
|
2974
4237
|
import { setTimeout as sleep } from "timers/promises";
|
|
2975
4238
|
function defaultPortFile(root) {
|
|
2976
|
-
return
|
|
4239
|
+
return path31.join(root, "api.port");
|
|
2977
4240
|
}
|
|
2978
4241
|
function defaultSecretFile(root) {
|
|
2979
|
-
return
|
|
4242
|
+
return path31.join(root, "api-secret");
|
|
2980
4243
|
}
|
|
2981
4244
|
async function waitForPortFile(portFilePath, timeoutMs = 5e3, intervalMs = 100) {
|
|
2982
4245
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -2987,10 +4250,30 @@ async function waitForPortFile(portFilePath, timeoutMs = 5e3, intervalMs = 100)
|
|
|
2987
4250
|
}
|
|
2988
4251
|
return readApiPort(portFilePath);
|
|
2989
4252
|
}
|
|
4253
|
+
async function waitForApiReady(instanceRoot, expectedInstanceId, timeoutMs = 1e4, intervalMs = 100) {
|
|
4254
|
+
const deadline = Date.now() + timeoutMs;
|
|
4255
|
+
while (Date.now() < deadline) {
|
|
4256
|
+
const port = readApiPort(void 0, instanceRoot);
|
|
4257
|
+
if (port !== null) {
|
|
4258
|
+
try {
|
|
4259
|
+
const response = await fetch(`http://127.0.0.1:${port}/api/v1/system/health`, {
|
|
4260
|
+
signal: AbortSignal.timeout(Math.max(intervalMs, 250))
|
|
4261
|
+
});
|
|
4262
|
+
if (response.ok) {
|
|
4263
|
+
const body = await response.json();
|
|
4264
|
+
if (body.status === "ok" && body.instanceId === expectedInstanceId) return port;
|
|
4265
|
+
}
|
|
4266
|
+
} catch {
|
|
4267
|
+
}
|
|
4268
|
+
}
|
|
4269
|
+
await sleep(intervalMs);
|
|
4270
|
+
}
|
|
4271
|
+
return null;
|
|
4272
|
+
}
|
|
2990
4273
|
function readApiPort(portFilePath, instanceRoot) {
|
|
2991
4274
|
const filePath = portFilePath ?? defaultPortFile(instanceRoot);
|
|
2992
4275
|
try {
|
|
2993
|
-
const content =
|
|
4276
|
+
const content = fs32.readFileSync(filePath, "utf-8").trim();
|
|
2994
4277
|
const port = parseInt(content, 10);
|
|
2995
4278
|
return isNaN(port) ? null : port;
|
|
2996
4279
|
} catch {
|
|
@@ -3000,7 +4283,7 @@ function readApiPort(portFilePath, instanceRoot) {
|
|
|
3000
4283
|
function readApiSecret(secretFilePath, instanceRoot) {
|
|
3001
4284
|
const filePath = secretFilePath ?? defaultSecretFile(instanceRoot);
|
|
3002
4285
|
try {
|
|
3003
|
-
const content =
|
|
4286
|
+
const content = fs32.readFileSync(filePath, "utf-8").trim();
|
|
3004
4287
|
return content || null;
|
|
3005
4288
|
} catch {
|
|
3006
4289
|
return null;
|
|
@@ -3009,7 +4292,7 @@ function readApiSecret(secretFilePath, instanceRoot) {
|
|
|
3009
4292
|
function removeStalePortFile(portFilePath, instanceRoot) {
|
|
3010
4293
|
const filePath = portFilePath ?? defaultPortFile(instanceRoot);
|
|
3011
4294
|
try {
|
|
3012
|
-
|
|
4295
|
+
fs32.unlinkSync(filePath);
|
|
3013
4296
|
} catch {
|
|
3014
4297
|
}
|
|
3015
4298
|
}
|
|
@@ -3146,8 +4429,8 @@ var init_notification = __esm({
|
|
|
3146
4429
|
});
|
|
3147
4430
|
|
|
3148
4431
|
// src/plugins/file-service/file-service.ts
|
|
3149
|
-
import
|
|
3150
|
-
import
|
|
4432
|
+
import fs34 from "fs";
|
|
4433
|
+
import path34 from "path";
|
|
3151
4434
|
import { OggOpusDecoder } from "ogg-opus-decoder";
|
|
3152
4435
|
import wav from "node-wav";
|
|
3153
4436
|
function classifyMime(mimeType) {
|
|
@@ -3204,14 +4487,14 @@ var init_file_service = __esm({
|
|
|
3204
4487
|
const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1e3;
|
|
3205
4488
|
let removed = 0;
|
|
3206
4489
|
try {
|
|
3207
|
-
const entries = await
|
|
4490
|
+
const entries = await fs34.promises.readdir(this.baseDir, { withFileTypes: true });
|
|
3208
4491
|
for (const entry of entries) {
|
|
3209
4492
|
if (!entry.isDirectory()) continue;
|
|
3210
|
-
const dirPath =
|
|
4493
|
+
const dirPath = path34.join(this.baseDir, entry.name);
|
|
3211
4494
|
try {
|
|
3212
|
-
const stat = await
|
|
4495
|
+
const stat = await fs34.promises.stat(dirPath);
|
|
3213
4496
|
if (stat.mtimeMs < cutoff) {
|
|
3214
|
-
await
|
|
4497
|
+
await fs34.promises.rm(dirPath, { recursive: true, force: true });
|
|
3215
4498
|
removed++;
|
|
3216
4499
|
}
|
|
3217
4500
|
} catch {
|
|
@@ -3230,11 +4513,11 @@ var init_file_service = __esm({
|
|
|
3230
4513
|
* so the user-facing name is not lost.
|
|
3231
4514
|
*/
|
|
3232
4515
|
async saveFile(sessionId, fileName, data, mimeType) {
|
|
3233
|
-
const sessionDir =
|
|
3234
|
-
await
|
|
4516
|
+
const sessionDir = path34.join(this.baseDir, sessionId);
|
|
4517
|
+
await fs34.promises.mkdir(sessionDir, { recursive: true });
|
|
3235
4518
|
const safeName = `${Date.now()}-${fileName.replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
3236
|
-
const filePath =
|
|
3237
|
-
await
|
|
4519
|
+
const filePath = path34.join(sessionDir, safeName);
|
|
4520
|
+
await fs34.promises.writeFile(filePath, data);
|
|
3238
4521
|
return {
|
|
3239
4522
|
type: classifyMime(mimeType),
|
|
3240
4523
|
filePath,
|
|
@@ -3249,14 +4532,14 @@ var init_file_service = __esm({
|
|
|
3249
4532
|
*/
|
|
3250
4533
|
async resolveFile(filePath) {
|
|
3251
4534
|
try {
|
|
3252
|
-
const stat = await
|
|
4535
|
+
const stat = await fs34.promises.stat(filePath);
|
|
3253
4536
|
if (!stat.isFile()) return null;
|
|
3254
|
-
const ext =
|
|
4537
|
+
const ext = path34.extname(filePath).toLowerCase();
|
|
3255
4538
|
const mimeType = EXT_TO_MIME[ext] || "application/octet-stream";
|
|
3256
4539
|
return {
|
|
3257
4540
|
type: classifyMime(mimeType),
|
|
3258
4541
|
filePath,
|
|
3259
|
-
fileName:
|
|
4542
|
+
fileName: path34.basename(filePath),
|
|
3260
4543
|
mimeType,
|
|
3261
4544
|
size: stat.size
|
|
3262
4545
|
};
|
|
@@ -3375,6 +4658,10 @@ function globalErrorHandler(error, _request, reply) {
|
|
|
3375
4658
|
});
|
|
3376
4659
|
return;
|
|
3377
4660
|
}
|
|
4661
|
+
if (error instanceof ConflictError) {
|
|
4662
|
+
reply.status(409).send({ error: { code: error.code, message: error.message, statusCode: 409 } });
|
|
4663
|
+
return;
|
|
4664
|
+
}
|
|
3378
4665
|
if (error instanceof NotFoundError) {
|
|
3379
4666
|
reply.status(404).send({
|
|
3380
4667
|
error: {
|
|
@@ -3413,7 +4700,7 @@ function globalErrorHandler(error, _request, reply) {
|
|
|
3413
4700
|
}
|
|
3414
4701
|
});
|
|
3415
4702
|
}
|
|
3416
|
-
var NotFoundError, BadRequestError, ServiceUnavailableError, AuthError;
|
|
4703
|
+
var NotFoundError, BadRequestError, ConflictError, ServiceUnavailableError, AuthError;
|
|
3417
4704
|
var init_error_handler = __esm({
|
|
3418
4705
|
"src/plugins/api-server/middleware/error-handler.ts"() {
|
|
3419
4706
|
"use strict";
|
|
@@ -3434,6 +4721,14 @@ var init_error_handler = __esm({
|
|
|
3434
4721
|
}
|
|
3435
4722
|
code;
|
|
3436
4723
|
};
|
|
4724
|
+
ConflictError = class extends Error {
|
|
4725
|
+
constructor(code, message) {
|
|
4726
|
+
super(message);
|
|
4727
|
+
this.code = code;
|
|
4728
|
+
this.name = "ConflictError";
|
|
4729
|
+
}
|
|
4730
|
+
code;
|
|
4731
|
+
};
|
|
3437
4732
|
ServiceUnavailableError = class extends Error {
|
|
3438
4733
|
constructor(code, message) {
|
|
3439
4734
|
super(message);
|
|
@@ -3887,8 +5182,8 @@ data: ${JSON.stringify(data)}
|
|
|
3887
5182
|
});
|
|
3888
5183
|
|
|
3889
5184
|
// src/plugins/api-server/static-server.ts
|
|
3890
|
-
import * as
|
|
3891
|
-
import * as
|
|
5185
|
+
import * as fs35 from "fs";
|
|
5186
|
+
import * as path35 from "path";
|
|
3892
5187
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3893
5188
|
var MIME_TYPES, StaticServer;
|
|
3894
5189
|
var init_static_server = __esm({
|
|
@@ -3912,16 +5207,16 @@ var init_static_server = __esm({
|
|
|
3912
5207
|
this.uiDir = uiDir;
|
|
3913
5208
|
if (!this.uiDir) {
|
|
3914
5209
|
const __filename = fileURLToPath2(import.meta.url);
|
|
3915
|
-
const candidate =
|
|
3916
|
-
if (
|
|
5210
|
+
const candidate = path35.resolve(path35.dirname(__filename), "../../ui/dist");
|
|
5211
|
+
if (fs35.existsSync(path35.join(candidate, "index.html"))) {
|
|
3917
5212
|
this.uiDir = candidate;
|
|
3918
5213
|
}
|
|
3919
5214
|
if (!this.uiDir) {
|
|
3920
|
-
const publishCandidate =
|
|
3921
|
-
|
|
5215
|
+
const publishCandidate = path35.resolve(
|
|
5216
|
+
path35.dirname(__filename),
|
|
3922
5217
|
"../ui"
|
|
3923
5218
|
);
|
|
3924
|
-
if (
|
|
5219
|
+
if (fs35.existsSync(path35.join(publishCandidate, "index.html"))) {
|
|
3925
5220
|
this.uiDir = publishCandidate;
|
|
3926
5221
|
}
|
|
3927
5222
|
}
|
|
@@ -3939,23 +5234,23 @@ var init_static_server = __esm({
|
|
|
3939
5234
|
serve(req, res) {
|
|
3940
5235
|
if (!this.uiDir) return false;
|
|
3941
5236
|
const urlPath = (req.url || "/").split("?")[0];
|
|
3942
|
-
const safePath =
|
|
3943
|
-
const filePath =
|
|
3944
|
-
if (!filePath.startsWith(this.uiDir +
|
|
5237
|
+
const safePath = path35.normalize(urlPath);
|
|
5238
|
+
const filePath = path35.join(this.uiDir, safePath);
|
|
5239
|
+
if (!filePath.startsWith(this.uiDir + path35.sep) && filePath !== this.uiDir)
|
|
3945
5240
|
return false;
|
|
3946
5241
|
let realFilePath;
|
|
3947
5242
|
try {
|
|
3948
|
-
realFilePath =
|
|
5243
|
+
realFilePath = fs35.realpathSync(filePath);
|
|
3949
5244
|
} catch {
|
|
3950
5245
|
realFilePath = null;
|
|
3951
5246
|
}
|
|
3952
5247
|
if (realFilePath !== null) {
|
|
3953
|
-
const realUiDir =
|
|
3954
|
-
if (!realFilePath.startsWith(realUiDir +
|
|
5248
|
+
const realUiDir = fs35.realpathSync(this.uiDir);
|
|
5249
|
+
if (!realFilePath.startsWith(realUiDir + path35.sep) && realFilePath !== realUiDir)
|
|
3955
5250
|
return false;
|
|
3956
5251
|
}
|
|
3957
|
-
if (realFilePath !== null &&
|
|
3958
|
-
const ext =
|
|
5252
|
+
if (realFilePath !== null && fs35.existsSync(realFilePath) && fs35.statSync(realFilePath).isFile()) {
|
|
5253
|
+
const ext = path35.extname(filePath);
|
|
3959
5254
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
3960
5255
|
const isHashed = /\.[a-zA-Z0-9]{8,}\.(js|css)$/.test(filePath);
|
|
3961
5256
|
const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "no-cache";
|
|
@@ -3963,16 +5258,16 @@ var init_static_server = __esm({
|
|
|
3963
5258
|
"Content-Type": contentType,
|
|
3964
5259
|
"Cache-Control": cacheControl
|
|
3965
5260
|
});
|
|
3966
|
-
|
|
5261
|
+
fs35.createReadStream(realFilePath).pipe(res);
|
|
3967
5262
|
return true;
|
|
3968
5263
|
}
|
|
3969
|
-
const indexPath =
|
|
3970
|
-
if (
|
|
5264
|
+
const indexPath = path35.join(this.uiDir, "index.html");
|
|
5265
|
+
if (fs35.existsSync(indexPath)) {
|
|
3971
5266
|
res.writeHead(200, {
|
|
3972
5267
|
"Content-Type": "text/html; charset=utf-8",
|
|
3973
5268
|
"Cache-Control": "no-cache"
|
|
3974
5269
|
});
|
|
3975
|
-
|
|
5270
|
+
fs35.createReadStream(indexPath).pipe(res);
|
|
3976
5271
|
return true;
|
|
3977
5272
|
}
|
|
3978
5273
|
return false;
|
|
@@ -4095,8 +5390,8 @@ var init_exports = __esm({
|
|
|
4095
5390
|
});
|
|
4096
5391
|
|
|
4097
5392
|
// src/plugins/context/context-cache.ts
|
|
4098
|
-
import * as
|
|
4099
|
-
import * as
|
|
5393
|
+
import * as fs36 from "fs";
|
|
5394
|
+
import * as path36 from "path";
|
|
4100
5395
|
import * as crypto2 from "crypto";
|
|
4101
5396
|
var DEFAULT_TTL_MS, ContextCache;
|
|
4102
5397
|
var init_context_cache = __esm({
|
|
@@ -4107,7 +5402,7 @@ var init_context_cache = __esm({
|
|
|
4107
5402
|
constructor(cacheDir, ttlMs = DEFAULT_TTL_MS) {
|
|
4108
5403
|
this.cacheDir = cacheDir;
|
|
4109
5404
|
this.ttlMs = ttlMs;
|
|
4110
|
-
|
|
5405
|
+
fs36.mkdirSync(cacheDir, { recursive: true });
|
|
4111
5406
|
}
|
|
4112
5407
|
cacheDir;
|
|
4113
5408
|
ttlMs;
|
|
@@ -4115,23 +5410,23 @@ var init_context_cache = __esm({
|
|
|
4115
5410
|
return crypto2.createHash("sha256").update(`${repoPath}:${queryKey}`).digest("hex").slice(0, 16);
|
|
4116
5411
|
}
|
|
4117
5412
|
filePath(repoPath, queryKey) {
|
|
4118
|
-
return
|
|
5413
|
+
return path36.join(this.cacheDir, `${this.keyHash(repoPath, queryKey)}.json`);
|
|
4119
5414
|
}
|
|
4120
5415
|
get(repoPath, queryKey) {
|
|
4121
5416
|
const fp = this.filePath(repoPath, queryKey);
|
|
4122
5417
|
try {
|
|
4123
|
-
const stat =
|
|
5418
|
+
const stat = fs36.statSync(fp);
|
|
4124
5419
|
if (Date.now() - stat.mtimeMs > this.ttlMs) {
|
|
4125
|
-
|
|
5420
|
+
fs36.unlinkSync(fp);
|
|
4126
5421
|
return null;
|
|
4127
5422
|
}
|
|
4128
|
-
return JSON.parse(
|
|
5423
|
+
return JSON.parse(fs36.readFileSync(fp, "utf-8"));
|
|
4129
5424
|
} catch {
|
|
4130
5425
|
return null;
|
|
4131
5426
|
}
|
|
4132
5427
|
}
|
|
4133
5428
|
set(repoPath, queryKey, result) {
|
|
4134
|
-
|
|
5429
|
+
fs36.writeFileSync(this.filePath(repoPath, queryKey), JSON.stringify(result));
|
|
4135
5430
|
}
|
|
4136
5431
|
};
|
|
4137
5432
|
}
|
|
@@ -5105,8 +6400,8 @@ function formatToolSummary(name, rawInput, displaySummary) {
|
|
|
5105
6400
|
}
|
|
5106
6401
|
if (lowerName === "grep") {
|
|
5107
6402
|
const pattern = args.pattern ?? "";
|
|
5108
|
-
const
|
|
5109
|
-
return pattern ? `\u{1F50D} Grep "${pattern}"${
|
|
6403
|
+
const path37 = args.path ?? "";
|
|
6404
|
+
return pattern ? `\u{1F50D} Grep "${pattern}"${path37 ? ` in ${path37}` : ""}` : `\u{1F527} ${name}`;
|
|
5110
6405
|
}
|
|
5111
6406
|
if (lowerName === "glob") {
|
|
5112
6407
|
const pattern = args.pattern ?? "";
|
|
@@ -5142,8 +6437,8 @@ function formatToolTitle(name, rawInput, displayTitle) {
|
|
|
5142
6437
|
}
|
|
5143
6438
|
if (lowerName === "grep") {
|
|
5144
6439
|
const pattern = args.pattern ?? "";
|
|
5145
|
-
const
|
|
5146
|
-
return pattern ? `"${pattern}"${
|
|
6440
|
+
const path37 = args.path ?? "";
|
|
6441
|
+
return pattern ? `"${pattern}"${path37 ? ` in ${path37}` : ""}` : name;
|
|
5147
6442
|
}
|
|
5148
6443
|
if (lowerName === "glob") {
|
|
5149
6444
|
return String(args.pattern ?? name);
|
|
@@ -6454,11 +7749,23 @@ __export(version_exports, {
|
|
|
6454
7749
|
compareVersions: () => compareVersions,
|
|
6455
7750
|
getCurrentVersion: () => getCurrentVersion,
|
|
6456
7751
|
getLatestVersion: () => getLatestVersion,
|
|
7752
|
+
getUpdateNetwork: () => getUpdateNetwork,
|
|
6457
7753
|
runUpdate: () => runUpdate
|
|
6458
7754
|
});
|
|
6459
7755
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6460
7756
|
import { dirname as dirname12, join as join16, resolve as resolve6 } from "path";
|
|
6461
|
-
import { existsSync as existsSync17, readFileSync as
|
|
7757
|
+
import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
|
|
7758
|
+
function getUpdateNetwork(instanceRoot) {
|
|
7759
|
+
if (!instanceRoot) return {};
|
|
7760
|
+
const proxy = new ProxyService(instanceRoot);
|
|
7761
|
+
const inherited = Object.fromEntries(
|
|
7762
|
+
Object.entries(process.env).filter((entry) => entry[1] !== void 0)
|
|
7763
|
+
);
|
|
7764
|
+
return {
|
|
7765
|
+
fetcher: proxy.createFetch("services.npmUpdate"),
|
|
7766
|
+
environment: proxy.buildChildEnv("services.npmUpdate", inherited)
|
|
7767
|
+
};
|
|
7768
|
+
}
|
|
6462
7769
|
function findPackageJson() {
|
|
6463
7770
|
let dir = dirname12(fileURLToPath3(import.meta.url));
|
|
6464
7771
|
for (let i = 0; i < 5; i++) {
|
|
@@ -6474,15 +7781,15 @@ function getCurrentVersion() {
|
|
|
6474
7781
|
try {
|
|
6475
7782
|
const pkgPath = findPackageJson();
|
|
6476
7783
|
if (!pkgPath) return "0.0.0-dev";
|
|
6477
|
-
const pkg = JSON.parse(
|
|
7784
|
+
const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
|
|
6478
7785
|
return pkg.version;
|
|
6479
7786
|
} catch {
|
|
6480
7787
|
return "0.0.0-dev";
|
|
6481
7788
|
}
|
|
6482
7789
|
}
|
|
6483
|
-
async function getLatestVersion() {
|
|
7790
|
+
async function getLatestVersion(fetcher = globalThis.fetch) {
|
|
6484
7791
|
try {
|
|
6485
|
-
const res = await
|
|
7792
|
+
const res = await fetcher(`https://registry.npmjs.org/${NPM_PACKAGE}/latest`, {
|
|
6486
7793
|
signal: AbortSignal.timeout(5e3)
|
|
6487
7794
|
});
|
|
6488
7795
|
if (!res.ok) return null;
|
|
@@ -6501,12 +7808,13 @@ function compareVersions(current, latest) {
|
|
|
6501
7808
|
}
|
|
6502
7809
|
return 0;
|
|
6503
7810
|
}
|
|
6504
|
-
async function runUpdate() {
|
|
7811
|
+
async function runUpdate(environment) {
|
|
6505
7812
|
const { spawn: spawn4 } = await import("child_process");
|
|
6506
7813
|
return new Promise((resolve7) => {
|
|
6507
7814
|
const child = spawn4("npm", ["install", "-g", `${NPM_PACKAGE}@latest`], {
|
|
6508
7815
|
stdio: "inherit",
|
|
6509
|
-
shell: false
|
|
7816
|
+
shell: false,
|
|
7817
|
+
env: environment
|
|
6510
7818
|
});
|
|
6511
7819
|
const onSignal = () => {
|
|
6512
7820
|
child.kill("SIGTERM");
|
|
@@ -6521,11 +7829,12 @@ async function runUpdate() {
|
|
|
6521
7829
|
});
|
|
6522
7830
|
});
|
|
6523
7831
|
}
|
|
6524
|
-
async function checkAndPromptUpdate() {
|
|
7832
|
+
async function checkAndPromptUpdate(instanceRoot) {
|
|
6525
7833
|
if (process.env.OPENACP_DEV_LOOP || process.env.OPENACP_SKIP_UPDATE_CHECK || !process.stdin.isTTY) return;
|
|
6526
7834
|
const current = getCurrentVersion();
|
|
6527
7835
|
if (current === "0.0.0-dev") return;
|
|
6528
|
-
const
|
|
7836
|
+
const network = getUpdateNetwork(instanceRoot);
|
|
7837
|
+
const latest = await getLatestVersion(network.fetcher);
|
|
6529
7838
|
if (!latest || compareVersions(current, latest) >= 0) return;
|
|
6530
7839
|
console.log(`\x1B[33mUpdate available: v${current} \u2192 v${latest}\x1B[0m`);
|
|
6531
7840
|
const clack3 = await import("@clack/prompts");
|
|
@@ -6535,7 +7844,7 @@ async function checkAndPromptUpdate() {
|
|
|
6535
7844
|
if (clack3.isCancel(yes) || !yes) {
|
|
6536
7845
|
return;
|
|
6537
7846
|
}
|
|
6538
|
-
const ok2 = await runUpdate();
|
|
7847
|
+
const ok2 = await runUpdate(network.environment);
|
|
6539
7848
|
if (ok2) {
|
|
6540
7849
|
console.log(`\x1B[32m\u2713 Updated to v${latest}. Please re-run your command.\x1B[0m`);
|
|
6541
7850
|
process.exit(0);
|
|
@@ -6547,6 +7856,7 @@ var NPM_PACKAGE;
|
|
|
6547
7856
|
var init_version = __esm({
|
|
6548
7857
|
"src/cli/version.ts"() {
|
|
6549
7858
|
"use strict";
|
|
7859
|
+
init_proxy_service();
|
|
6550
7860
|
NPM_PACKAGE = "@n1creator/openacp-cli";
|
|
6551
7861
|
}
|
|
6552
7862
|
});
|
|
@@ -6729,7 +8039,8 @@ async function handleUpdate(ctx, core) {
|
|
|
6729
8039
|
`\u{1F50D} Checking for updates... (current: v${escapeHtml(current)})`,
|
|
6730
8040
|
{ parse_mode: "HTML" }
|
|
6731
8041
|
);
|
|
6732
|
-
const
|
|
8042
|
+
const updateFetch = core.proxyService.createFetch("services.npmUpdate");
|
|
8043
|
+
const latest = await getLatestVersion2(updateFetch);
|
|
6733
8044
|
if (!latest) {
|
|
6734
8045
|
await ctx.api.editMessageText(
|
|
6735
8046
|
ctx.chat.id,
|
|
@@ -6754,7 +8065,11 @@ async function handleUpdate(ctx, core) {
|
|
|
6754
8065
|
`\u2B07\uFE0F Updating v${escapeHtml(current)} \u2192 v${escapeHtml(latest)}...`,
|
|
6755
8066
|
{ parse_mode: "HTML" }
|
|
6756
8067
|
);
|
|
6757
|
-
const
|
|
8068
|
+
const updateEnv = core.proxyService.buildChildEnv(
|
|
8069
|
+
"services.npmUpdate",
|
|
8070
|
+
Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] !== void 0))
|
|
8071
|
+
);
|
|
8072
|
+
const ok2 = await runUpdate2(updateEnv);
|
|
6758
8073
|
if (!ok2) {
|
|
6759
8074
|
await ctx.api.editMessageText(
|
|
6760
8075
|
ctx.chat.id,
|
|
@@ -7341,7 +8656,7 @@ __export(integrate_exports, {
|
|
|
7341
8656
|
listIntegrations: () => listIntegrations,
|
|
7342
8657
|
uninstallIntegration: () => uninstallIntegration
|
|
7343
8658
|
});
|
|
7344
|
-
import { existsSync as existsSync18, mkdirSync as mkdirSync11, readFileSync as
|
|
8659
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync11, readFileSync as readFileSync16, writeFileSync as writeFileSync11, unlinkSync as unlinkSync7, chmodSync, rmdirSync } from "fs";
|
|
7345
8660
|
import { join as join17, dirname as dirname13 } from "path";
|
|
7346
8661
|
import { homedir as homedir4 } from "os";
|
|
7347
8662
|
function isHooksIntegrationSpec(spec) {
|
|
@@ -7504,7 +8819,7 @@ function mergeSettingsJson(settingsPath, hookEvent, hookScriptPath) {
|
|
|
7504
8819
|
const fullPath = expandPath(settingsPath);
|
|
7505
8820
|
let settings = {};
|
|
7506
8821
|
if (existsSync18(fullPath)) {
|
|
7507
|
-
const raw =
|
|
8822
|
+
const raw = readFileSync16(fullPath, "utf-8");
|
|
7508
8823
|
writeFileSync11(`${fullPath}.bak`, raw);
|
|
7509
8824
|
settings = JSON.parse(raw);
|
|
7510
8825
|
}
|
|
@@ -7527,7 +8842,7 @@ function mergeHooksJson(settingsPath, hookEvent, hookScriptPath) {
|
|
|
7527
8842
|
const fullPath = expandPath(settingsPath);
|
|
7528
8843
|
let config = { version: 1 };
|
|
7529
8844
|
if (existsSync18(fullPath)) {
|
|
7530
|
-
const raw =
|
|
8845
|
+
const raw = readFileSync16(fullPath, "utf-8");
|
|
7531
8846
|
writeFileSync11(`${fullPath}.bak`, raw);
|
|
7532
8847
|
config = JSON.parse(raw);
|
|
7533
8848
|
}
|
|
@@ -7545,7 +8860,7 @@ function mergeHooksJson(settingsPath, hookEvent, hookScriptPath) {
|
|
|
7545
8860
|
function removeFromSettingsJson(settingsPath, hookEvent) {
|
|
7546
8861
|
const fullPath = expandPath(settingsPath);
|
|
7547
8862
|
if (!existsSync18(fullPath)) return;
|
|
7548
|
-
const raw =
|
|
8863
|
+
const raw = readFileSync16(fullPath, "utf-8");
|
|
7549
8864
|
const settings = JSON.parse(raw);
|
|
7550
8865
|
const hooks = settings.hooks;
|
|
7551
8866
|
if (!hooks?.[hookEvent]) return;
|
|
@@ -7560,7 +8875,7 @@ function removeFromSettingsJson(settingsPath, hookEvent) {
|
|
|
7560
8875
|
function removeFromHooksJson(settingsPath, hookEvent) {
|
|
7561
8876
|
const fullPath = expandPath(settingsPath);
|
|
7562
8877
|
if (!existsSync18(fullPath)) return;
|
|
7563
|
-
const raw =
|
|
8878
|
+
const raw = readFileSync16(fullPath, "utf-8");
|
|
7564
8879
|
const config = JSON.parse(raw);
|
|
7565
8880
|
const hooks = config.hooks;
|
|
7566
8881
|
if (!hooks?.[hookEvent]) return;
|
|
@@ -7999,6 +9314,7 @@ Downloading... ${bar} ${percent}%`, { parse_mode: "HTML" });
|
|
|
7999
9314
|
};
|
|
8000
9315
|
const result = await catalog.install(nameOrId, progress);
|
|
8001
9316
|
if (result.ok) {
|
|
9317
|
+
core.proxyService.registerScope(`agents.${result.agentKey}`);
|
|
8002
9318
|
const { getAgentCapabilities: getAgentCapabilities2 } = await Promise.resolve().then(() => (init_agent_dependencies(), agent_dependencies_exports));
|
|
8003
9319
|
const caps = getAgentCapabilities2(result.agentKey);
|
|
8004
9320
|
if (caps.integration) {
|
|
@@ -10089,9 +11405,12 @@ __export(validators_exports, {
|
|
|
10089
11405
|
validateBotToken: () => validateBotToken,
|
|
10090
11406
|
validateChatId: () => validateChatId
|
|
10091
11407
|
});
|
|
10092
|
-
|
|
11408
|
+
function safeNetworkError(error) {
|
|
11409
|
+
return redactNetworkSecrets(error instanceof Error ? error.message : String(error));
|
|
11410
|
+
}
|
|
11411
|
+
async function validateBotToken(token, telegramFetch) {
|
|
10093
11412
|
try {
|
|
10094
|
-
const res = await
|
|
11413
|
+
const res = await telegramFetch(`https://api.telegram.org/bot${token}/getMe`);
|
|
10095
11414
|
const data = await res.json();
|
|
10096
11415
|
if (data.ok && data.result) {
|
|
10097
11416
|
return {
|
|
@@ -10102,12 +11421,12 @@ async function validateBotToken(token) {
|
|
|
10102
11421
|
}
|
|
10103
11422
|
return { ok: false, error: data.description || "Invalid token" };
|
|
10104
11423
|
} catch (err) {
|
|
10105
|
-
return { ok: false, error: err
|
|
11424
|
+
return { ok: false, error: safeNetworkError(err) };
|
|
10106
11425
|
}
|
|
10107
11426
|
}
|
|
10108
|
-
async function validateChatId(token, chatId) {
|
|
11427
|
+
async function validateChatId(token, chatId, telegramFetch) {
|
|
10109
11428
|
try {
|
|
10110
|
-
const res = await
|
|
11429
|
+
const res = await telegramFetch(`https://api.telegram.org/bot${token}/getChat`, {
|
|
10111
11430
|
method: "POST",
|
|
10112
11431
|
headers: { "Content-Type": "application/json" },
|
|
10113
11432
|
body: JSON.stringify({ chat_id: chatId })
|
|
@@ -10126,17 +11445,17 @@ async function validateChatId(token, chatId) {
|
|
|
10126
11445
|
isForum: data.result.is_forum === true
|
|
10127
11446
|
};
|
|
10128
11447
|
} catch (err) {
|
|
10129
|
-
return { ok: false, error: err
|
|
11448
|
+
return { ok: false, error: safeNetworkError(err) };
|
|
10130
11449
|
}
|
|
10131
11450
|
}
|
|
10132
|
-
async function validateBotAdmin(token, chatId) {
|
|
11451
|
+
async function validateBotAdmin(token, chatId, telegramFetch) {
|
|
10133
11452
|
try {
|
|
10134
|
-
const meRes = await
|
|
11453
|
+
const meRes = await telegramFetch(`https://api.telegram.org/bot${token}/getMe`);
|
|
10135
11454
|
const meData = await meRes.json();
|
|
10136
11455
|
if (!meData.ok || !meData.result) {
|
|
10137
11456
|
return { ok: false, error: "Could not retrieve bot info" };
|
|
10138
11457
|
}
|
|
10139
|
-
const res = await
|
|
11458
|
+
const res = await telegramFetch(
|
|
10140
11459
|
`https://api.telegram.org/bot${token}/getChatMember`,
|
|
10141
11460
|
{
|
|
10142
11461
|
method: "POST",
|
|
@@ -10167,14 +11486,14 @@ async function validateBotAdmin(token, chatId) {
|
|
|
10167
11486
|
error: `Bot is "${status}" in this group \u2014 it must be an admin. Ask a group admin to go to Group Settings \u2192 Administrators \u2192 add the bot \u2192 tap Save/Done. Note: only existing admins can promote the bot.`
|
|
10168
11487
|
};
|
|
10169
11488
|
} catch (err) {
|
|
10170
|
-
return { ok: false, error: err
|
|
11489
|
+
return { ok: false, error: safeNetworkError(err) };
|
|
10171
11490
|
}
|
|
10172
11491
|
}
|
|
10173
|
-
async function checkTopicsPrerequisites(token, chatId) {
|
|
11492
|
+
async function checkTopicsPrerequisites(token, chatId, telegramFetch) {
|
|
10174
11493
|
const issues = [];
|
|
10175
11494
|
log36.info({ chatId }, "checkTopicsPrerequisites: starting checks");
|
|
10176
11495
|
try {
|
|
10177
|
-
const res = await
|
|
11496
|
+
const res = await telegramFetch(`https://api.telegram.org/bot${token}/getChat`, {
|
|
10178
11497
|
method: "POST",
|
|
10179
11498
|
headers: { "Content-Type": "application/json" },
|
|
10180
11499
|
body: JSON.stringify({ chat_id: chatId })
|
|
@@ -10193,7 +11512,7 @@ async function checkTopicsPrerequisites(token, chatId) {
|
|
|
10193
11512
|
log36.warn({ err, chatId }, "checkTopicsPrerequisites: getChat failed (network error)");
|
|
10194
11513
|
issues.push("\u274C Could not check if Topics are enabled (network error).");
|
|
10195
11514
|
}
|
|
10196
|
-
const adminResult = await validateBotAdmin(token, chatId);
|
|
11515
|
+
const adminResult = await validateBotAdmin(token, chatId, telegramFetch);
|
|
10197
11516
|
log36.info(
|
|
10198
11517
|
{ chatId, adminOk: adminResult.ok, canManageTopics: adminResult.ok ? adminResult.canManageTopics : void 0, error: !adminResult.ok ? adminResult.error : void 0 },
|
|
10199
11518
|
"checkTopicsPrerequisites: validateBotAdmin result"
|
|
@@ -10218,13 +11537,14 @@ var init_validators = __esm({
|
|
|
10218
11537
|
"src/plugins/telegram/validators.ts"() {
|
|
10219
11538
|
"use strict";
|
|
10220
11539
|
init_log();
|
|
11540
|
+
init_network_redaction();
|
|
10221
11541
|
log36 = createChildLogger({ module: "telegram-validators" });
|
|
10222
11542
|
}
|
|
10223
11543
|
});
|
|
10224
11544
|
|
|
10225
11545
|
// src/plugins/telegram/adapter.ts
|
|
10226
11546
|
import { Bot, InputFile, InlineKeyboard as InlineKeyboard13 } from "grammy";
|
|
10227
|
-
function patchedFetch(input2, init) {
|
|
11547
|
+
function patchedFetch(input2, init, delegate) {
|
|
10228
11548
|
if (init?.signal && !(init.signal instanceof AbortSignal)) {
|
|
10229
11549
|
const nativeController = new AbortController();
|
|
10230
11550
|
const polyfillSignal = init.signal;
|
|
@@ -10235,7 +11555,7 @@ function patchedFetch(input2, init) {
|
|
|
10235
11555
|
}
|
|
10236
11556
|
init = { ...init, signal: nativeController.signal };
|
|
10237
11557
|
}
|
|
10238
|
-
return
|
|
11558
|
+
return delegate(input2, init);
|
|
10239
11559
|
}
|
|
10240
11560
|
var log37, TelegramAdapter;
|
|
10241
11561
|
var init_adapter = __esm({
|
|
@@ -10309,6 +11629,10 @@ var init_adapter = __esm({
|
|
|
10309
11629
|
_prerequisiteWatcher = null;
|
|
10310
11630
|
/** Set during normal shutdown so bot.stop() does not trigger a self-restart. */
|
|
10311
11631
|
_stopping = false;
|
|
11632
|
+
unregisterProxyTester;
|
|
11633
|
+
telegramFetch() {
|
|
11634
|
+
return this.core.proxyService.createFetch("channels.telegram");
|
|
11635
|
+
}
|
|
10312
11636
|
/** Returns the configured Telegram supergroup chat ID. */
|
|
10313
11637
|
getChatId() {
|
|
10314
11638
|
return this.telegramConfig.chatId;
|
|
@@ -10381,6 +11705,16 @@ var init_adapter = __esm({
|
|
|
10381
11705
|
this.core = core;
|
|
10382
11706
|
this.telegramConfig = config;
|
|
10383
11707
|
this.saveTopicIds = saveTopicIds;
|
|
11708
|
+
this.unregisterProxyTester = this.core.proxyService.registerRouteTester(
|
|
11709
|
+
"channels.telegram",
|
|
11710
|
+
async (fetcher) => {
|
|
11711
|
+
const response = await fetcher(`https://api.telegram.org/bot${this.telegramConfig.botToken}/getMe`, {
|
|
11712
|
+
signal: AbortSignal.timeout(1e4)
|
|
11713
|
+
});
|
|
11714
|
+
const body = await response.json().catch(() => null);
|
|
11715
|
+
if (!response.ok || !body?.ok) throw new Error("Telegram rejected the candidate proxy route");
|
|
11716
|
+
}
|
|
11717
|
+
);
|
|
10384
11718
|
}
|
|
10385
11719
|
/**
|
|
10386
11720
|
* Set up the grammY bot, register all callback and message handlers, then perform
|
|
@@ -10393,7 +11727,11 @@ var init_adapter = __esm({
|
|
|
10393
11727
|
this.bot = new Bot(this.telegramConfig.botToken, {
|
|
10394
11728
|
client: {
|
|
10395
11729
|
baseFetchConfig: { duplex: "half" },
|
|
10396
|
-
fetch: patchedFetch
|
|
11730
|
+
fetch: (input2, init) => patchedFetch(
|
|
11731
|
+
input2,
|
|
11732
|
+
init,
|
|
11733
|
+
this.telegramFetch()
|
|
11734
|
+
)
|
|
10397
11735
|
}
|
|
10398
11736
|
});
|
|
10399
11737
|
this.fileService = this.core.fileService;
|
|
@@ -10708,7 +12046,8 @@ ${p}` : p;
|
|
|
10708
12046
|
const { checkTopicsPrerequisites: checkTopicsPrerequisites2 } = await Promise.resolve().then(() => (init_validators(), validators_exports));
|
|
10709
12047
|
const prereqResult = await checkTopicsPrerequisites2(
|
|
10710
12048
|
this.telegramConfig.botToken,
|
|
10711
|
-
this.telegramConfig.chatId
|
|
12049
|
+
this.telegramConfig.chatId,
|
|
12050
|
+
this.telegramFetch()
|
|
10712
12051
|
);
|
|
10713
12052
|
if (prereqResult.ok) {
|
|
10714
12053
|
log37.info("Telegram adapter: prerequisites OK, initializing topic-dependent features");
|
|
@@ -10936,7 +12275,8 @@ ${p}` : p;
|
|
|
10936
12275
|
const { checkTopicsPrerequisites: checkTopicsPrerequisites2 } = await Promise.resolve().then(() => (init_validators(), validators_exports));
|
|
10937
12276
|
const result = await checkTopicsPrerequisites2(
|
|
10938
12277
|
this.telegramConfig.botToken,
|
|
10939
|
-
this.telegramConfig.chatId
|
|
12278
|
+
this.telegramConfig.chatId,
|
|
12279
|
+
this.telegramFetch()
|
|
10940
12280
|
);
|
|
10941
12281
|
if (result.ok) {
|
|
10942
12282
|
try {
|
|
@@ -10987,7 +12327,8 @@ OpenACP will automatically retry every 30 seconds. After fixing the issues above
|
|
|
10987
12327
|
const { checkTopicsPrerequisites: checkTopicsPrerequisites2 } = await Promise.resolve().then(() => (init_validators(), validators_exports));
|
|
10988
12328
|
const result = await checkTopicsPrerequisites2(
|
|
10989
12329
|
this.telegramConfig.botToken,
|
|
10990
|
-
this.telegramConfig.chatId
|
|
12330
|
+
this.telegramConfig.chatId,
|
|
12331
|
+
this.telegramFetch()
|
|
10991
12332
|
);
|
|
10992
12333
|
if (result.ok) {
|
|
10993
12334
|
log37.info("Prerequisites met \u2014 completing Telegram adapter initialization");
|
|
@@ -11026,6 +12367,8 @@ System topics have been created. Tap${assistantTopicLink} to get started.`,
|
|
|
11026
12367
|
*/
|
|
11027
12368
|
async stop() {
|
|
11028
12369
|
this._stopping = true;
|
|
12370
|
+
this.unregisterProxyTester?.();
|
|
12371
|
+
this.unregisterProxyTester = void 0;
|
|
11029
12372
|
if (this._prerequisiteWatcher !== null) {
|
|
11030
12373
|
clearTimeout(this._prerequisiteWatcher);
|
|
11031
12374
|
this._prerequisiteWatcher = null;
|
|
@@ -11765,7 +13108,7 @@ Task completed.
|
|
|
11765
13108
|
const file = await this.bot.api.getFile(fileId);
|
|
11766
13109
|
if (!file.file_path) return null;
|
|
11767
13110
|
const url = `https://api.telegram.org/file/bot${this.telegramConfig.botToken}/${file.file_path}`;
|
|
11768
|
-
const response = await
|
|
13111
|
+
const response = await this.telegramFetch()(url);
|
|
11769
13112
|
if (!response.ok) return null;
|
|
11770
13113
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
11771
13114
|
return { buffer, filePath: file.file_path };
|
|
@@ -12697,7 +14040,7 @@ var AgentInstance = class _AgentInstance extends TypedEmitter {
|
|
|
12697
14040
|
*
|
|
12698
14041
|
* Does NOT create a session — callers must follow up with newSession or loadSession.
|
|
12699
14042
|
*/
|
|
12700
|
-
static async spawnSubprocess(agentDef, workingDirectory, allowedPaths = []) {
|
|
14043
|
+
static async spawnSubprocess(agentDef, workingDirectory, allowedPaths = [], environment) {
|
|
12701
14044
|
const instance = new _AgentInstance(agentDef.name);
|
|
12702
14045
|
const resolved = resolveAgentCommand(agentDef.command);
|
|
12703
14046
|
log4.debug(
|
|
@@ -12722,7 +14065,7 @@ var AgentInstance = class _AgentInstance extends TypedEmitter {
|
|
|
12722
14065
|
cwd: workingDirectory,
|
|
12723
14066
|
// envWhitelist from workspace.security.envWhitelist config would extend DEFAULT_ENV_WHITELIST.
|
|
12724
14067
|
// Tracked as follow-up: pass workspace security config through spawn/resume call chain.
|
|
12725
|
-
env: filterEnv(process.env, agentDef.env)
|
|
14068
|
+
env: environment ?? filterEnv(process.env, agentDef.env)
|
|
12726
14069
|
}
|
|
12727
14070
|
);
|
|
12728
14071
|
await new Promise((resolve7, reject) => {
|
|
@@ -12881,10 +14224,10 @@ ${stderr}`
|
|
|
12881
14224
|
* @param mcpServers - Optional MCP server configs to extend agent capabilities
|
|
12882
14225
|
* @param allowedPaths - Extra filesystem paths the agent may access
|
|
12883
14226
|
*/
|
|
12884
|
-
static async spawn(agentDef, workingDirectory, mcpServers, allowedPaths) {
|
|
14227
|
+
static async spawn(agentDef, workingDirectory, mcpServers, allowedPaths, environment) {
|
|
12885
14228
|
log4.debug({ agentName: agentDef.name, command: agentDef.command }, "Spawning agent");
|
|
12886
14229
|
const spawnStart = Date.now();
|
|
12887
|
-
const instance = await _AgentInstance.spawnSubprocess(agentDef, workingDirectory, allowedPaths);
|
|
14230
|
+
const instance = await _AgentInstance.spawnSubprocess(agentDef, workingDirectory, allowedPaths, environment);
|
|
12888
14231
|
await instance.claimForSession(workingDirectory, mcpServers, agentDef.initTimeoutMs);
|
|
12889
14232
|
log4.info(
|
|
12890
14233
|
{
|
|
@@ -12906,13 +14249,14 @@ ${stderr}`
|
|
|
12906
14249
|
*
|
|
12907
14250
|
* @param agentSessionId - The agent-side session ID to restore
|
|
12908
14251
|
*/
|
|
12909
|
-
static async resume(agentDef, workingDirectory, agentSessionId, mcpServers, allowedPaths) {
|
|
14252
|
+
static async resume(agentDef, workingDirectory, agentSessionId, mcpServers, allowedPaths, environment) {
|
|
12910
14253
|
log4.debug({ agentName: agentDef.name, agentSessionId }, "Resuming agent");
|
|
12911
14254
|
const spawnStart = Date.now();
|
|
12912
14255
|
const instance = await _AgentInstance.spawnSubprocess(
|
|
12913
14256
|
agentDef,
|
|
12914
14257
|
workingDirectory,
|
|
12915
|
-
allowedPaths
|
|
14258
|
+
allowedPaths,
|
|
14259
|
+
environment
|
|
12916
14260
|
);
|
|
12917
14261
|
const resolvedMcp = _AgentInstance.mcpManager.resolve(mcpServers);
|
|
12918
14262
|
try {
|
|
@@ -13411,13 +14755,24 @@ function pathListsEqual(a, b) {
|
|
|
13411
14755
|
return true;
|
|
13412
14756
|
}
|
|
13413
14757
|
var AgentManager = class {
|
|
13414
|
-
constructor(catalog) {
|
|
14758
|
+
constructor(catalog, proxyService) {
|
|
13415
14759
|
this.catalog = catalog;
|
|
14760
|
+
this.proxyService = proxyService;
|
|
14761
|
+
if (proxyService?.registerScope) for (const name of Object.keys(catalog.getInstalledEntries())) proxyService.registerScope(`agents.${name}`);
|
|
13416
14762
|
}
|
|
13417
14763
|
catalog;
|
|
14764
|
+
proxyService;
|
|
13418
14765
|
warmEntry = null;
|
|
13419
14766
|
/** In-flight prewarm promise — guards against concurrent prewarm calls. */
|
|
13420
14767
|
warming = null;
|
|
14768
|
+
currentPolicyGeneration() {
|
|
14769
|
+
return typeof this.proxyService?.getPolicyGeneration === "function" ? this.proxyService.getPolicyGeneration() : 0;
|
|
14770
|
+
}
|
|
14771
|
+
childEnv(agentDef, routeName = agentDef.name) {
|
|
14772
|
+
if (!this.proxyService) return void 0;
|
|
14773
|
+
const filtered = filterEnv(process.env, agentDef.env);
|
|
14774
|
+
return this.proxyService.buildAgentEnv(routeName, filtered);
|
|
14775
|
+
}
|
|
13421
14776
|
/** Return definitions for all installed agents. */
|
|
13422
14777
|
getAvailableAgents() {
|
|
13423
14778
|
const installed = this.catalog.getInstalledEntries();
|
|
@@ -13465,10 +14820,12 @@ var AgentManager = class {
|
|
|
13465
14820
|
log5.debug({ agentName }, "prewarm: agent not installed, skipping");
|
|
13466
14821
|
return;
|
|
13467
14822
|
}
|
|
14823
|
+
const policyGeneration = this.currentPolicyGeneration();
|
|
13468
14824
|
this.warming = (async () => {
|
|
13469
14825
|
try {
|
|
13470
|
-
const
|
|
13471
|
-
|
|
14826
|
+
const environment = this.childEnv(agentDef, agentName);
|
|
14827
|
+
const instance = environment ? await AgentInstance.spawnSubprocess(agentDef, workingDir, [...allowedPaths], environment) : await AgentInstance.spawnSubprocess(agentDef, workingDir, [...allowedPaths]);
|
|
14828
|
+
if (this.warmEntry || this.currentPolicyGeneration() !== policyGeneration) {
|
|
13472
14829
|
await instance.destroy().catch(() => {
|
|
13473
14830
|
});
|
|
13474
14831
|
return;
|
|
@@ -13478,7 +14835,8 @@ var AgentManager = class {
|
|
|
13478
14835
|
workingDir,
|
|
13479
14836
|
allowedPaths: [...allowedPaths],
|
|
13480
14837
|
instance,
|
|
13481
|
-
createdAt: Date.now()
|
|
14838
|
+
createdAt: Date.now(),
|
|
14839
|
+
policyGeneration
|
|
13482
14840
|
};
|
|
13483
14841
|
log5.info({ agentName, workingDir }, "Agent warm-pool: instance ready");
|
|
13484
14842
|
} catch (err) {
|
|
@@ -13494,6 +14852,13 @@ var AgentManager = class {
|
|
|
13494
14852
|
* Best-effort — errors are swallowed since shutdown should not fail.
|
|
13495
14853
|
*/
|
|
13496
14854
|
async destroyWarm() {
|
|
14855
|
+
const inFlight = this.warming;
|
|
14856
|
+
if (inFlight) {
|
|
14857
|
+
try {
|
|
14858
|
+
await inFlight;
|
|
14859
|
+
} catch {
|
|
14860
|
+
}
|
|
14861
|
+
}
|
|
13497
14862
|
const entry = this.warmEntry;
|
|
13498
14863
|
this.warmEntry = null;
|
|
13499
14864
|
if (entry) {
|
|
@@ -13517,6 +14882,12 @@ var AgentManager = class {
|
|
|
13517
14882
|
const entry = this.warmEntry;
|
|
13518
14883
|
if (!entry) return null;
|
|
13519
14884
|
if (entry.agentName !== agentName || entry.workingDir !== workingDir) return null;
|
|
14885
|
+
if (entry.policyGeneration !== this.currentPolicyGeneration()) {
|
|
14886
|
+
this.warmEntry = null;
|
|
14887
|
+
entry.instance.destroy().catch(() => {
|
|
14888
|
+
});
|
|
14889
|
+
return null;
|
|
14890
|
+
}
|
|
13520
14891
|
if (!pathListsEqual(entry.allowedPaths, allowedPaths)) {
|
|
13521
14892
|
log5.debug(
|
|
13522
14893
|
{ agentName, workingDir },
|
|
@@ -13576,7 +14947,8 @@ var AgentManager = class {
|
|
|
13576
14947
|
});
|
|
13577
14948
|
}
|
|
13578
14949
|
}
|
|
13579
|
-
|
|
14950
|
+
const environment = this.childEnv(agentDef, agentName);
|
|
14951
|
+
return environment ? AgentInstance.spawn(agentDef, workingDirectory, void 0, allowedPaths, environment) : AgentInstance.spawn(agentDef, workingDirectory, void 0, allowedPaths);
|
|
13580
14952
|
}
|
|
13581
14953
|
/**
|
|
13582
14954
|
* Spawn a subprocess and resume an existing agent session.
|
|
@@ -13591,7 +14963,8 @@ var AgentManager = class {
|
|
|
13591
14963
|
`Agent "${agentName}" is not installed. Run "openacp agents install ${agentName}" to add it.`
|
|
13592
14964
|
);
|
|
13593
14965
|
}
|
|
13594
|
-
|
|
14966
|
+
const environment = this.childEnv(agentDef, agentName);
|
|
14967
|
+
return environment ? AgentInstance.resume(agentDef, workingDirectory, agentSessionId, void 0, allowedPaths, environment) : AgentInstance.resume(agentDef, workingDirectory, agentSessionId, void 0, allowedPaths);
|
|
13595
14968
|
}
|
|
13596
14969
|
};
|
|
13597
14970
|
|
|
@@ -14560,7 +15933,7 @@ ${result.text}` : result.text;
|
|
|
14560
15933
|
}
|
|
14561
15934
|
this.queue.clear();
|
|
14562
15935
|
await this.agentInstance.destroy();
|
|
14563
|
-
closeSessionLogger(this.log);
|
|
15936
|
+
await closeSessionLogger(this.log);
|
|
14564
15937
|
}
|
|
14565
15938
|
};
|
|
14566
15939
|
|
|
@@ -15611,12 +16984,12 @@ var SessionBridge = class {
|
|
|
15611
16984
|
break;
|
|
15612
16985
|
case "image_content": {
|
|
15613
16986
|
if (this.deps.fileService) {
|
|
15614
|
-
const
|
|
16987
|
+
const fs37 = this.deps.fileService;
|
|
15615
16988
|
const sid = this.session.id;
|
|
15616
16989
|
const { data, mimeType } = event;
|
|
15617
16990
|
const buffer = Buffer.from(data, "base64");
|
|
15618
|
-
const ext =
|
|
15619
|
-
|
|
16991
|
+
const ext = fs37.extensionFromMime(mimeType);
|
|
16992
|
+
fs37.saveFile(sid, `agent-image${ext}`, buffer, mimeType).then((att) => {
|
|
15620
16993
|
this.sendMessage(sid, {
|
|
15621
16994
|
type: "attachment",
|
|
15622
16995
|
text: "",
|
|
@@ -15628,12 +17001,12 @@ var SessionBridge = class {
|
|
|
15628
17001
|
}
|
|
15629
17002
|
case "audio_content": {
|
|
15630
17003
|
if (this.deps.fileService) {
|
|
15631
|
-
const
|
|
17004
|
+
const fs37 = this.deps.fileService;
|
|
15632
17005
|
const sid = this.session.id;
|
|
15633
17006
|
const { data, mimeType } = event;
|
|
15634
17007
|
const buffer = Buffer.from(data, "base64");
|
|
15635
|
-
const ext =
|
|
15636
|
-
|
|
17008
|
+
const ext = fs37.extensionFromMime(mimeType);
|
|
17009
|
+
fs37.saveFile(sid, `agent-audio${ext}`, buffer, mimeType).then((att) => {
|
|
15637
17010
|
this.sendMessage(sid, {
|
|
15638
17011
|
type: "attachment",
|
|
15639
17012
|
text: "",
|
|
@@ -16245,7 +17618,7 @@ var SessionFactory = class {
|
|
|
16245
17618
|
};
|
|
16246
17619
|
|
|
16247
17620
|
// src/core/core.ts
|
|
16248
|
-
import
|
|
17621
|
+
import path16 from "path";
|
|
16249
17622
|
import { nanoid as nanoid3 } from "nanoid";
|
|
16250
17623
|
|
|
16251
17624
|
// src/core/sessions/session-store.ts
|
|
@@ -16634,17 +18007,21 @@ var log12 = createChildLogger({ module: "agent-catalog" });
|
|
|
16634
18007
|
var REGISTRY_URL = "https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json";
|
|
16635
18008
|
var DEFAULT_TTL_HOURS = 24;
|
|
16636
18009
|
var AgentCatalog = class {
|
|
18010
|
+
constructor(store, cachePath, agentsDir, scopedFetch = globalThis.fetch, registryUrl = REGISTRY_URL) {
|
|
18011
|
+
this.scopedFetch = scopedFetch;
|
|
18012
|
+
this.registryUrl = registryUrl;
|
|
18013
|
+
this.store = store;
|
|
18014
|
+
this.cachePath = cachePath;
|
|
18015
|
+
this.agentsDir = agentsDir;
|
|
18016
|
+
}
|
|
18017
|
+
scopedFetch;
|
|
18018
|
+
registryUrl;
|
|
16637
18019
|
store;
|
|
16638
18020
|
/** Agents available in the remote registry (cached in memory after load). */
|
|
16639
18021
|
registryAgents = [];
|
|
16640
18022
|
cachePath;
|
|
16641
18023
|
/** Directory where binary agent archives are extracted to. */
|
|
16642
18024
|
agentsDir;
|
|
16643
|
-
constructor(store, cachePath, agentsDir) {
|
|
16644
|
-
this.store = store;
|
|
16645
|
-
this.cachePath = cachePath;
|
|
16646
|
-
this.agentsDir = agentsDir;
|
|
16647
|
-
}
|
|
16648
18025
|
/**
|
|
16649
18026
|
* Load installed agents from disk and hydrate the registry from cache/snapshot.
|
|
16650
18027
|
*
|
|
@@ -16661,7 +18038,7 @@ var AgentCatalog = class {
|
|
|
16661
18038
|
async fetchRegistry() {
|
|
16662
18039
|
try {
|
|
16663
18040
|
log12.info("Fetching agent registry from CDN...");
|
|
16664
|
-
const response = await
|
|
18041
|
+
const response = await this.scopedFetch(this.registryUrl);
|
|
16665
18042
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
16666
18043
|
const data = await response.json();
|
|
16667
18044
|
this.registryAgents = data.agents ?? [];
|
|
@@ -16787,7 +18164,7 @@ var AgentCatalog = class {
|
|
|
16787
18164
|
await progress?.onError(msg);
|
|
16788
18165
|
return { ok: false, agentKey, error: msg };
|
|
16789
18166
|
}
|
|
16790
|
-
return installAgent(agent, this.store, progress, this.agentsDir);
|
|
18167
|
+
return installAgent(agent, this.store, progress, this.agentsDir, this.scopedFetch);
|
|
16791
18168
|
}
|
|
16792
18169
|
/**
|
|
16793
18170
|
* Register an agent directly into the catalog store without going through
|
|
@@ -18361,6 +19738,7 @@ function registerCoreMenuItems(registry) {
|
|
|
18361
19738
|
init_log();
|
|
18362
19739
|
init_native_stt();
|
|
18363
19740
|
init_events();
|
|
19741
|
+
init_proxy_service();
|
|
18364
19742
|
var log17 = createChildLogger({ module: "core" });
|
|
18365
19743
|
var OpenACPCore = class {
|
|
18366
19744
|
configManager;
|
|
@@ -18383,6 +19761,8 @@ var OpenACPCore = class {
|
|
|
18383
19761
|
menuRegistry = new MenuRegistry();
|
|
18384
19762
|
assistantRegistry = new AssistantRegistry();
|
|
18385
19763
|
assistantManager;
|
|
19764
|
+
/** Scoped proxy policy shared by transports, agent spawns, API and commands. */
|
|
19765
|
+
proxyService;
|
|
18386
19766
|
// Services (security, notifications, speech, etc.) are provided by plugins that
|
|
18387
19767
|
// register during boot. Core accesses them lazily via ServiceRegistry so it doesn't
|
|
18388
19768
|
// need compile-time dependencies on plugin implementations.
|
|
@@ -18430,13 +19810,21 @@ var OpenACPCore = class {
|
|
|
18430
19810
|
this.configManager = configManager;
|
|
18431
19811
|
this.instanceContext = ctx;
|
|
18432
19812
|
const config = configManager.get();
|
|
19813
|
+
this.proxyService = new ProxyService(ctx.root);
|
|
18433
19814
|
this.agentCatalog = new AgentCatalog(
|
|
18434
19815
|
new AgentStore(ctx.paths.agents),
|
|
18435
19816
|
ctx.paths.registryCache,
|
|
18436
|
-
ctx.paths.agentsDir
|
|
19817
|
+
ctx.paths.agentsDir,
|
|
19818
|
+
this.proxyService.createFetch("services.agentRegistry")
|
|
18437
19819
|
);
|
|
18438
19820
|
this.agentCatalog.load();
|
|
18439
|
-
this.agentManager = new AgentManager(this.agentCatalog);
|
|
19821
|
+
this.agentManager = new AgentManager(this.agentCatalog, this.proxyService);
|
|
19822
|
+
this.proxyService.onRouteChanged(async (scope) => {
|
|
19823
|
+
if (scope === "global" || scope === "agents.default" || scope.startsWith("agents.")) {
|
|
19824
|
+
await this.agentManager.destroyWarm();
|
|
19825
|
+
log17.info({ scope }, "Proxy route changed; idle agent warm pool invalidated");
|
|
19826
|
+
}
|
|
19827
|
+
});
|
|
18440
19828
|
const storePath = ctx.paths.sessions;
|
|
18441
19829
|
this.sessionStore = new JsonFileSessionStore(
|
|
18442
19830
|
storePath,
|
|
@@ -18465,6 +19853,7 @@ var OpenACPCore = class {
|
|
|
18465
19853
|
instanceRoot: ctx.root,
|
|
18466
19854
|
log: createChildLogger({ module: "plugin" })
|
|
18467
19855
|
});
|
|
19856
|
+
this.lifecycleManager.serviceRegistry.register("proxy", this.proxyService, "@openacp/core");
|
|
18468
19857
|
this.sessionFactory.middlewareChain = this.lifecycleManager.middlewareChain;
|
|
18469
19858
|
this.sessionManager.middlewareChain = this.lifecycleManager.middlewareChain;
|
|
18470
19859
|
this.sessionFactory.sessionStore = this.sessionStore;
|
|
@@ -18512,7 +19901,7 @@ var OpenACPCore = class {
|
|
|
18512
19901
|
}
|
|
18513
19902
|
);
|
|
18514
19903
|
registerCoreMenuItems(this.menuRegistry);
|
|
18515
|
-
this.assistantRegistry.setInstanceRoot(
|
|
19904
|
+
this.assistantRegistry.setInstanceRoot(path16.dirname(ctx.root));
|
|
18516
19905
|
this.assistantRegistry.register(createSessionsSection(this));
|
|
18517
19906
|
this.assistantRegistry.register(createAgentsSection(this));
|
|
18518
19907
|
this.assistantRegistry.register(createConfigSection(this));
|
|
@@ -19332,29 +20721,71 @@ init_doctor();
|
|
|
19332
20721
|
init_config_registry();
|
|
19333
20722
|
|
|
19334
20723
|
// src/core/config/config-editor.ts
|
|
19335
|
-
import * as
|
|
20724
|
+
import * as path32 from "path";
|
|
19336
20725
|
import * as clack2 from "@clack/prompts";
|
|
19337
20726
|
|
|
19338
20727
|
// src/cli/autostart.ts
|
|
19339
20728
|
init_log();
|
|
19340
20729
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
19341
|
-
import * as
|
|
19342
|
-
import * as
|
|
20730
|
+
import * as fs27 from "fs";
|
|
20731
|
+
import * as path25 from "path";
|
|
19343
20732
|
import * as os7 from "os";
|
|
19344
20733
|
var log19 = createChildLogger({ module: "autostart" });
|
|
19345
|
-
var LEGACY_LAUNCHD_PLIST_PATH =
|
|
19346
|
-
var LEGACY_SYSTEMD_SERVICE_PATH =
|
|
20734
|
+
var LEGACY_LAUNCHD_PLIST_PATH = path25.join(os7.homedir(), "Library", "LaunchAgents", "com.openacp.daemon.plist");
|
|
20735
|
+
var LEGACY_SYSTEMD_SERVICE_PATH = path25.join(os7.homedir(), ".config", "systemd", "user", "openacp.service");
|
|
19347
20736
|
function getLaunchdLabel(instanceId) {
|
|
19348
20737
|
return `com.openacp.daemon.${instanceId}`;
|
|
19349
20738
|
}
|
|
19350
20739
|
function getLaunchdPlistPath(instanceId) {
|
|
19351
|
-
return
|
|
20740
|
+
return path25.join(os7.homedir(), "Library", "LaunchAgents", `${getLaunchdLabel(instanceId)}.plist`);
|
|
19352
20741
|
}
|
|
19353
20742
|
function getSystemdServiceName(instanceId) {
|
|
19354
20743
|
return `openacp-${instanceId}`;
|
|
19355
20744
|
}
|
|
19356
20745
|
function getSystemdServicePath(instanceId) {
|
|
19357
|
-
return
|
|
20746
|
+
return path25.join(os7.homedir(), ".config", "systemd", "user", `${getSystemdServiceName(instanceId)}.service`);
|
|
20747
|
+
}
|
|
20748
|
+
function getAutoStartState(instanceId) {
|
|
20749
|
+
if (process.platform === "linux") {
|
|
20750
|
+
const installed = fs27.existsSync(getSystemdServicePath(instanceId));
|
|
20751
|
+
if (!installed) return { installed: false, manager: null, active: false };
|
|
20752
|
+
try {
|
|
20753
|
+
const output = execFileSync6("systemctl", [
|
|
20754
|
+
"--user",
|
|
20755
|
+
"show",
|
|
20756
|
+
getSystemdServiceName(instanceId),
|
|
20757
|
+
"--property=ActiveState",
|
|
20758
|
+
"--property=MainPID"
|
|
20759
|
+
], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
20760
|
+
const properties = Object.fromEntries(output.trim().split(/\r?\n/).map((line) => {
|
|
20761
|
+
const index = line.indexOf("=");
|
|
20762
|
+
return index >= 0 ? [line.slice(0, index), line.slice(index + 1)] : [line, ""];
|
|
20763
|
+
}));
|
|
20764
|
+
const active = properties.ActiveState === "active";
|
|
20765
|
+
const pid = Number(properties.MainPID);
|
|
20766
|
+
return { installed: true, manager: "systemd", active, ...pid > 0 ? { pid } : {} };
|
|
20767
|
+
} catch {
|
|
20768
|
+
return { installed: true, manager: "systemd", active: false };
|
|
20769
|
+
}
|
|
20770
|
+
}
|
|
20771
|
+
if (process.platform === "darwin") {
|
|
20772
|
+
const installed = fs27.existsSync(getLaunchdPlistPath(instanceId));
|
|
20773
|
+
if (!installed) return { installed: false, manager: null, active: false };
|
|
20774
|
+
try {
|
|
20775
|
+
const uid = process.getuid();
|
|
20776
|
+
const output = execFileSync6("launchctl", ["print", `gui/${uid}/${getLaunchdLabel(instanceId)}`], {
|
|
20777
|
+
encoding: "utf8",
|
|
20778
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
20779
|
+
});
|
|
20780
|
+
const active = /^\s*state\s*=\s*running\s*$/m.test(output);
|
|
20781
|
+
const pidMatch = output.match(/^\s*pid\s*=\s*(\d+)\s*$/m);
|
|
20782
|
+
const pid = pidMatch ? Number(pidMatch[1]) : 0;
|
|
20783
|
+
return { installed: true, manager: "launchd", active, ...pid > 0 ? { pid } : {} };
|
|
20784
|
+
} catch {
|
|
20785
|
+
return { installed: true, manager: "launchd", active: false };
|
|
20786
|
+
}
|
|
20787
|
+
}
|
|
20788
|
+
return { installed: false, manager: null, active: false };
|
|
19358
20789
|
}
|
|
19359
20790
|
function isAutoStartSupported() {
|
|
19360
20791
|
return process.platform === "darwin" || process.platform === "linux";
|
|
@@ -19368,7 +20799,7 @@ function escapeSystemdValue(str) {
|
|
|
19368
20799
|
}
|
|
19369
20800
|
function generateLaunchdPlist(nodePath, cliPath, logDir2, instanceRoot, instanceId) {
|
|
19370
20801
|
const label = getLaunchdLabel(instanceId);
|
|
19371
|
-
const logFile =
|
|
20802
|
+
const logFile = path25.join(logDir2, "openacp.log");
|
|
19372
20803
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
19373
20804
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
19374
20805
|
<plist version="1.0">
|
|
@@ -19385,6 +20816,8 @@ function generateLaunchdPlist(nodePath, cliPath, logDir2, instanceRoot, instance
|
|
|
19385
20816
|
<dict>
|
|
19386
20817
|
<key>OPENACP_INSTANCE_ROOT</key>
|
|
19387
20818
|
<string>${escapeXml(instanceRoot)}</string>
|
|
20819
|
+
<key>OPENACP_SUPERVISOR</key>
|
|
20820
|
+
<string>launchd</string>
|
|
19388
20821
|
</dict>
|
|
19389
20822
|
<key>RunAtLoad</key>
|
|
19390
20823
|
<true/>
|
|
@@ -19409,6 +20842,7 @@ Description=OpenACP Daemon (${instanceId})
|
|
|
19409
20842
|
[Service]
|
|
19410
20843
|
ExecStart=${escapeSystemdValue(nodePath)} ${escapeSystemdValue(cliPath)} --daemon-child
|
|
19411
20844
|
Environment=OPENACP_INSTANCE_ROOT=${escapeSystemdValue(instanceRoot)}
|
|
20845
|
+
Environment=OPENACP_SUPERVISOR=systemd
|
|
19412
20846
|
Restart=on-failure
|
|
19413
20847
|
|
|
19414
20848
|
[Install]
|
|
@@ -19417,25 +20851,25 @@ WantedBy=default.target
|
|
|
19417
20851
|
`;
|
|
19418
20852
|
}
|
|
19419
20853
|
function migrateLegacy() {
|
|
19420
|
-
if (process.platform === "darwin" &&
|
|
20854
|
+
if (process.platform === "darwin" && fs27.existsSync(LEGACY_LAUNCHD_PLIST_PATH)) {
|
|
19421
20855
|
try {
|
|
19422
20856
|
const uid = process.getuid();
|
|
19423
20857
|
execFileSync6("launchctl", ["bootout", `gui/${uid}`, "com.openacp.daemon"], { stdio: "pipe" });
|
|
19424
20858
|
} catch {
|
|
19425
20859
|
}
|
|
19426
20860
|
try {
|
|
19427
|
-
|
|
20861
|
+
fs27.unlinkSync(LEGACY_LAUNCHD_PLIST_PATH);
|
|
19428
20862
|
} catch {
|
|
19429
20863
|
}
|
|
19430
20864
|
log19.info("Removed legacy single-instance LaunchAgent");
|
|
19431
20865
|
}
|
|
19432
|
-
if (process.platform === "linux" &&
|
|
20866
|
+
if (process.platform === "linux" && fs27.existsSync(LEGACY_SYSTEMD_SERVICE_PATH)) {
|
|
19433
20867
|
try {
|
|
19434
20868
|
execFileSync6("systemctl", ["--user", "disable", "openacp"], { stdio: "pipe" });
|
|
19435
20869
|
} catch {
|
|
19436
20870
|
}
|
|
19437
20871
|
try {
|
|
19438
|
-
|
|
20872
|
+
fs27.unlinkSync(LEGACY_SYSTEMD_SERVICE_PATH);
|
|
19439
20873
|
} catch {
|
|
19440
20874
|
}
|
|
19441
20875
|
try {
|
|
@@ -19450,23 +20884,54 @@ function installAutoStart(logDir2, instanceRoot, instanceId) {
|
|
|
19450
20884
|
return { success: false, error: "Auto-start not supported on this platform" };
|
|
19451
20885
|
}
|
|
19452
20886
|
const nodePath = process.execPath;
|
|
19453
|
-
const cliPath =
|
|
19454
|
-
const resolvedLogDir = logDir2.startsWith("~") ?
|
|
20887
|
+
const cliPath = path25.resolve(process.argv[1]);
|
|
20888
|
+
const resolvedLogDir = logDir2.startsWith("~") ? path25.join(os7.homedir(), logDir2.slice(1)) : logDir2;
|
|
19455
20889
|
try {
|
|
19456
20890
|
migrateLegacy();
|
|
19457
20891
|
if (process.platform === "darwin") {
|
|
19458
20892
|
const plistPath = getLaunchdPlistPath(instanceId);
|
|
19459
20893
|
const plist = generateLaunchdPlist(nodePath, cliPath, resolvedLogDir, instanceRoot, instanceId);
|
|
19460
|
-
const dir =
|
|
19461
|
-
|
|
19462
|
-
fs25.writeFileSync(plistPath, plist);
|
|
20894
|
+
const dir = path25.dirname(plistPath);
|
|
20895
|
+
fs27.mkdirSync(dir, { recursive: true });
|
|
19463
20896
|
const uid = process.getuid();
|
|
19464
20897
|
const domain = `gui/${uid}`;
|
|
20898
|
+
const previous = fs27.existsSync(plistPath) ? fs27.readFileSync(plistPath, "utf8") : void 0;
|
|
20899
|
+
const previousState = getAutoStartState(instanceId);
|
|
20900
|
+
const tmp = `${plistPath}.${process.pid}.tmp`;
|
|
20901
|
+
let replaced = false;
|
|
20902
|
+
let bootedOut = false;
|
|
19465
20903
|
try {
|
|
19466
|
-
|
|
19467
|
-
|
|
20904
|
+
fs27.writeFileSync(tmp, plist, { mode: 384 });
|
|
20905
|
+
execFileSync6("plutil", ["-lint", tmp], { stdio: "pipe" });
|
|
20906
|
+
fs27.renameSync(tmp, plistPath);
|
|
20907
|
+
replaced = true;
|
|
20908
|
+
if (previousState.active) {
|
|
20909
|
+
execFileSync6("launchctl", ["bootout", domain, plistPath], { stdio: "pipe" });
|
|
20910
|
+
bootedOut = true;
|
|
20911
|
+
}
|
|
20912
|
+
execFileSync6("launchctl", ["bootstrap", domain, plistPath], { stdio: "pipe" });
|
|
20913
|
+
} catch (error) {
|
|
20914
|
+
try {
|
|
20915
|
+
fs27.rmSync(tmp, { force: true });
|
|
20916
|
+
} catch {
|
|
20917
|
+
}
|
|
20918
|
+
if (replaced) {
|
|
20919
|
+
let rollbackBootedOut = false;
|
|
20920
|
+
try {
|
|
20921
|
+
execFileSync6("launchctl", ["bootout", domain, plistPath], { stdio: "pipe" });
|
|
20922
|
+
rollbackBootedOut = true;
|
|
20923
|
+
} catch {
|
|
20924
|
+
}
|
|
20925
|
+
if (previous === void 0) fs27.rmSync(plistPath, { force: true });
|
|
20926
|
+
else {
|
|
20927
|
+
const rollbackTmp = `${plistPath}.${process.pid}.rollback.tmp`;
|
|
20928
|
+
fs27.writeFileSync(rollbackTmp, previous, { mode: 384 });
|
|
20929
|
+
fs27.renameSync(rollbackTmp, plistPath);
|
|
20930
|
+
if (previousState.active || bootedOut || rollbackBootedOut) execFileSync6("launchctl", ["bootstrap", domain, plistPath], { stdio: "pipe" });
|
|
20931
|
+
}
|
|
20932
|
+
}
|
|
20933
|
+
throw error;
|
|
19468
20934
|
}
|
|
19469
|
-
execFileSync6("launchctl", ["bootstrap", domain, plistPath], { stdio: "pipe" });
|
|
19470
20935
|
log19.info({ instanceId }, "LaunchAgent installed");
|
|
19471
20936
|
return { success: true };
|
|
19472
20937
|
}
|
|
@@ -19474,11 +20939,24 @@ function installAutoStart(logDir2, instanceRoot, instanceId) {
|
|
|
19474
20939
|
const servicePath = getSystemdServicePath(instanceId);
|
|
19475
20940
|
const serviceName = getSystemdServiceName(instanceId);
|
|
19476
20941
|
const unit = generateSystemdUnit(nodePath, cliPath, instanceRoot, instanceId);
|
|
19477
|
-
const dir =
|
|
19478
|
-
|
|
19479
|
-
|
|
19480
|
-
|
|
19481
|
-
|
|
20942
|
+
const dir = path25.dirname(servicePath);
|
|
20943
|
+
fs27.mkdirSync(dir, { recursive: true });
|
|
20944
|
+
const previous = fs27.existsSync(servicePath) ? fs27.readFileSync(servicePath, "utf8") : void 0;
|
|
20945
|
+
const tmp = `${servicePath}.${process.pid}.tmp`;
|
|
20946
|
+
fs27.writeFileSync(tmp, unit, { mode: 384 });
|
|
20947
|
+
fs27.renameSync(tmp, servicePath);
|
|
20948
|
+
try {
|
|
20949
|
+
execFileSync6("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
|
|
20950
|
+
execFileSync6("systemctl", ["--user", "enable", serviceName], { stdio: "pipe" });
|
|
20951
|
+
} catch (error) {
|
|
20952
|
+
if (previous === void 0) fs27.unlinkSync(servicePath);
|
|
20953
|
+
else fs27.writeFileSync(servicePath, previous);
|
|
20954
|
+
try {
|
|
20955
|
+
execFileSync6("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
|
|
20956
|
+
} catch {
|
|
20957
|
+
}
|
|
20958
|
+
throw error;
|
|
20959
|
+
}
|
|
19482
20960
|
log19.info({ instanceId }, "systemd user service installed");
|
|
19483
20961
|
return { success: true };
|
|
19484
20962
|
}
|
|
@@ -19496,13 +20974,13 @@ function uninstallAutoStart(instanceId) {
|
|
|
19496
20974
|
try {
|
|
19497
20975
|
if (process.platform === "darwin") {
|
|
19498
20976
|
const plistPath = getLaunchdPlistPath(instanceId);
|
|
19499
|
-
if (
|
|
20977
|
+
if (fs27.existsSync(plistPath)) {
|
|
19500
20978
|
const uid = process.getuid();
|
|
19501
20979
|
try {
|
|
19502
20980
|
execFileSync6("launchctl", ["bootout", `gui/${uid}`, plistPath], { stdio: "pipe" });
|
|
19503
20981
|
} catch {
|
|
19504
20982
|
}
|
|
19505
|
-
|
|
20983
|
+
fs27.unlinkSync(plistPath);
|
|
19506
20984
|
log19.info({ instanceId }, "LaunchAgent removed");
|
|
19507
20985
|
}
|
|
19508
20986
|
return { success: true };
|
|
@@ -19510,12 +20988,12 @@ function uninstallAutoStart(instanceId) {
|
|
|
19510
20988
|
if (process.platform === "linux") {
|
|
19511
20989
|
const servicePath = getSystemdServicePath(instanceId);
|
|
19512
20990
|
const serviceName = getSystemdServiceName(instanceId);
|
|
19513
|
-
if (
|
|
20991
|
+
if (fs27.existsSync(servicePath)) {
|
|
19514
20992
|
try {
|
|
19515
20993
|
execFileSync6("systemctl", ["--user", "disable", serviceName], { stdio: "pipe" });
|
|
19516
20994
|
} catch {
|
|
19517
20995
|
}
|
|
19518
|
-
|
|
20996
|
+
fs27.unlinkSync(servicePath);
|
|
19519
20997
|
execFileSync6("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
|
|
19520
20998
|
log19.info({ instanceId }, "systemd user service removed");
|
|
19521
20999
|
}
|
|
@@ -19530,10 +21008,10 @@ function uninstallAutoStart(instanceId) {
|
|
|
19530
21008
|
}
|
|
19531
21009
|
function isAutoStartInstalled(instanceId) {
|
|
19532
21010
|
if (process.platform === "darwin") {
|
|
19533
|
-
return
|
|
21011
|
+
return fs27.existsSync(getLaunchdPlistPath(instanceId));
|
|
19534
21012
|
}
|
|
19535
21013
|
if (process.platform === "linux") {
|
|
19536
|
-
return
|
|
21014
|
+
return fs27.existsSync(getSystemdServicePath(instanceId));
|
|
19537
21015
|
}
|
|
19538
21016
|
return false;
|
|
19539
21017
|
}
|
|
@@ -19542,25 +21020,25 @@ function isAutoStartInstalled(instanceId) {
|
|
|
19542
21020
|
init_instance_context();
|
|
19543
21021
|
init_instance_registry();
|
|
19544
21022
|
init_log();
|
|
19545
|
-
import
|
|
19546
|
-
import
|
|
21023
|
+
import fs30 from "fs";
|
|
21024
|
+
import path28 from "path";
|
|
19547
21025
|
var log20 = createChildLogger({ module: "resolve-instance-id" });
|
|
19548
21026
|
function resolveInstanceId(instanceRoot) {
|
|
19549
21027
|
try {
|
|
19550
|
-
const configPath =
|
|
19551
|
-
const raw = JSON.parse(
|
|
21028
|
+
const configPath = path28.join(instanceRoot, "config.json");
|
|
21029
|
+
const raw = JSON.parse(fs30.readFileSync(configPath, "utf-8"));
|
|
19552
21030
|
if (raw.id && typeof raw.id === "string") return raw.id;
|
|
19553
21031
|
} catch {
|
|
19554
21032
|
}
|
|
19555
21033
|
try {
|
|
19556
|
-
const reg = new InstanceRegistry(
|
|
21034
|
+
const reg = new InstanceRegistry(path28.join(getGlobalRoot(), "instances.json"));
|
|
19557
21035
|
reg.load();
|
|
19558
21036
|
const entry = reg.getByRoot(instanceRoot);
|
|
19559
21037
|
if (entry?.id) return entry.id;
|
|
19560
21038
|
} catch (err) {
|
|
19561
21039
|
log20.debug({ err: err.message, instanceRoot }, "Could not read instance registry, using fallback id");
|
|
19562
21040
|
}
|
|
19563
|
-
return
|
|
21041
|
+
return path28.basename(path28.dirname(instanceRoot)).replace(/[^a-zA-Z0-9-]/g, "-") || "default";
|
|
19564
21042
|
}
|
|
19565
21043
|
|
|
19566
21044
|
// src/core/config/config-editor.ts
|
|
@@ -20132,7 +21610,7 @@ async function runConfigEditor(configManager, mode = "file", apiPort, settingsMa
|
|
|
20132
21610
|
await configManager.load();
|
|
20133
21611
|
const config = configManager.get();
|
|
20134
21612
|
const updates = {};
|
|
20135
|
-
const instanceRoot =
|
|
21613
|
+
const instanceRoot = path32.dirname(configManager.getConfigPath());
|
|
20136
21614
|
console.log(`
|
|
20137
21615
|
${c.cyan}${c.bold}OpenACP Config Editor${c.reset}`);
|
|
20138
21616
|
console.log(dim(`Config: ${configManager.getConfigPath()}`));
|
|
@@ -20189,17 +21667,17 @@ ${c.cyan}${c.bold}OpenACP Config Editor${c.reset}`);
|
|
|
20189
21667
|
async function sendConfigViaApi(port, updates) {
|
|
20190
21668
|
const { apiCall: call } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
20191
21669
|
const paths = flattenToPaths(updates);
|
|
20192
|
-
for (const { path:
|
|
21670
|
+
for (const { path: path37, value } of paths) {
|
|
20193
21671
|
const res = await call(port, "/api/config", {
|
|
20194
21672
|
method: "PATCH",
|
|
20195
21673
|
headers: { "Content-Type": "application/json" },
|
|
20196
|
-
body: JSON.stringify({ path:
|
|
21674
|
+
body: JSON.stringify({ path: path37, value })
|
|
20197
21675
|
});
|
|
20198
21676
|
const data = await res.json();
|
|
20199
21677
|
if (!res.ok) {
|
|
20200
|
-
console.log(warn(`Failed to update ${
|
|
21678
|
+
console.log(warn(`Failed to update ${path37}: ${data.error}`));
|
|
20201
21679
|
} else if (data.needsRestart) {
|
|
20202
|
-
console.log(warn(`${
|
|
21680
|
+
console.log(warn(`${path37} updated \u2014 restart required`));
|
|
20203
21681
|
}
|
|
20204
21682
|
}
|
|
20205
21683
|
}
|
|
@@ -20219,25 +21697,25 @@ function flattenToPaths(obj, prefix = "") {
|
|
|
20219
21697
|
// src/cli/daemon.ts
|
|
20220
21698
|
init_config();
|
|
20221
21699
|
import { spawn as spawn3 } from "child_process";
|
|
20222
|
-
import * as
|
|
20223
|
-
import * as
|
|
21700
|
+
import * as fs33 from "fs";
|
|
21701
|
+
import * as path33 from "path";
|
|
20224
21702
|
function getPidPath(root) {
|
|
20225
|
-
return
|
|
21703
|
+
return path33.join(root, "openacp.pid");
|
|
20226
21704
|
}
|
|
20227
21705
|
function getLogDir(root) {
|
|
20228
|
-
return
|
|
21706
|
+
return path33.join(root, "logs");
|
|
20229
21707
|
}
|
|
20230
21708
|
function getRunningMarker(root) {
|
|
20231
|
-
return
|
|
21709
|
+
return path33.join(root, "running");
|
|
20232
21710
|
}
|
|
20233
21711
|
function writePidFile(pidPath, pid) {
|
|
20234
|
-
const dir =
|
|
20235
|
-
|
|
20236
|
-
|
|
21712
|
+
const dir = path33.dirname(pidPath);
|
|
21713
|
+
fs33.mkdirSync(dir, { recursive: true });
|
|
21714
|
+
fs33.writeFileSync(pidPath, String(pid));
|
|
20237
21715
|
}
|
|
20238
21716
|
function readPidFile(pidPath) {
|
|
20239
21717
|
try {
|
|
20240
|
-
const content =
|
|
21718
|
+
const content = fs33.readFileSync(pidPath, "utf-8").trim();
|
|
20241
21719
|
const pid = parseInt(content, 10);
|
|
20242
21720
|
return isNaN(pid) ? null : pid;
|
|
20243
21721
|
} catch {
|
|
@@ -20246,7 +21724,7 @@ function readPidFile(pidPath) {
|
|
|
20246
21724
|
}
|
|
20247
21725
|
function removePidFile(pidPath) {
|
|
20248
21726
|
try {
|
|
20249
|
-
|
|
21727
|
+
fs33.unlinkSync(pidPath);
|
|
20250
21728
|
} catch {
|
|
20251
21729
|
}
|
|
20252
21730
|
}
|
|
@@ -20279,12 +21757,12 @@ function startDaemon(pidPath, logDir2, instanceRoot) {
|
|
|
20279
21757
|
return { error: `Already running (PID ${pid})` };
|
|
20280
21758
|
}
|
|
20281
21759
|
const resolvedLogDir = logDir2 ? expandHome2(logDir2) : getLogDir(instanceRoot);
|
|
20282
|
-
|
|
20283
|
-
const logFile =
|
|
20284
|
-
const cliPath =
|
|
21760
|
+
fs33.mkdirSync(resolvedLogDir, { recursive: true });
|
|
21761
|
+
const logFile = path33.join(resolvedLogDir, "openacp.log");
|
|
21762
|
+
const cliPath = path33.resolve(process.argv[1]);
|
|
20285
21763
|
const nodePath = process.execPath;
|
|
20286
|
-
const out =
|
|
20287
|
-
const err =
|
|
21764
|
+
const out = fs33.openSync(logFile, "a");
|
|
21765
|
+
const err = fs33.openSync(logFile, "a");
|
|
20288
21766
|
const child = spawn3(nodePath, [cliPath, "--daemon-child"], {
|
|
20289
21767
|
detached: true,
|
|
20290
21768
|
stdio: ["ignore", out, err],
|
|
@@ -20293,8 +21771,8 @@ function startDaemon(pidPath, logDir2, instanceRoot) {
|
|
|
20293
21771
|
...instanceRoot ? { OPENACP_INSTANCE_ROOT: instanceRoot } : {}
|
|
20294
21772
|
}
|
|
20295
21773
|
});
|
|
20296
|
-
|
|
20297
|
-
|
|
21774
|
+
fs33.closeSync(out);
|
|
21775
|
+
fs33.closeSync(err);
|
|
20298
21776
|
if (!child.pid) {
|
|
20299
21777
|
return { error: "Failed to spawn daemon process" };
|
|
20300
21778
|
}
|
|
@@ -20365,12 +21843,12 @@ async function stopDaemon(pidPath, instanceRoot) {
|
|
|
20365
21843
|
}
|
|
20366
21844
|
function markRunning(root) {
|
|
20367
21845
|
const marker = getRunningMarker(root);
|
|
20368
|
-
|
|
20369
|
-
|
|
21846
|
+
fs33.mkdirSync(path33.dirname(marker), { recursive: true });
|
|
21847
|
+
fs33.writeFileSync(marker, "");
|
|
20370
21848
|
}
|
|
20371
21849
|
function clearRunning(root) {
|
|
20372
21850
|
try {
|
|
20373
|
-
|
|
21851
|
+
fs33.unlinkSync(getRunningMarker(root));
|
|
20374
21852
|
} catch {
|
|
20375
21853
|
}
|
|
20376
21854
|
}
|
|
@@ -21219,6 +22697,9 @@ Session records auto-cleanup: 30 days (configurable via \`sessionStore.ttlDays\`
|
|
|
21219
22697
|
Session logs auto-cleanup: 30 days (configurable via \`logging.sessionLogRetentionDays\`).
|
|
21220
22698
|
`;
|
|
21221
22699
|
|
|
22700
|
+
// src/core/index.ts
|
|
22701
|
+
init_proxy_service();
|
|
22702
|
+
|
|
21222
22703
|
// src/index.ts
|
|
21223
22704
|
init_adapter();
|
|
21224
22705
|
export {
|
|
@@ -21250,8 +22731,10 @@ export {
|
|
|
21250
22731
|
OpenACPCore,
|
|
21251
22732
|
OutputModeResolver,
|
|
21252
22733
|
PRODUCT_GUIDE,
|
|
22734
|
+
PROXY_ENV_KEYS,
|
|
21253
22735
|
PermissionGate,
|
|
21254
22736
|
PromptQueue,
|
|
22737
|
+
ProxyService,
|
|
21255
22738
|
SSEManager,
|
|
21256
22739
|
STATUS_ICONS,
|
|
21257
22740
|
SecurityGuard,
|