@n1creator/openacp-cli 2026.712.9 → 2026.712.10
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 +18 -0
- package/dist/cli.js +2715 -990
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +95 -6
- package/dist/index.js +1555 -481
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -458,22 +458,22 @@ __export(config_registry_exports, {
|
|
|
458
458
|
resolveOptions: () => resolveOptions,
|
|
459
459
|
setFieldValueAsync: () => setFieldValueAsync
|
|
460
460
|
});
|
|
461
|
-
function getFieldDef(
|
|
462
|
-
return CONFIG_REGISTRY.find((f) => f.path ===
|
|
461
|
+
function getFieldDef(path38) {
|
|
462
|
+
return CONFIG_REGISTRY.find((f) => f.path === path38);
|
|
463
463
|
}
|
|
464
464
|
function getSafeFields() {
|
|
465
465
|
return CONFIG_REGISTRY.filter((f) => f.scope === "safe");
|
|
466
466
|
}
|
|
467
|
-
function isHotReloadable(
|
|
468
|
-
const def = getFieldDef(
|
|
467
|
+
function isHotReloadable(path38) {
|
|
468
|
+
const def = getFieldDef(path38);
|
|
469
469
|
return def?.hotReload ?? false;
|
|
470
470
|
}
|
|
471
471
|
function resolveOptions(def, config) {
|
|
472
472
|
if (!def.options) return void 0;
|
|
473
473
|
return typeof def.options === "function" ? def.options(config) : def.options;
|
|
474
474
|
}
|
|
475
|
-
function getConfigValue(config,
|
|
476
|
-
const parts =
|
|
475
|
+
function getConfigValue(config, path38) {
|
|
476
|
+
const parts = path38.split(".");
|
|
477
477
|
let current = config;
|
|
478
478
|
for (const part of parts) {
|
|
479
479
|
if (current && typeof current === "object" && part in current) {
|
|
@@ -1546,7 +1546,7 @@ async function downloadAndExtract(agentId, archiveUrl, progress, agentsDir, scop
|
|
|
1546
1546
|
const destDir = path10.join(agentsDir ?? DEFAULT_AGENTS_DIR, agentId);
|
|
1547
1547
|
fs11.mkdirSync(destDir, { recursive: true });
|
|
1548
1548
|
await progress?.onStep("Downloading...");
|
|
1549
|
-
|
|
1549
|
+
log12.info({ agentId, url: archiveUrl }, "Downloading agent binary");
|
|
1550
1550
|
const response = await scopedFetch(archiveUrl);
|
|
1551
1551
|
if (!response.ok) {
|
|
1552
1552
|
throw new Error(`Download failed: ${response.status} ${response.statusText}`);
|
|
@@ -1656,17 +1656,17 @@ async function uninstallAgent(agentKey, store, agentsDir) {
|
|
|
1656
1656
|
if (agent.binaryPath && fs11.existsSync(agent.binaryPath)) {
|
|
1657
1657
|
validateUninstallPath(agent.binaryPath, agentsDir ?? DEFAULT_AGENTS_DIR);
|
|
1658
1658
|
fs11.rmSync(agent.binaryPath, { recursive: true, force: true });
|
|
1659
|
-
|
|
1659
|
+
log12.info({ agentKey, binaryPath: agent.binaryPath }, "Deleted agent binary");
|
|
1660
1660
|
}
|
|
1661
1661
|
store.removeAgent(agentKey);
|
|
1662
1662
|
}
|
|
1663
|
-
var
|
|
1663
|
+
var log12, DEFAULT_AGENTS_DIR, MAX_DOWNLOAD_SIZE, validateTarContents, ARCH_MAP, PLATFORM_MAP;
|
|
1664
1664
|
var init_agent_installer = __esm({
|
|
1665
1665
|
"src/core/agents/agent-installer.ts"() {
|
|
1666
1666
|
"use strict";
|
|
1667
1667
|
init_log();
|
|
1668
1668
|
init_agent_dependencies();
|
|
1669
|
-
|
|
1669
|
+
log12 = createChildLogger({ module: "agent-installer" });
|
|
1670
1670
|
DEFAULT_AGENTS_DIR = path10.join(os4.homedir(), ".openacp", "agents");
|
|
1671
1671
|
MAX_DOWNLOAD_SIZE = 500 * 1024 * 1024;
|
|
1672
1672
|
validateTarContents = validateArchiveContents;
|
|
@@ -2242,7 +2242,7 @@ function copyResponse(response, body) {
|
|
|
2242
2242
|
}
|
|
2243
2243
|
return copy;
|
|
2244
2244
|
}
|
|
2245
|
-
var enabledDebug, PROXY_ENV_KEYS, ProxyValidationError, ProxyUnknownScopeError, ProxyProfileInUseError, ProxyRouteTestError, ProxyProfileTestError, ProxyService;
|
|
2245
|
+
var enabledDebug, PROXY_ENV_KEYS, ProxyValidationError, ProxyUnknownScopeError, ProxyProfileInUseError, ProxyProfileNotFoundError, ProxyProfileExistsError, ProxyRouteTestError, ProxyProfileTestError, ProxyService;
|
|
2246
2246
|
var init_proxy_service = __esm({
|
|
2247
2247
|
"src/core/network/proxy-service.ts"() {
|
|
2248
2248
|
"use strict";
|
|
@@ -2281,6 +2281,16 @@ var init_proxy_service = __esm({
|
|
|
2281
2281
|
super(`Proxy profile "${id}" is still used by routing`, "PROXY_PROFILE_IN_USE");
|
|
2282
2282
|
}
|
|
2283
2283
|
};
|
|
2284
|
+
ProxyProfileNotFoundError = class extends ProxyValidationError {
|
|
2285
|
+
constructor(id) {
|
|
2286
|
+
super(`Proxy profile "${id}" does not exist`, "PROXY_PROFILE_NOT_FOUND");
|
|
2287
|
+
}
|
|
2288
|
+
};
|
|
2289
|
+
ProxyProfileExistsError = class extends ProxyValidationError {
|
|
2290
|
+
constructor(id) {
|
|
2291
|
+
super(`Proxy profile "${id}" already exists`, "PROXY_PROFILE_EXISTS");
|
|
2292
|
+
}
|
|
2293
|
+
};
|
|
2284
2294
|
ProxyRouteTestError = class extends Error {
|
|
2285
2295
|
code = "PROXY_ROUTE_TEST_FAILED";
|
|
2286
2296
|
constructor(scope, cause) {
|
|
@@ -2390,13 +2400,28 @@ var init_proxy_service = __esm({
|
|
|
2390
2400
|
return profile;
|
|
2391
2401
|
}
|
|
2392
2402
|
buildProfile(input2, config, secrets) {
|
|
2403
|
+
input2 = this.normalizeProfileInput(input2);
|
|
2393
2404
|
validateId(input2.id, "profile id");
|
|
2394
|
-
if (
|
|
2405
|
+
if (input2.name !== void 0) {
|
|
2406
|
+
const name = input2.name.trim();
|
|
2407
|
+
if (!name || name.length > 100) throw new ProxyValidationError("Proxy profile name must contain 1-100 non-whitespace characters");
|
|
2408
|
+
input2 = { ...input2, name };
|
|
2409
|
+
}
|
|
2410
|
+
if (!input2.protocol || !PROXY_PROTOCOLS.includes(input2.protocol)) throw new ProxyValidationError(`Unsupported proxy protocol: ${input2.protocol}`);
|
|
2395
2411
|
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);
|
|
2412
|
+
const host = canonicalHost(input2.host ?? "");
|
|
2413
|
+
for (const [label, value] of [["username", input2.username], ["password", input2.password]]) {
|
|
2414
|
+
if (value !== void 0 && (value.length > 4096 || /[\r\n\u0000]/.test(value))) {
|
|
2415
|
+
throw new ProxyValidationError(`Proxy ${label} contains unsupported control characters or exceeds 4096 characters`);
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
if (input2.noProxy && (input2.noProxy.length > 256 || input2.noProxy.some((item) => !item || /[\u0000-\u0020\u007f]/.test(item)))) {
|
|
2419
|
+
throw new ProxyValidationError("NO_PROXY entries must be non-empty, whitespace-free values (maximum 256 entries)");
|
|
2420
|
+
}
|
|
2397
2421
|
const existing = config.profiles.find((p) => p.id === input2.id);
|
|
2398
2422
|
const nextSecrets = structuredClone(secrets);
|
|
2399
|
-
if (input2.
|
|
2423
|
+
if (input2.clearCredentials) delete nextSecrets[input2.id];
|
|
2424
|
+
else 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
2425
|
const secret = nextSecrets[input2.id];
|
|
2401
2426
|
return { profile: {
|
|
2402
2427
|
id: input2.id,
|
|
@@ -2409,11 +2434,68 @@ var init_proxy_service = __esm({
|
|
|
2409
2434
|
hasCredentials: Boolean(secret?.username || secret?.password)
|
|
2410
2435
|
}, nextSecrets };
|
|
2411
2436
|
}
|
|
2437
|
+
/** Parse a write-only proxy URL into canonical components without retaining it. */
|
|
2438
|
+
parseProxyUrlInput(proxyUrl2) {
|
|
2439
|
+
if (!proxyUrl2 || /[\u0000-\u001f\u007f]/.test(proxyUrl2)) throw new ProxyValidationError("Proxy URL contains invalid control characters");
|
|
2440
|
+
let url;
|
|
2441
|
+
try {
|
|
2442
|
+
url = new URL(proxyUrl2);
|
|
2443
|
+
} catch {
|
|
2444
|
+
throw new ProxyValidationError("Proxy URL is invalid");
|
|
2445
|
+
}
|
|
2446
|
+
const protocol = url.protocol.slice(0, -1);
|
|
2447
|
+
if (!PROXY_PROTOCOLS.includes(protocol)) throw new ProxyValidationError(`Unsupported proxy protocol: ${protocol}`);
|
|
2448
|
+
if (url.pathname && url.pathname !== "/" || url.search || url.hash) throw new ProxyValidationError("Proxy URL must not contain a path, query, or fragment");
|
|
2449
|
+
const authority = /^[a-z][a-z0-9+.-]*:\/\/([^/?#]*)/i.exec(proxyUrl2)?.[1];
|
|
2450
|
+
const hostPort = authority?.slice(authority.lastIndexOf("@") + 1 || 0);
|
|
2451
|
+
const explicitPort = hostPort?.startsWith("[") ? /^\[[^\]]+\]:(\d+)$/.exec(hostPort)?.[1] : /:(\d+)$/.exec(hostPort ?? "")?.[1];
|
|
2452
|
+
if (!explicitPort) throw new ProxyValidationError("Proxy URL must include an explicit port");
|
|
2453
|
+
let username = "";
|
|
2454
|
+
let password2 = "";
|
|
2455
|
+
try {
|
|
2456
|
+
username = decodeURIComponent(url.username);
|
|
2457
|
+
password2 = decodeURIComponent(url.password);
|
|
2458
|
+
} catch {
|
|
2459
|
+
throw new ProxyValidationError("Proxy URL credentials contain invalid percent encoding");
|
|
2460
|
+
}
|
|
2461
|
+
return {
|
|
2462
|
+
protocol,
|
|
2463
|
+
host: url.hostname,
|
|
2464
|
+
port: Number(url.port || explicitPort),
|
|
2465
|
+
...username ? { username } : {},
|
|
2466
|
+
...password2 ? { password: password2 } : {},
|
|
2467
|
+
// Replacing an existing endpoint with a URL that has no userinfo must
|
|
2468
|
+
// remove the old write-only secret instead of silently retaining it.
|
|
2469
|
+
clearCredentials: !username && !password2
|
|
2470
|
+
};
|
|
2471
|
+
}
|
|
2472
|
+
normalizeProfileInput(input2) {
|
|
2473
|
+
if (input2.proxyUrl !== void 0) {
|
|
2474
|
+
const componentKeys = ["protocol", "host", "port", "username", "password", "clearCredentials"];
|
|
2475
|
+
if (componentKeys.some((key) => input2[key] !== void 0)) throw new ProxyValidationError("proxyUrl is mutually exclusive with endpoint and credential fields");
|
|
2476
|
+
const { proxyUrl: proxyUrl2, ...metadata } = input2;
|
|
2477
|
+
return { ...metadata, ...this.parseProxyUrlInput(proxyUrl2) };
|
|
2478
|
+
}
|
|
2479
|
+
if (!input2.protocol || !input2.host || input2.port === void 0) throw new ProxyValidationError("protocol, host, and port are required when proxyUrl is not provided");
|
|
2480
|
+
return input2;
|
|
2481
|
+
}
|
|
2412
2482
|
async saveProfileSafely(input2, expectedRevision) {
|
|
2483
|
+
return this.mutateProfileSafely(input2, "upsert", expectedRevision);
|
|
2484
|
+
}
|
|
2485
|
+
async createProfileSafely(input2, expectedRevision) {
|
|
2486
|
+
return this.mutateProfileSafely(input2, "create", expectedRevision);
|
|
2487
|
+
}
|
|
2488
|
+
async updateProfileSafely(input2, expectedRevision) {
|
|
2489
|
+
return this.mutateProfileSafely(input2, "update", expectedRevision);
|
|
2490
|
+
}
|
|
2491
|
+
async mutateProfileSafely(input2, operation, expectedRevision) {
|
|
2413
2492
|
return this.serialize(async () => {
|
|
2414
2493
|
const config = this.store.load();
|
|
2415
2494
|
const secrets = this.store.getSecrets();
|
|
2416
2495
|
if (expectedRevision !== void 0 && config.revision !== expectedRevision) throw new ProxyRevisionConflictError(expectedRevision, config.revision);
|
|
2496
|
+
const exists = config.profiles.some((profile) => profile.id === input2.id);
|
|
2497
|
+
if (operation === "create" && exists) throw new ProxyProfileExistsError(input2.id);
|
|
2498
|
+
if (operation === "update" && !exists) throw new ProxyProfileNotFoundError(input2.id);
|
|
2417
2499
|
const { profile: candidate, nextSecrets } = this.buildProfile(input2, config, secrets);
|
|
2418
2500
|
const affectedScopes = this.scopesUsingProfile(config, input2.id);
|
|
2419
2501
|
const candidateSecret = nextSecrets[input2.id];
|
|
@@ -2431,7 +2513,18 @@ var init_proxy_service = __esm({
|
|
|
2431
2513
|
}
|
|
2432
2514
|
this.invalidatePolicyBeforeCommit(config, input2.id);
|
|
2433
2515
|
config.profiles = [...config.profiles.filter((p) => p.id !== candidate.id), candidate];
|
|
2434
|
-
|
|
2516
|
+
let committed;
|
|
2517
|
+
try {
|
|
2518
|
+
committed = this.store.commit(config, nextSecrets, config.revision);
|
|
2519
|
+
} catch (error) {
|
|
2520
|
+
if (error instanceof ProxyRevisionConflictError && operation !== "upsert") {
|
|
2521
|
+
const latestExists = this.store.load().profiles.some((profile) => profile.id === input2.id);
|
|
2522
|
+
if (operation === "create" && latestExists) throw new ProxyProfileExistsError(input2.id);
|
|
2523
|
+
if (operation === "update" && !latestExists) throw new ProxyProfileNotFoundError(input2.id);
|
|
2524
|
+
}
|
|
2525
|
+
throw error;
|
|
2526
|
+
}
|
|
2527
|
+
const saved = committed.profiles.find((p) => p.id === candidate.id);
|
|
2435
2528
|
this.retireScopes(affectedScopes);
|
|
2436
2529
|
return saved;
|
|
2437
2530
|
});
|
|
@@ -2486,15 +2579,37 @@ var init_proxy_service = __esm({
|
|
|
2486
2579
|
return this.saveProfileSafely(this.parseEnvFile(id, envFile, name), expectedRevision);
|
|
2487
2580
|
}
|
|
2488
2581
|
async deleteProfile(id, expectedRevision) {
|
|
2582
|
+
await this.deleteProfileSafely(id, void 0, expectedRevision);
|
|
2583
|
+
}
|
|
2584
|
+
/** Delete a profile, optionally reassigning every direct reference in one tested CAS transaction. */
|
|
2585
|
+
async deleteProfileSafely(id, reassign, expectedRevision) {
|
|
2489
2586
|
return this.serialize(async () => {
|
|
2490
2587
|
const config = this.store.load();
|
|
2491
2588
|
const secrets = this.store.getSecrets();
|
|
2492
2589
|
if (expectedRevision !== void 0 && config.revision !== expectedRevision) throw new ProxyRevisionConflictError(expectedRevision, config.revision);
|
|
2493
|
-
if (!config.profiles.some((p) => p.id === id)) throw new
|
|
2494
|
-
|
|
2495
|
-
|
|
2590
|
+
if (!config.profiles.some((p) => p.id === id)) throw new ProxyProfileNotFoundError(id);
|
|
2591
|
+
const directScopes = Object.entries(config.routing.routes).filter(([, route]) => route === `profile:${id}`).map(([scope]) => scope);
|
|
2592
|
+
const globalUsesProfile = config.routing.global === `profile:${id}`;
|
|
2593
|
+
if ((directScopes.length || globalUsesProfile) && !reassign) throw new ProxyProfileInUseError(id);
|
|
2594
|
+
if (reassign === `profile:${id}`) throw new ProxyValidationError("A deleted profile cannot be reassigned to itself");
|
|
2595
|
+
if (reassign) this.validateRoute(reassign);
|
|
2596
|
+
const candidate = structuredClone(config);
|
|
2597
|
+
if (globalUsesProfile) candidate.routing.global = reassign;
|
|
2598
|
+
for (const scope of directScopes) candidate.routing.routes[scope] = reassign;
|
|
2599
|
+
const changedScopes = this.changedResolutionScopes(config, candidate);
|
|
2600
|
+
try {
|
|
2601
|
+
await this.testCandidateRoutes(config, candidate);
|
|
2602
|
+
} catch (error) {
|
|
2603
|
+
throw new ProxyRouteTestError(globalUsesProfile ? "global" : directScopes[0], error);
|
|
2604
|
+
}
|
|
2605
|
+
candidate.profiles = candidate.profiles.filter((p) => p.id !== id);
|
|
2496
2606
|
delete secrets[id];
|
|
2497
|
-
this.
|
|
2607
|
+
this.invalidatePolicyBeforeCommit(config, id);
|
|
2608
|
+
this.store.commit(candidate, secrets, config.revision);
|
|
2609
|
+
this.retireScopes(changedScopes);
|
|
2610
|
+
if (globalUsesProfile) for (const listener of this.listeners) await listener("global", reassign);
|
|
2611
|
+
for (const scope of directScopes) for (const listener of this.listeners) await listener(scope, reassign);
|
|
2612
|
+
return { reassignedScopes: [.../* @__PURE__ */ new Set([...globalUsesProfile ? ["global"] : [], ...directScopes])] };
|
|
2498
2613
|
});
|
|
2499
2614
|
}
|
|
2500
2615
|
resolve(scope, routeOverride) {
|
|
@@ -2713,6 +2828,27 @@ var init_proxy_service = __esm({
|
|
|
2713
2828
|
fetcher?.destroy?.();
|
|
2714
2829
|
}
|
|
2715
2830
|
}
|
|
2831
|
+
/** Test an unsaved profile candidate entirely in memory. Credentials never reach persistence. */
|
|
2832
|
+
async testProfileCandidate(input2, targetUrl) {
|
|
2833
|
+
const config = this.store.load();
|
|
2834
|
+
const { profile, nextSecrets } = this.buildProfile(input2, config, this.store.getSecrets());
|
|
2835
|
+
const fetcher = this.createProfileFetch(profile, nextSecrets[profile.id]);
|
|
2836
|
+
let response;
|
|
2837
|
+
try {
|
|
2838
|
+
response = await fetcher(targetUrl ?? "https://api.ipify.org?format=json", {
|
|
2839
|
+
signal: AbortSignal.timeout(1e4)
|
|
2840
|
+
});
|
|
2841
|
+
return { ok: response.ok, status: response.status };
|
|
2842
|
+
} catch (error) {
|
|
2843
|
+
return { ok: false, error: error instanceof Error ? redactNetworkSecrets(error.message) : "Proxy test failed" };
|
|
2844
|
+
} finally {
|
|
2845
|
+
try {
|
|
2846
|
+
await response?.body?.cancel();
|
|
2847
|
+
} catch {
|
|
2848
|
+
}
|
|
2849
|
+
fetcher.destroy?.();
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2716
2852
|
status() {
|
|
2717
2853
|
const config = this.store.load();
|
|
2718
2854
|
const scopes = this.getKnownScopes();
|
|
@@ -3035,6 +3171,19 @@ var init_agents = __esm({
|
|
|
3035
3171
|
}
|
|
3036
3172
|
});
|
|
3037
3173
|
|
|
3174
|
+
// src/core/telegram-command-scopes.ts
|
|
3175
|
+
function effectiveTelegramGroupCommands(lists, administrator) {
|
|
3176
|
+
const candidates = administrator ? [lists.chatAdmins, lists.chat, lists.allAdmins, lists.allGroup, lists.default] : [lists.chat, lists.allGroup, lists.default];
|
|
3177
|
+
return candidates.find((commands) => commands.length > 0) ?? [];
|
|
3178
|
+
}
|
|
3179
|
+
var TELEGRAM_COMMAND_LOCALES;
|
|
3180
|
+
var init_telegram_command_scopes = __esm({
|
|
3181
|
+
"src/core/telegram-command-scopes.ts"() {
|
|
3182
|
+
"use strict";
|
|
3183
|
+
TELEGRAM_COMMAND_LOCALES = ["", "en", "ru"];
|
|
3184
|
+
}
|
|
3185
|
+
});
|
|
3186
|
+
|
|
3038
3187
|
// src/core/plugin/settings-manager.ts
|
|
3039
3188
|
var settings_manager_exports = {};
|
|
3040
3189
|
__export(settings_manager_exports, {
|
|
@@ -3150,13 +3299,350 @@ var init_settings_manager = __esm({
|
|
|
3150
3299
|
}
|
|
3151
3300
|
});
|
|
3152
3301
|
|
|
3302
|
+
// src/plugins/telegram/command-ownership-store.ts
|
|
3303
|
+
var command_ownership_store_exports = {};
|
|
3304
|
+
__export(command_ownership_store_exports, {
|
|
3305
|
+
TelegramCommandOwnerConflictError: () => TelegramCommandOwnerConflictError,
|
|
3306
|
+
TelegramCommandOwnershipStore: () => TelegramCommandOwnershipStore,
|
|
3307
|
+
telegramCommandHostId: () => telegramCommandHostId,
|
|
3308
|
+
telegramCommandInstanceKey: () => telegramCommandInstanceKey
|
|
3309
|
+
});
|
|
3310
|
+
import fs20 from "fs";
|
|
3311
|
+
import path19 from "path";
|
|
3312
|
+
import os5 from "os";
|
|
3313
|
+
import { createHash as createHash2 } from "crypto";
|
|
3314
|
+
function telegramCommandHostId() {
|
|
3315
|
+
let source = `${os5.hostname()}|${typeof process.getuid === "function" ? process.getuid() : "unknown"}`;
|
|
3316
|
+
try {
|
|
3317
|
+
source = fs20.readFileSync("/etc/machine-id", "utf8").trim() || source;
|
|
3318
|
+
} catch {
|
|
3319
|
+
}
|
|
3320
|
+
return createHash2("sha256").update(source).digest("hex");
|
|
3321
|
+
}
|
|
3322
|
+
function telegramCommandInstanceKey(instanceRoot) {
|
|
3323
|
+
return createHash2("sha256").update(path19.resolve(instanceRoot)).digest("hex");
|
|
3324
|
+
}
|
|
3325
|
+
function emptyLedger() {
|
|
3326
|
+
return { version: STORE_VERSION, bots: {} };
|
|
3327
|
+
}
|
|
3328
|
+
function validateLedger(value) {
|
|
3329
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid root");
|
|
3330
|
+
const ledger = value;
|
|
3331
|
+
if (ledger.version !== STORE_VERSION || !ledger.bots || typeof ledger.bots !== "object") throw new Error("invalid version/bots");
|
|
3332
|
+
const botEntries = Object.entries(ledger.bots);
|
|
3333
|
+
if (botEntries.length > MAX_BOTS) throw new Error("too many bots");
|
|
3334
|
+
for (const [botId, bot] of botEntries) {
|
|
3335
|
+
if (!/^\d{1,20}$/.test(botId) || !bot || typeof bot !== "object" || !bot.scopes || typeof bot.scopes !== "object") throw new Error("invalid bot");
|
|
3336
|
+
if (bot.owner) {
|
|
3337
|
+
const owner = bot.owner;
|
|
3338
|
+
if (typeof owner.instanceId !== "string" || !owner.instanceId || owner.instanceId.length > 128 || /[\u0000-\u001f\u007f]/.test(owner.instanceId) || !/^[a-f0-9]{64}$/.test(owner.instanceKey) || !/^[a-f0-9]{64}$/.test(owner.hostId) || !Number.isSafeInteger(owner.pid) || owner.pid <= 0 || !Number.isFinite(Date.parse(owner.heartbeatAt)) || owner.stoppedAt !== void 0 && !Number.isFinite(Date.parse(owner.stoppedAt))) throw new Error("invalid owner");
|
|
3339
|
+
}
|
|
3340
|
+
const scopes = Object.entries(bot.scopes);
|
|
3341
|
+
if (scopes.length > MAX_SCOPES_PER_BOT) throw new Error("too many scopes");
|
|
3342
|
+
for (const [key, scope] of scopes) {
|
|
3343
|
+
if (!/^(default|chat:[a-f0-9]{16}|chat_administrators:[a-f0-9]{16})\|(neutral|en|ru)$/.test(key)) throw new Error("invalid scope key");
|
|
3344
|
+
if (!scope || !Array.isArray(scope.owned) || scope.owned.length > MAX_NAMES_PER_SCOPE) throw new Error("invalid ownership");
|
|
3345
|
+
if (scope.owned.some((name) => typeof name !== "string" || !/^[a-z0-9_]{1,32}$/.test(name))) throw new Error("invalid command name");
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
return structuredClone(ledger);
|
|
3349
|
+
}
|
|
3350
|
+
var STORE_VERSION, MAX_BOTS, MAX_SCOPES_PER_BOT, MAX_NAMES_PER_SCOPE, TelegramCommandOwnerConflictError, TelegramCommandOwnershipStore;
|
|
3351
|
+
var init_command_ownership_store = __esm({
|
|
3352
|
+
"src/plugins/telegram/command-ownership-store.ts"() {
|
|
3353
|
+
"use strict";
|
|
3354
|
+
STORE_VERSION = 2;
|
|
3355
|
+
MAX_BOTS = 8;
|
|
3356
|
+
MAX_SCOPES_PER_BOT = 64;
|
|
3357
|
+
MAX_NAMES_PER_SCOPE = 100;
|
|
3358
|
+
TelegramCommandOwnerConflictError = class extends Error {
|
|
3359
|
+
constructor(ownerInstanceId) {
|
|
3360
|
+
super("This Telegram bot command menu is owned by another OpenACP instance. Configure a unique bot per instance, stop the current owner, or perform an explicit same-host takeover.");
|
|
3361
|
+
this.ownerInstanceId = ownerInstanceId;
|
|
3362
|
+
this.name = "TelegramCommandOwnerConflictError";
|
|
3363
|
+
}
|
|
3364
|
+
ownerInstanceId;
|
|
3365
|
+
code = "TELEGRAM_COMMAND_OWNER_CONFLICT";
|
|
3366
|
+
};
|
|
3367
|
+
TelegramCommandOwnershipStore = class {
|
|
3368
|
+
file;
|
|
3369
|
+
lockFile;
|
|
3370
|
+
constructor(instanceRoot) {
|
|
3371
|
+
this.file = path19.join(instanceRoot, "telegram-command-ownership.json");
|
|
3372
|
+
this.lockFile = `${this.file}.lock`;
|
|
3373
|
+
}
|
|
3374
|
+
async withLock(operation) {
|
|
3375
|
+
fs20.mkdirSync(path19.dirname(this.file), { recursive: true, mode: 448 });
|
|
3376
|
+
const deadline = Date.now() + 1e4;
|
|
3377
|
+
let fd;
|
|
3378
|
+
while (fd === void 0) {
|
|
3379
|
+
try {
|
|
3380
|
+
fd = fs20.openSync(this.lockFile, "wx", 384);
|
|
3381
|
+
fs20.writeFileSync(fd, String(process.pid));
|
|
3382
|
+
} catch (error) {
|
|
3383
|
+
if (error.code !== "EEXIST") throw error;
|
|
3384
|
+
try {
|
|
3385
|
+
const owner = Number(fs20.readFileSync(this.lockFile, "utf8"));
|
|
3386
|
+
let alive = Number.isSafeInteger(owner) && owner > 0;
|
|
3387
|
+
if (alive) {
|
|
3388
|
+
try {
|
|
3389
|
+
process.kill(owner, 0);
|
|
3390
|
+
} catch {
|
|
3391
|
+
alive = false;
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
if (!alive) fs20.unlinkSync(this.lockFile);
|
|
3395
|
+
} catch {
|
|
3396
|
+
}
|
|
3397
|
+
if (Date.now() >= deadline) throw new Error("Timed out waiting for Telegram command ownership lock");
|
|
3398
|
+
await new Promise((resolve7) => setTimeout(resolve7, 50));
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
try {
|
|
3402
|
+
return await operation(this.load());
|
|
3403
|
+
} finally {
|
|
3404
|
+
try {
|
|
3405
|
+
fs20.closeSync(fd);
|
|
3406
|
+
} catch {
|
|
3407
|
+
}
|
|
3408
|
+
try {
|
|
3409
|
+
fs20.unlinkSync(this.lockFile);
|
|
3410
|
+
} catch {
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
load() {
|
|
3415
|
+
if (!fs20.existsSync(this.file)) return { ledger: emptyLedger(), conservative: false };
|
|
3416
|
+
try {
|
|
3417
|
+
return { ledger: validateLedger(JSON.parse(fs20.readFileSync(this.file, "utf8"))), conservative: false };
|
|
3418
|
+
} catch {
|
|
3419
|
+
const quarantine = `${this.file}.corrupt.${Date.now()}`;
|
|
3420
|
+
try {
|
|
3421
|
+
fs20.renameSync(this.file, quarantine);
|
|
3422
|
+
} catch {
|
|
3423
|
+
}
|
|
3424
|
+
return { ledger: emptyLedger(), conservative: true };
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
getOwned(ledger, botId, scopeKey) {
|
|
3428
|
+
return ledger.bots[botId]?.scopes[scopeKey]?.owned;
|
|
3429
|
+
}
|
|
3430
|
+
/** Claim command-list ownership while the ledger lock is held. */
|
|
3431
|
+
claimOwner(ledger, botId, identity, allowTakeover = false) {
|
|
3432
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3433
|
+
const bot = this.ensureBot(ledger, botId, now);
|
|
3434
|
+
const current = bot.owner;
|
|
3435
|
+
const sameProcess = current?.hostId === identity.hostId && current.instanceKey === identity.instanceKey && current.pid === identity.pid;
|
|
3436
|
+
if (!current || sameProcess || current.stoppedAt && current.hostId === identity.hostId && current.instanceKey === identity.instanceKey) {
|
|
3437
|
+
bot.owner = { ...identity, heartbeatAt: now };
|
|
3438
|
+
bot.updatedAt = now;
|
|
3439
|
+
return;
|
|
3440
|
+
}
|
|
3441
|
+
const sameHost = current.hostId === identity.hostId;
|
|
3442
|
+
const provablyStopped = sameHost && (Boolean(current.stoppedAt) || !this.processAlive(current.pid));
|
|
3443
|
+
if (allowTakeover && provablyStopped) {
|
|
3444
|
+
bot.owner = { ...identity, heartbeatAt: now };
|
|
3445
|
+
bot.updatedAt = now;
|
|
3446
|
+
return;
|
|
3447
|
+
}
|
|
3448
|
+
throw new TelegramCommandOwnerConflictError(current.instanceId);
|
|
3449
|
+
}
|
|
3450
|
+
async heartbeatOwner(botId, identity) {
|
|
3451
|
+
return this.withLock(async ({ ledger }) => {
|
|
3452
|
+
const bot = ledger.bots[botId];
|
|
3453
|
+
const owner = bot?.owner;
|
|
3454
|
+
if (!owner || owner.hostId !== identity.hostId || owner.instanceKey !== identity.instanceKey || owner.pid !== identity.pid || owner.stoppedAt) return false;
|
|
3455
|
+
owner.heartbeatAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3456
|
+
bot.updatedAt = owner.heartbeatAt;
|
|
3457
|
+
this.save(ledger);
|
|
3458
|
+
return true;
|
|
3459
|
+
});
|
|
3460
|
+
}
|
|
3461
|
+
async releaseOwner(botId, identity) {
|
|
3462
|
+
await this.withLock(async ({ ledger }) => {
|
|
3463
|
+
const bot = ledger.bots[botId];
|
|
3464
|
+
const owner = bot?.owner;
|
|
3465
|
+
if (!owner || owner.hostId !== identity.hostId || owner.instanceKey !== identity.instanceKey || owner.pid !== identity.pid) return;
|
|
3466
|
+
owner.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3467
|
+
owner.heartbeatAt = owner.stoppedAt;
|
|
3468
|
+
bot.updatedAt = owner.stoppedAt;
|
|
3469
|
+
this.save(ledger);
|
|
3470
|
+
});
|
|
3471
|
+
}
|
|
3472
|
+
getOwner(botId) {
|
|
3473
|
+
return this.load().ledger.bots[botId]?.owner;
|
|
3474
|
+
}
|
|
3475
|
+
setOwned(ledger, botId, scopeKey, names) {
|
|
3476
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3477
|
+
const bot = this.ensureBot(ledger, botId, now);
|
|
3478
|
+
bot.updatedAt = now;
|
|
3479
|
+
bot.scopes[scopeKey] = { owned: [...new Set(names)].slice(0, MAX_NAMES_PER_SCOPE), updatedAt: now };
|
|
3480
|
+
}
|
|
3481
|
+
ensureBot(ledger, botId, now) {
|
|
3482
|
+
if (!ledger.bots[botId]) {
|
|
3483
|
+
const entries = Object.entries(ledger.bots).sort((a, b) => a[1].updatedAt.localeCompare(b[1].updatedAt));
|
|
3484
|
+
while (entries.length >= MAX_BOTS) {
|
|
3485
|
+
const oldest = entries.shift();
|
|
3486
|
+
if (oldest) delete ledger.bots[oldest[0]];
|
|
3487
|
+
}
|
|
3488
|
+
ledger.bots[botId] = { scopes: {}, updatedAt: now };
|
|
3489
|
+
}
|
|
3490
|
+
return ledger.bots[botId];
|
|
3491
|
+
}
|
|
3492
|
+
processAlive(pid) {
|
|
3493
|
+
try {
|
|
3494
|
+
process.kill(pid, 0);
|
|
3495
|
+
return true;
|
|
3496
|
+
} catch {
|
|
3497
|
+
return false;
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
3500
|
+
save(ledger) {
|
|
3501
|
+
const clean = validateLedger(ledger);
|
|
3502
|
+
fs20.mkdirSync(path19.dirname(this.file), { recursive: true, mode: 448 });
|
|
3503
|
+
const tmp = `${this.file}.${process.pid}.${Date.now()}.tmp`;
|
|
3504
|
+
fs20.writeFileSync(tmp, `${JSON.stringify(clean, null, 2)}
|
|
3505
|
+
`, { mode: 384, flag: "wx" });
|
|
3506
|
+
fs20.chmodSync(tmp, 384);
|
|
3507
|
+
const fd = fs20.openSync(tmp, "r");
|
|
3508
|
+
fs20.fsyncSync(fd);
|
|
3509
|
+
fs20.closeSync(fd);
|
|
3510
|
+
fs20.renameSync(tmp, this.file);
|
|
3511
|
+
try {
|
|
3512
|
+
const dir = fs20.openSync(path19.dirname(this.file), "r");
|
|
3513
|
+
fs20.fsyncSync(dir);
|
|
3514
|
+
fs20.closeSync(dir);
|
|
3515
|
+
} catch {
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3518
|
+
};
|
|
3519
|
+
}
|
|
3520
|
+
});
|
|
3521
|
+
|
|
3522
|
+
// src/core/instance/instance-context.ts
|
|
3523
|
+
var instance_context_exports = {};
|
|
3524
|
+
__export(instance_context_exports, {
|
|
3525
|
+
createInstanceContext: () => createInstanceContext,
|
|
3526
|
+
generateSlug: () => generateSlug,
|
|
3527
|
+
getGlobalRoot: () => getGlobalRoot,
|
|
3528
|
+
resolveInstanceRoot: () => resolveInstanceRoot,
|
|
3529
|
+
resolveRunningInstance: () => resolveRunningInstance
|
|
3530
|
+
});
|
|
3531
|
+
import path20 from "path";
|
|
3532
|
+
import fs21 from "fs";
|
|
3533
|
+
import os6 from "os";
|
|
3534
|
+
function createInstanceContext(opts) {
|
|
3535
|
+
const { id, root } = opts;
|
|
3536
|
+
const globalRoot = getGlobalRoot();
|
|
3537
|
+
return {
|
|
3538
|
+
id,
|
|
3539
|
+
root,
|
|
3540
|
+
paths: {
|
|
3541
|
+
config: path20.join(root, "config.json"),
|
|
3542
|
+
sessions: path20.join(root, "sessions.json"),
|
|
3543
|
+
agents: path20.join(root, "agents.json"),
|
|
3544
|
+
registryCache: path20.join(globalRoot, "cache", "registry-cache.json"),
|
|
3545
|
+
plugins: path20.join(root, "plugins"),
|
|
3546
|
+
pluginsData: path20.join(root, "plugins", "data"),
|
|
3547
|
+
pluginRegistry: path20.join(root, "plugins.json"),
|
|
3548
|
+
logs: path20.join(root, "logs"),
|
|
3549
|
+
pid: path20.join(root, "openacp.pid"),
|
|
3550
|
+
running: path20.join(root, "running"),
|
|
3551
|
+
apiPort: path20.join(root, "api.port"),
|
|
3552
|
+
apiSecret: path20.join(root, "api-secret"),
|
|
3553
|
+
bin: path20.join(globalRoot, "bin"),
|
|
3554
|
+
cache: path20.join(root, "cache"),
|
|
3555
|
+
tunnels: path20.join(root, "tunnels.json"),
|
|
3556
|
+
agentsDir: path20.join(globalRoot, "agents")
|
|
3557
|
+
}
|
|
3558
|
+
};
|
|
3559
|
+
}
|
|
3560
|
+
function generateSlug(name) {
|
|
3561
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
3562
|
+
return slug || "openacp";
|
|
3563
|
+
}
|
|
3564
|
+
function expandHome3(p) {
|
|
3565
|
+
if (p.startsWith("~")) return path20.join(os6.homedir(), p.slice(1));
|
|
3566
|
+
return p;
|
|
3567
|
+
}
|
|
3568
|
+
function resolveInstanceRoot(opts) {
|
|
3569
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
3570
|
+
const home = os6.homedir();
|
|
3571
|
+
const globalRoot = getGlobalRoot();
|
|
3572
|
+
if (opts.dir) return path20.join(expandHome3(opts.dir), ".openacp");
|
|
3573
|
+
if (opts.local) return path20.join(cwd, ".openacp");
|
|
3574
|
+
const cwdRoot = path20.join(cwd, ".openacp");
|
|
3575
|
+
if (fs21.existsSync(path20.join(cwdRoot, "config.json"))) return cwdRoot;
|
|
3576
|
+
let dir = path20.resolve(cwd);
|
|
3577
|
+
while (true) {
|
|
3578
|
+
const parent = path20.dirname(dir);
|
|
3579
|
+
if (parent === dir) break;
|
|
3580
|
+
dir = parent;
|
|
3581
|
+
const candidate = path20.join(dir, ".openacp");
|
|
3582
|
+
if (candidate === globalRoot) {
|
|
3583
|
+
if (dir === home) break;
|
|
3584
|
+
continue;
|
|
3585
|
+
}
|
|
3586
|
+
if (fs21.existsSync(path20.join(candidate, "config.json"))) return candidate;
|
|
3587
|
+
if (dir === home) break;
|
|
3588
|
+
}
|
|
3589
|
+
if (path20.resolve(cwd) === path20.resolve(home)) {
|
|
3590
|
+
const defaultWs = path20.join(home, "openacp-workspace", ".openacp");
|
|
3591
|
+
if (fs21.existsSync(path20.join(defaultWs, "config.json"))) return defaultWs;
|
|
3592
|
+
}
|
|
3593
|
+
if (process.env.OPENACP_INSTANCE_ROOT) return process.env.OPENACP_INSTANCE_ROOT;
|
|
3594
|
+
return null;
|
|
3595
|
+
}
|
|
3596
|
+
function getGlobalRoot() {
|
|
3597
|
+
return path20.join(os6.homedir(), ".openacp");
|
|
3598
|
+
}
|
|
3599
|
+
async function resolveRunningInstance(cwd) {
|
|
3600
|
+
const globalRoot = getGlobalRoot();
|
|
3601
|
+
const home = os6.homedir();
|
|
3602
|
+
let dir = path20.resolve(cwd);
|
|
3603
|
+
while (true) {
|
|
3604
|
+
const candidate = path20.join(dir, ".openacp");
|
|
3605
|
+
if (candidate !== globalRoot && fs21.existsSync(candidate)) {
|
|
3606
|
+
if (await isInstanceRunning(candidate)) return candidate;
|
|
3607
|
+
}
|
|
3608
|
+
const parent = path20.dirname(dir);
|
|
3609
|
+
if (parent === dir) break;
|
|
3610
|
+
if (dir === home) break;
|
|
3611
|
+
dir = parent;
|
|
3612
|
+
}
|
|
3613
|
+
return null;
|
|
3614
|
+
}
|
|
3615
|
+
async function isInstanceRunning(instanceRoot) {
|
|
3616
|
+
const portFile = path20.join(instanceRoot, "api.port");
|
|
3617
|
+
try {
|
|
3618
|
+
const content = fs21.readFileSync(portFile, "utf-8").trim();
|
|
3619
|
+
const port = parseInt(content, 10);
|
|
3620
|
+
if (isNaN(port)) return false;
|
|
3621
|
+
const controller = new AbortController();
|
|
3622
|
+
const timeout = setTimeout(() => controller.abort(), 2e3);
|
|
3623
|
+
const res = await fetch(`http://127.0.0.1:${port}/api/v1/system/health`, {
|
|
3624
|
+
signal: controller.signal
|
|
3625
|
+
});
|
|
3626
|
+
clearTimeout(timeout);
|
|
3627
|
+
return res.ok;
|
|
3628
|
+
} catch {
|
|
3629
|
+
return false;
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
var init_instance_context = __esm({
|
|
3633
|
+
"src/core/instance/instance-context.ts"() {
|
|
3634
|
+
"use strict";
|
|
3635
|
+
}
|
|
3636
|
+
});
|
|
3637
|
+
|
|
3153
3638
|
// src/core/doctor/checks/telegram.ts
|
|
3154
|
-
import * as
|
|
3639
|
+
import * as path21 from "path";
|
|
3155
3640
|
var BOT_TOKEN_REGEX, telegramCheck;
|
|
3156
3641
|
var init_telegram = __esm({
|
|
3157
3642
|
"src/core/doctor/checks/telegram.ts"() {
|
|
3158
3643
|
"use strict";
|
|
3159
3644
|
init_network_redaction();
|
|
3645
|
+
init_telegram_command_scopes();
|
|
3160
3646
|
BOT_TOKEN_REGEX = /^\d+:[A-Za-z0-9_-]{35,}$/;
|
|
3161
3647
|
telegramCheck = {
|
|
3162
3648
|
name: "Telegram",
|
|
@@ -3168,7 +3654,7 @@ var init_telegram = __esm({
|
|
|
3168
3654
|
return results;
|
|
3169
3655
|
}
|
|
3170
3656
|
const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
|
|
3171
|
-
const sm = new SettingsManager2(
|
|
3657
|
+
const sm = new SettingsManager2(path21.join(ctx.pluginsDir, "data"));
|
|
3172
3658
|
const ps = await sm.loadSettings("@openacp/telegram");
|
|
3173
3659
|
const botToken = ps.botToken;
|
|
3174
3660
|
const chatId = ps.chatId;
|
|
@@ -3209,6 +3695,24 @@ var init_telegram = __esm({
|
|
|
3209
3695
|
results.push({ status: "fail", message: "Chat ID not configured" });
|
|
3210
3696
|
return results;
|
|
3211
3697
|
}
|
|
3698
|
+
try {
|
|
3699
|
+
const [{ TelegramCommandOwnershipStore: TelegramCommandOwnershipStore2, telegramCommandInstanceKey: telegramCommandInstanceKey2 }, { getGlobalRoot: getGlobalRoot2 }] = await Promise.all([
|
|
3700
|
+
Promise.resolve().then(() => (init_command_ownership_store(), command_ownership_store_exports)),
|
|
3701
|
+
Promise.resolve().then(() => (init_instance_context(), instance_context_exports))
|
|
3702
|
+
]);
|
|
3703
|
+
const owner = new TelegramCommandOwnershipStore2(getGlobalRoot2()).getOwner(String(botId));
|
|
3704
|
+
if (owner && owner.instanceKey !== telegramCommandInstanceKey2(ctx.dataDir)) {
|
|
3705
|
+
results.push({
|
|
3706
|
+
status: "warn",
|
|
3707
|
+
message: "Telegram command sync is owned by another OpenACP instance. Use a unique bot per instance. For a stopped same-host owner only, request one explicit takeover with OPENACP_TELEGRAM_COMMAND_TAKEOVER=1."
|
|
3708
|
+
});
|
|
3709
|
+
}
|
|
3710
|
+
} catch (err) {
|
|
3711
|
+
results.push({
|
|
3712
|
+
status: "warn",
|
|
3713
|
+
message: `Cannot inspect Telegram command-sync ownership: ${redactNetworkSecrets(err instanceof Error ? err.message : String(err))}`
|
|
3714
|
+
});
|
|
3715
|
+
}
|
|
3212
3716
|
try {
|
|
3213
3717
|
const res = await telegramFetch(`https://api.telegram.org/bot${botToken}/getChat`, {
|
|
3214
3718
|
method: "POST",
|
|
@@ -3255,6 +3759,54 @@ var init_telegram = __esm({
|
|
|
3255
3759
|
const message = err instanceof Error ? err.message : String(err);
|
|
3256
3760
|
results.push({ status: "fail", message: `Admin check failed: ${redactNetworkSecrets(message)}` });
|
|
3257
3761
|
}
|
|
3762
|
+
try {
|
|
3763
|
+
const getCommands = async (scope, languageCode) => {
|
|
3764
|
+
const res = await telegramFetch(`https://api.telegram.org/bot${botToken}/getMyCommands`, {
|
|
3765
|
+
method: "POST",
|
|
3766
|
+
headers: { "Content-Type": "application/json" },
|
|
3767
|
+
body: JSON.stringify({ scope, ...languageCode ? { language_code: languageCode } : {} })
|
|
3768
|
+
});
|
|
3769
|
+
const data = await res.json();
|
|
3770
|
+
if (!data.ok || !Array.isArray(data.result)) {
|
|
3771
|
+
throw new Error(data.description || "Telegram returned an invalid command list");
|
|
3772
|
+
}
|
|
3773
|
+
return data.result;
|
|
3774
|
+
};
|
|
3775
|
+
const missing = [];
|
|
3776
|
+
for (const locale of TELEGRAM_COMMAND_LOCALES) {
|
|
3777
|
+
const [defaultCommands, groupCommands, globalAdminCommands, chatCommands, chatAdminCommands] = await Promise.all([
|
|
3778
|
+
getCommands({ type: "default" }, locale),
|
|
3779
|
+
getCommands({ type: "all_group_chats" }, locale),
|
|
3780
|
+
getCommands({ type: "all_chat_administrators" }, locale),
|
|
3781
|
+
getCommands({ type: "chat", chat_id: chatId }, locale),
|
|
3782
|
+
getCommands({ type: "chat_administrators", chat_id: chatId }, locale)
|
|
3783
|
+
]);
|
|
3784
|
+
const lists = {
|
|
3785
|
+
default: defaultCommands,
|
|
3786
|
+
allGroup: groupCommands,
|
|
3787
|
+
allAdmins: globalAdminCommands,
|
|
3788
|
+
chat: chatCommands,
|
|
3789
|
+
chatAdmins: chatAdminCommands
|
|
3790
|
+
};
|
|
3791
|
+
const localeName = locale || "neutral";
|
|
3792
|
+
if (!effectiveTelegramGroupCommands(lists, false).some((command) => command.command === "proxy")) missing.push(`${localeName} members`);
|
|
3793
|
+
if (!effectiveTelegramGroupCommands(lists, true).some((command) => command.command === "proxy")) missing.push(`${localeName} administrators`);
|
|
3794
|
+
}
|
|
3795
|
+
if (missing.length === 0) {
|
|
3796
|
+
results.push({ status: "pass", message: "Telegram command menus are synchronized for neutral/en/ru scopes (including /proxy)" });
|
|
3797
|
+
} else {
|
|
3798
|
+
results.push({
|
|
3799
|
+
status: "warn",
|
|
3800
|
+
message: `Telegram command menu is out of sync for ${missing.join(", ")} (missing /proxy). Restart OpenACP; if the warning remains, inspect Telegram connectivity logs.`
|
|
3801
|
+
});
|
|
3802
|
+
}
|
|
3803
|
+
} catch (err) {
|
|
3804
|
+
const message = redactNetworkSecrets(err instanceof Error ? err.message : String(err));
|
|
3805
|
+
results.push({
|
|
3806
|
+
status: "warn",
|
|
3807
|
+
message: `Cannot verify Telegram command menus: ${message}. Restart OpenACP; if the warning remains, inspect Telegram connectivity logs.`
|
|
3808
|
+
});
|
|
3809
|
+
}
|
|
3258
3810
|
return results;
|
|
3259
3811
|
}
|
|
3260
3812
|
};
|
|
@@ -3262,7 +3814,7 @@ var init_telegram = __esm({
|
|
|
3262
3814
|
});
|
|
3263
3815
|
|
|
3264
3816
|
// src/core/doctor/checks/storage.ts
|
|
3265
|
-
import * as
|
|
3817
|
+
import * as fs22 from "fs";
|
|
3266
3818
|
var storageCheck;
|
|
3267
3819
|
var init_storage = __esm({
|
|
3268
3820
|
"src/core/doctor/checks/storage.ts"() {
|
|
@@ -3272,28 +3824,28 @@ var init_storage = __esm({
|
|
|
3272
3824
|
order: 4,
|
|
3273
3825
|
async run(ctx) {
|
|
3274
3826
|
const results = [];
|
|
3275
|
-
if (!
|
|
3827
|
+
if (!fs22.existsSync(ctx.dataDir)) {
|
|
3276
3828
|
results.push({
|
|
3277
3829
|
status: "fail",
|
|
3278
3830
|
message: "Data directory ~/.openacp does not exist",
|
|
3279
3831
|
fixable: true,
|
|
3280
3832
|
fixRisk: "safe",
|
|
3281
3833
|
fix: async () => {
|
|
3282
|
-
|
|
3834
|
+
fs22.mkdirSync(ctx.dataDir, { recursive: true });
|
|
3283
3835
|
return { success: true, message: "created directory" };
|
|
3284
3836
|
}
|
|
3285
3837
|
});
|
|
3286
3838
|
} else {
|
|
3287
3839
|
try {
|
|
3288
|
-
|
|
3840
|
+
fs22.accessSync(ctx.dataDir, fs22.constants.W_OK);
|
|
3289
3841
|
results.push({ status: "pass", message: "Data directory exists and writable" });
|
|
3290
3842
|
} catch {
|
|
3291
3843
|
results.push({ status: "fail", message: "Data directory not writable" });
|
|
3292
3844
|
}
|
|
3293
3845
|
}
|
|
3294
|
-
if (
|
|
3846
|
+
if (fs22.existsSync(ctx.sessionsPath)) {
|
|
3295
3847
|
try {
|
|
3296
|
-
const content =
|
|
3848
|
+
const content = fs22.readFileSync(ctx.sessionsPath, "utf-8");
|
|
3297
3849
|
const data = JSON.parse(content);
|
|
3298
3850
|
if (typeof data === "object" && data !== null && "sessions" in data) {
|
|
3299
3851
|
results.push({ status: "pass", message: "Sessions file valid" });
|
|
@@ -3304,7 +3856,7 @@ var init_storage = __esm({
|
|
|
3304
3856
|
fixable: true,
|
|
3305
3857
|
fixRisk: "risky",
|
|
3306
3858
|
fix: async () => {
|
|
3307
|
-
|
|
3859
|
+
fs22.writeFileSync(ctx.sessionsPath, JSON.stringify({ version: 1, sessions: {} }, null, 2));
|
|
3308
3860
|
return { success: true, message: "reset sessions file" };
|
|
3309
3861
|
}
|
|
3310
3862
|
});
|
|
@@ -3316,7 +3868,7 @@ var init_storage = __esm({
|
|
|
3316
3868
|
fixable: true,
|
|
3317
3869
|
fixRisk: "risky",
|
|
3318
3870
|
fix: async () => {
|
|
3319
|
-
|
|
3871
|
+
fs22.writeFileSync(ctx.sessionsPath, JSON.stringify({ version: 1, sessions: {} }, null, 2));
|
|
3320
3872
|
return { success: true, message: "reset sessions file" };
|
|
3321
3873
|
}
|
|
3322
3874
|
});
|
|
@@ -3324,20 +3876,20 @@ var init_storage = __esm({
|
|
|
3324
3876
|
} else {
|
|
3325
3877
|
results.push({ status: "pass", message: "Sessions file not present yet (created on first session)" });
|
|
3326
3878
|
}
|
|
3327
|
-
if (!
|
|
3879
|
+
if (!fs22.existsSync(ctx.logsDir)) {
|
|
3328
3880
|
results.push({
|
|
3329
3881
|
status: "warn",
|
|
3330
3882
|
message: "Log directory does not exist",
|
|
3331
3883
|
fixable: true,
|
|
3332
3884
|
fixRisk: "safe",
|
|
3333
3885
|
fix: async () => {
|
|
3334
|
-
|
|
3886
|
+
fs22.mkdirSync(ctx.logsDir, { recursive: true });
|
|
3335
3887
|
return { success: true, message: "created log directory" };
|
|
3336
3888
|
}
|
|
3337
3889
|
});
|
|
3338
3890
|
} else {
|
|
3339
3891
|
try {
|
|
3340
|
-
|
|
3892
|
+
fs22.accessSync(ctx.logsDir, fs22.constants.W_OK);
|
|
3341
3893
|
results.push({ status: "pass", message: "Log directory exists and writable" });
|
|
3342
3894
|
} catch {
|
|
3343
3895
|
results.push({ status: "fail", message: "Log directory not writable" });
|
|
@@ -3350,8 +3902,8 @@ var init_storage = __esm({
|
|
|
3350
3902
|
});
|
|
3351
3903
|
|
|
3352
3904
|
// src/core/doctor/checks/workspace.ts
|
|
3353
|
-
import * as
|
|
3354
|
-
import * as
|
|
3905
|
+
import * as fs23 from "fs";
|
|
3906
|
+
import * as path22 from "path";
|
|
3355
3907
|
var workspaceCheck;
|
|
3356
3908
|
var init_workspace = __esm({
|
|
3357
3909
|
"src/core/doctor/checks/workspace.ts"() {
|
|
@@ -3361,21 +3913,21 @@ var init_workspace = __esm({
|
|
|
3361
3913
|
order: 5,
|
|
3362
3914
|
async run(ctx) {
|
|
3363
3915
|
const results = [];
|
|
3364
|
-
const workspace =
|
|
3365
|
-
if (!
|
|
3916
|
+
const workspace = path22.dirname(ctx.dataDir);
|
|
3917
|
+
if (!fs23.existsSync(workspace)) {
|
|
3366
3918
|
results.push({
|
|
3367
3919
|
status: "warn",
|
|
3368
3920
|
message: `Workspace directory does not exist: ${workspace}`,
|
|
3369
3921
|
fixable: true,
|
|
3370
3922
|
fixRisk: "safe",
|
|
3371
3923
|
fix: async () => {
|
|
3372
|
-
|
|
3924
|
+
fs23.mkdirSync(workspace, { recursive: true });
|
|
3373
3925
|
return { success: true, message: "created directory" };
|
|
3374
3926
|
}
|
|
3375
3927
|
});
|
|
3376
3928
|
} else {
|
|
3377
3929
|
try {
|
|
3378
|
-
|
|
3930
|
+
fs23.accessSync(workspace, fs23.constants.W_OK);
|
|
3379
3931
|
results.push({ status: "pass", message: `Workspace directory exists: ${workspace}` });
|
|
3380
3932
|
} catch {
|
|
3381
3933
|
results.push({ status: "fail", message: `Workspace directory not writable: ${workspace}` });
|
|
@@ -3388,8 +3940,8 @@ var init_workspace = __esm({
|
|
|
3388
3940
|
});
|
|
3389
3941
|
|
|
3390
3942
|
// src/core/doctor/checks/plugins.ts
|
|
3391
|
-
import * as
|
|
3392
|
-
import * as
|
|
3943
|
+
import * as fs24 from "fs";
|
|
3944
|
+
import * as path23 from "path";
|
|
3393
3945
|
var pluginsCheck;
|
|
3394
3946
|
var init_plugins = __esm({
|
|
3395
3947
|
"src/core/doctor/checks/plugins.ts"() {
|
|
@@ -3399,16 +3951,16 @@ var init_plugins = __esm({
|
|
|
3399
3951
|
order: 6,
|
|
3400
3952
|
async run(ctx) {
|
|
3401
3953
|
const results = [];
|
|
3402
|
-
if (!
|
|
3954
|
+
if (!fs24.existsSync(ctx.pluginsDir)) {
|
|
3403
3955
|
results.push({
|
|
3404
3956
|
status: "warn",
|
|
3405
3957
|
message: "Plugins directory does not exist",
|
|
3406
3958
|
fixable: true,
|
|
3407
3959
|
fixRisk: "safe",
|
|
3408
3960
|
fix: async () => {
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3961
|
+
fs24.mkdirSync(ctx.pluginsDir, { recursive: true });
|
|
3962
|
+
fs24.writeFileSync(
|
|
3963
|
+
path23.join(ctx.pluginsDir, "package.json"),
|
|
3412
3964
|
JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
|
|
3413
3965
|
);
|
|
3414
3966
|
return { success: true, message: "initialized plugins directory" };
|
|
@@ -3417,15 +3969,15 @@ var init_plugins = __esm({
|
|
|
3417
3969
|
return results;
|
|
3418
3970
|
}
|
|
3419
3971
|
results.push({ status: "pass", message: "Plugins directory exists" });
|
|
3420
|
-
const pkgPath =
|
|
3421
|
-
if (!
|
|
3972
|
+
const pkgPath = path23.join(ctx.pluginsDir, "package.json");
|
|
3973
|
+
if (!fs24.existsSync(pkgPath)) {
|
|
3422
3974
|
results.push({
|
|
3423
3975
|
status: "warn",
|
|
3424
3976
|
message: "Plugins package.json missing",
|
|
3425
3977
|
fixable: true,
|
|
3426
3978
|
fixRisk: "safe",
|
|
3427
3979
|
fix: async () => {
|
|
3428
|
-
|
|
3980
|
+
fs24.writeFileSync(
|
|
3429
3981
|
pkgPath,
|
|
3430
3982
|
JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
|
|
3431
3983
|
);
|
|
@@ -3435,7 +3987,7 @@ var init_plugins = __esm({
|
|
|
3435
3987
|
return results;
|
|
3436
3988
|
}
|
|
3437
3989
|
try {
|
|
3438
|
-
const pkg = JSON.parse(
|
|
3990
|
+
const pkg = JSON.parse(fs24.readFileSync(pkgPath, "utf-8"));
|
|
3439
3991
|
const deps = pkg.dependencies || {};
|
|
3440
3992
|
const count = Object.keys(deps).length;
|
|
3441
3993
|
results.push({ status: "pass", message: `Plugins package.json valid (${count} plugins)` });
|
|
@@ -3446,7 +3998,7 @@ var init_plugins = __esm({
|
|
|
3446
3998
|
fixable: true,
|
|
3447
3999
|
fixRisk: "risky",
|
|
3448
4000
|
fix: async () => {
|
|
3449
|
-
|
|
4001
|
+
fs24.writeFileSync(
|
|
3450
4002
|
pkgPath,
|
|
3451
4003
|
JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
|
|
3452
4004
|
);
|
|
@@ -3461,7 +4013,7 @@ var init_plugins = __esm({
|
|
|
3461
4013
|
});
|
|
3462
4014
|
|
|
3463
4015
|
// src/core/doctor/checks/daemon.ts
|
|
3464
|
-
import * as
|
|
4016
|
+
import * as fs25 from "fs";
|
|
3465
4017
|
import * as net3 from "net";
|
|
3466
4018
|
function isProcessAlive(pid) {
|
|
3467
4019
|
try {
|
|
@@ -3491,8 +4043,8 @@ var init_daemon = __esm({
|
|
|
3491
4043
|
order: 7,
|
|
3492
4044
|
async run(ctx) {
|
|
3493
4045
|
const results = [];
|
|
3494
|
-
if (
|
|
3495
|
-
const content =
|
|
4046
|
+
if (fs25.existsSync(ctx.pidPath)) {
|
|
4047
|
+
const content = fs25.readFileSync(ctx.pidPath, "utf-8").trim();
|
|
3496
4048
|
const pid = parseInt(content, 10);
|
|
3497
4049
|
if (isNaN(pid)) {
|
|
3498
4050
|
results.push({
|
|
@@ -3501,7 +4053,7 @@ var init_daemon = __esm({
|
|
|
3501
4053
|
fixable: true,
|
|
3502
4054
|
fixRisk: "safe",
|
|
3503
4055
|
fix: async () => {
|
|
3504
|
-
|
|
4056
|
+
fs25.unlinkSync(ctx.pidPath);
|
|
3505
4057
|
return { success: true, message: "removed invalid PID file" };
|
|
3506
4058
|
}
|
|
3507
4059
|
});
|
|
@@ -3512,7 +4064,7 @@ var init_daemon = __esm({
|
|
|
3512
4064
|
fixable: true,
|
|
3513
4065
|
fixRisk: "safe",
|
|
3514
4066
|
fix: async () => {
|
|
3515
|
-
|
|
4067
|
+
fs25.unlinkSync(ctx.pidPath);
|
|
3516
4068
|
return { success: true, message: "removed stale PID file" };
|
|
3517
4069
|
}
|
|
3518
4070
|
});
|
|
@@ -3520,8 +4072,8 @@ var init_daemon = __esm({
|
|
|
3520
4072
|
results.push({ status: "pass", message: `Daemon running (PID ${pid})` });
|
|
3521
4073
|
}
|
|
3522
4074
|
}
|
|
3523
|
-
if (
|
|
3524
|
-
const content =
|
|
4075
|
+
if (fs25.existsSync(ctx.portFilePath)) {
|
|
4076
|
+
const content = fs25.readFileSync(ctx.portFilePath, "utf-8").trim();
|
|
3525
4077
|
const port = parseInt(content, 10);
|
|
3526
4078
|
if (isNaN(port)) {
|
|
3527
4079
|
results.push({
|
|
@@ -3530,7 +4082,7 @@ var init_daemon = __esm({
|
|
|
3530
4082
|
fixable: true,
|
|
3531
4083
|
fixRisk: "safe",
|
|
3532
4084
|
fix: async () => {
|
|
3533
|
-
|
|
4085
|
+
fs25.unlinkSync(ctx.portFilePath);
|
|
3534
4086
|
return { success: true, message: "removed invalid port file" };
|
|
3535
4087
|
}
|
|
3536
4088
|
});
|
|
@@ -3542,8 +4094,8 @@ var init_daemon = __esm({
|
|
|
3542
4094
|
const apiPort = 21420;
|
|
3543
4095
|
const inUse = await checkPortInUse(apiPort);
|
|
3544
4096
|
if (inUse) {
|
|
3545
|
-
if (
|
|
3546
|
-
const pid = parseInt(
|
|
4097
|
+
if (fs25.existsSync(ctx.pidPath)) {
|
|
4098
|
+
const pid = parseInt(fs25.readFileSync(ctx.pidPath, "utf-8").trim(), 10);
|
|
3547
4099
|
if (!isNaN(pid) && isProcessAlive(pid)) {
|
|
3548
4100
|
results.push({ status: "pass", message: `API port ${apiPort} in use by OpenACP daemon` });
|
|
3549
4101
|
} else {
|
|
@@ -3563,17 +4115,17 @@ var init_daemon = __esm({
|
|
|
3563
4115
|
});
|
|
3564
4116
|
|
|
3565
4117
|
// src/core/utils/install-binary.ts
|
|
3566
|
-
import
|
|
3567
|
-
import
|
|
4118
|
+
import fs26 from "fs";
|
|
4119
|
+
import path24 from "path";
|
|
3568
4120
|
import https from "https";
|
|
3569
|
-
import
|
|
4121
|
+
import os7 from "os";
|
|
3570
4122
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
3571
4123
|
function downloadFile(url, dest, maxRedirects = 10) {
|
|
3572
4124
|
return new Promise((resolve7, reject) => {
|
|
3573
|
-
const file =
|
|
4125
|
+
const file = fs26.createWriteStream(dest);
|
|
3574
4126
|
const cleanup = () => {
|
|
3575
4127
|
try {
|
|
3576
|
-
if (
|
|
4128
|
+
if (fs26.existsSync(dest)) fs26.unlinkSync(dest);
|
|
3577
4129
|
} catch {
|
|
3578
4130
|
}
|
|
3579
4131
|
};
|
|
@@ -3628,8 +4180,8 @@ function downloadFile(url, dest, maxRedirects = 10) {
|
|
|
3628
4180
|
});
|
|
3629
4181
|
}
|
|
3630
4182
|
function getDownloadUrl(spec) {
|
|
3631
|
-
const platform2 =
|
|
3632
|
-
const arch =
|
|
4183
|
+
const platform2 = os7.platform();
|
|
4184
|
+
const arch = os7.arch();
|
|
3633
4185
|
const mapping = spec.platforms[platform2];
|
|
3634
4186
|
if (!mapping) throw new Error(`${spec.name}: unsupported platform ${platform2}`);
|
|
3635
4187
|
const binary = mapping[arch];
|
|
@@ -3639,50 +4191,50 @@ function getDownloadUrl(spec) {
|
|
|
3639
4191
|
async function ensureBinary(spec, binDir) {
|
|
3640
4192
|
const resolvedBinDir = binDir ?? DEFAULT_BIN_DIR;
|
|
3641
4193
|
const binName = IS_WINDOWS ? `${spec.name}.exe` : spec.name;
|
|
3642
|
-
const binPath =
|
|
4194
|
+
const binPath = path24.join(resolvedBinDir, binName);
|
|
3643
4195
|
if (commandExists(spec.name)) {
|
|
3644
|
-
|
|
4196
|
+
log19.debug({ name: spec.name }, "Found in PATH");
|
|
3645
4197
|
return spec.name;
|
|
3646
4198
|
}
|
|
3647
|
-
if (
|
|
3648
|
-
if (!IS_WINDOWS)
|
|
3649
|
-
|
|
4199
|
+
if (fs26.existsSync(binPath)) {
|
|
4200
|
+
if (!IS_WINDOWS) fs26.chmodSync(binPath, "755");
|
|
4201
|
+
log19.debug({ name: spec.name, path: binPath }, "Found in ~/.openacp/bin");
|
|
3650
4202
|
return binPath;
|
|
3651
4203
|
}
|
|
3652
|
-
|
|
3653
|
-
|
|
4204
|
+
log19.info({ name: spec.name }, "Not found, downloading from GitHub...");
|
|
4205
|
+
fs26.mkdirSync(resolvedBinDir, { recursive: true });
|
|
3654
4206
|
const url = getDownloadUrl(spec);
|
|
3655
4207
|
const isArchive = spec.isArchive?.(url) ?? false;
|
|
3656
|
-
const downloadDest = isArchive ?
|
|
4208
|
+
const downloadDest = isArchive ? path24.join(resolvedBinDir, `${spec.name}.tgz`) : binPath;
|
|
3657
4209
|
await downloadFile(url, downloadDest);
|
|
3658
4210
|
if (isArchive) {
|
|
3659
4211
|
const listing = execFileSync4("tar", ["-tf", downloadDest], { stdio: "pipe" }).toString().trim().split("\n").filter(Boolean);
|
|
3660
4212
|
validateTarContents(listing, resolvedBinDir);
|
|
3661
4213
|
execFileSync4("tar", ["-xzf", downloadDest, "-C", resolvedBinDir], { stdio: "pipe" });
|
|
3662
4214
|
try {
|
|
3663
|
-
|
|
4215
|
+
fs26.unlinkSync(downloadDest);
|
|
3664
4216
|
} catch {
|
|
3665
4217
|
}
|
|
3666
4218
|
}
|
|
3667
|
-
if (!
|
|
4219
|
+
if (!fs26.existsSync(binPath)) {
|
|
3668
4220
|
throw new Error(`${spec.name}: binary not found at ${binPath} after download/extraction. The archive structure may have changed.`);
|
|
3669
4221
|
}
|
|
3670
4222
|
if (!IS_WINDOWS) {
|
|
3671
|
-
|
|
4223
|
+
fs26.chmodSync(binPath, "755");
|
|
3672
4224
|
}
|
|
3673
|
-
|
|
4225
|
+
log19.info({ name: spec.name, path: binPath }, "Installed successfully");
|
|
3674
4226
|
return binPath;
|
|
3675
4227
|
}
|
|
3676
|
-
var
|
|
4228
|
+
var log19, DEFAULT_BIN_DIR, IS_WINDOWS;
|
|
3677
4229
|
var init_install_binary = __esm({
|
|
3678
4230
|
"src/core/utils/install-binary.ts"() {
|
|
3679
4231
|
"use strict";
|
|
3680
4232
|
init_log();
|
|
3681
4233
|
init_agent_dependencies();
|
|
3682
4234
|
init_agent_installer();
|
|
3683
|
-
|
|
3684
|
-
DEFAULT_BIN_DIR =
|
|
3685
|
-
IS_WINDOWS =
|
|
4235
|
+
log19 = createChildLogger({ module: "binary-installer" });
|
|
4236
|
+
DEFAULT_BIN_DIR = path24.join(os7.homedir(), ".openacp", "bin");
|
|
4237
|
+
IS_WINDOWS = os7.platform() === "win32";
|
|
3686
4238
|
}
|
|
3687
4239
|
});
|
|
3688
4240
|
|
|
@@ -3722,9 +4274,9 @@ var init_install_cloudflared = __esm({
|
|
|
3722
4274
|
});
|
|
3723
4275
|
|
|
3724
4276
|
// src/core/doctor/checks/tunnel.ts
|
|
3725
|
-
import * as
|
|
3726
|
-
import * as
|
|
3727
|
-
import * as
|
|
4277
|
+
import * as fs27 from "fs";
|
|
4278
|
+
import * as path25 from "path";
|
|
4279
|
+
import * as os8 from "os";
|
|
3728
4280
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
3729
4281
|
var tunnelCheck;
|
|
3730
4282
|
var init_tunnel = __esm({
|
|
@@ -3740,7 +4292,7 @@ var init_tunnel = __esm({
|
|
|
3740
4292
|
return results;
|
|
3741
4293
|
}
|
|
3742
4294
|
const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
|
|
3743
|
-
const sm = new SettingsManager2(
|
|
4295
|
+
const sm = new SettingsManager2(path25.join(ctx.pluginsDir, "data"));
|
|
3744
4296
|
const tunnelSettings = await sm.loadSettings("@openacp/tunnel");
|
|
3745
4297
|
const tunnelEnabled = tunnelSettings.enabled ?? false;
|
|
3746
4298
|
const provider = tunnelSettings.provider ?? "cloudflare";
|
|
@@ -3751,10 +4303,10 @@ var init_tunnel = __esm({
|
|
|
3751
4303
|
}
|
|
3752
4304
|
results.push({ status: "pass", message: `Tunnel provider: ${provider}` });
|
|
3753
4305
|
if (provider === "cloudflare") {
|
|
3754
|
-
const binName =
|
|
3755
|
-
const binPath =
|
|
4306
|
+
const binName = os8.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
|
|
4307
|
+
const binPath = path25.join(ctx.dataDir, "bin", binName);
|
|
3756
4308
|
let found = false;
|
|
3757
|
-
if (
|
|
4309
|
+
if (fs27.existsSync(binPath)) {
|
|
3758
4310
|
found = true;
|
|
3759
4311
|
} else {
|
|
3760
4312
|
try {
|
|
@@ -3828,8 +4380,8 @@ var init_proxy = __esm({
|
|
|
3828
4380
|
});
|
|
3829
4381
|
|
|
3830
4382
|
// src/core/doctor/index.ts
|
|
3831
|
-
import * as
|
|
3832
|
-
import * as
|
|
4383
|
+
import * as fs28 from "fs";
|
|
4384
|
+
import * as path26 from "path";
|
|
3833
4385
|
var ALL_CHECKS, CHECK_TIMEOUT_MS, DoctorEngine;
|
|
3834
4386
|
var init_doctor = __esm({
|
|
3835
4387
|
"src/core/doctor/index.ts"() {
|
|
@@ -3922,27 +4474,27 @@ var init_doctor = __esm({
|
|
|
3922
4474
|
/** Constructs the shared context used by all checks — loads config if available. */
|
|
3923
4475
|
async buildContext() {
|
|
3924
4476
|
const dataDir = this.dataDir;
|
|
3925
|
-
const configPath = process.env.OPENACP_CONFIG_PATH ||
|
|
4477
|
+
const configPath = process.env.OPENACP_CONFIG_PATH || path26.join(dataDir, "config.json");
|
|
3926
4478
|
let config = null;
|
|
3927
4479
|
let rawConfig = null;
|
|
3928
4480
|
try {
|
|
3929
|
-
const content =
|
|
4481
|
+
const content = fs28.readFileSync(configPath, "utf-8");
|
|
3930
4482
|
rawConfig = JSON.parse(content);
|
|
3931
4483
|
const cm = new ConfigManager(configPath);
|
|
3932
4484
|
await cm.load();
|
|
3933
4485
|
config = cm.get();
|
|
3934
4486
|
} catch {
|
|
3935
4487
|
}
|
|
3936
|
-
const logsDir = config ? expandHome2(config.logging.logDir) :
|
|
4488
|
+
const logsDir = config ? expandHome2(config.logging.logDir) : path26.join(dataDir, "logs");
|
|
3937
4489
|
return {
|
|
3938
4490
|
config,
|
|
3939
4491
|
rawConfig,
|
|
3940
4492
|
configPath,
|
|
3941
4493
|
dataDir,
|
|
3942
|
-
sessionsPath:
|
|
3943
|
-
pidPath:
|
|
3944
|
-
portFilePath:
|
|
3945
|
-
pluginsDir:
|
|
4494
|
+
sessionsPath: path26.join(dataDir, "sessions.json"),
|
|
4495
|
+
pidPath: path26.join(dataDir, "openacp.pid"),
|
|
4496
|
+
portFilePath: path26.join(dataDir, "api.port"),
|
|
4497
|
+
pluginsDir: path26.join(dataDir, "plugins"),
|
|
3946
4498
|
logsDir,
|
|
3947
4499
|
fetchForScope: (scope) => this.proxyService.createFetch(scope)
|
|
3948
4500
|
};
|
|
@@ -3951,26 +4503,13 @@ var init_doctor = __esm({
|
|
|
3951
4503
|
}
|
|
3952
4504
|
});
|
|
3953
4505
|
|
|
3954
|
-
// src/core/instance/instance-context.ts
|
|
3955
|
-
import path26 from "path";
|
|
3956
|
-
import fs28 from "fs";
|
|
3957
|
-
import os8 from "os";
|
|
3958
|
-
function getGlobalRoot() {
|
|
3959
|
-
return path26.join(os8.homedir(), ".openacp");
|
|
3960
|
-
}
|
|
3961
|
-
var init_instance_context = __esm({
|
|
3962
|
-
"src/core/instance/instance-context.ts"() {
|
|
3963
|
-
"use strict";
|
|
3964
|
-
}
|
|
3965
|
-
});
|
|
3966
|
-
|
|
3967
4506
|
// src/core/instance/instance-registry.ts
|
|
3968
|
-
import
|
|
3969
|
-
import
|
|
4507
|
+
import fs30 from "fs";
|
|
4508
|
+
import path28 from "path";
|
|
3970
4509
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3971
4510
|
function readIdFromConfig(instanceRoot) {
|
|
3972
4511
|
try {
|
|
3973
|
-
const raw = JSON.parse(
|
|
4512
|
+
const raw = JSON.parse(fs30.readFileSync(path28.join(instanceRoot, "config.json"), "utf-8"));
|
|
3974
4513
|
return typeof raw.id === "string" && raw.id ? raw.id : null;
|
|
3975
4514
|
} catch {
|
|
3976
4515
|
return null;
|
|
@@ -3989,7 +4528,7 @@ var init_instance_registry = __esm({
|
|
|
3989
4528
|
/** Load the registry from disk. If the file is missing or corrupt, starts fresh. */
|
|
3990
4529
|
load() {
|
|
3991
4530
|
try {
|
|
3992
|
-
const raw =
|
|
4531
|
+
const raw = fs30.readFileSync(this.registryPath, "utf-8");
|
|
3993
4532
|
const parsed = JSON.parse(raw);
|
|
3994
4533
|
if (parsed.version === 1 && parsed.instances) {
|
|
3995
4534
|
this.data = parsed;
|
|
@@ -4016,9 +4555,9 @@ var init_instance_registry = __esm({
|
|
|
4016
4555
|
}
|
|
4017
4556
|
/** Persist the registry to disk, creating parent directories if needed. */
|
|
4018
4557
|
save() {
|
|
4019
|
-
const dir =
|
|
4020
|
-
|
|
4021
|
-
|
|
4558
|
+
const dir = path28.dirname(this.registryPath);
|
|
4559
|
+
fs30.mkdirSync(dir, { recursive: true });
|
|
4560
|
+
fs30.writeFileSync(this.registryPath, JSON.stringify(this.data, null, 2));
|
|
4022
4561
|
}
|
|
4023
4562
|
/** Add or update an instance entry in the registry. Does not persist — call save() after. */
|
|
4024
4563
|
register(id, root) {
|
|
@@ -4085,15 +4624,15 @@ __export(plugin_installer_exports, {
|
|
|
4085
4624
|
});
|
|
4086
4625
|
import { execFile as execFile2 } from "child_process";
|
|
4087
4626
|
import { promisify as promisify2 } from "util";
|
|
4088
|
-
import * as
|
|
4089
|
-
import * as
|
|
4627
|
+
import * as fs32 from "fs/promises";
|
|
4628
|
+
import * as path30 from "path";
|
|
4090
4629
|
import { pathToFileURL } from "url";
|
|
4091
4630
|
async function importFromDir(packageName, dir) {
|
|
4092
|
-
const pkgDir =
|
|
4093
|
-
const pkgJsonPath =
|
|
4631
|
+
const pkgDir = path30.join(dir, "node_modules", ...packageName.split("/"));
|
|
4632
|
+
const pkgJsonPath = path30.join(pkgDir, "package.json");
|
|
4094
4633
|
let pkgJson;
|
|
4095
4634
|
try {
|
|
4096
|
-
pkgJson = JSON.parse(await
|
|
4635
|
+
pkgJson = JSON.parse(await fs32.readFile(pkgJsonPath, "utf-8"));
|
|
4097
4636
|
} catch (err) {
|
|
4098
4637
|
throw new Error(`Cannot read package.json for "${packageName}" at ${pkgJsonPath}: ${err.message}`);
|
|
4099
4638
|
}
|
|
@@ -4106,9 +4645,9 @@ async function importFromDir(packageName, dir) {
|
|
|
4106
4645
|
} else {
|
|
4107
4646
|
entry = pkgJson.main ?? "index.js";
|
|
4108
4647
|
}
|
|
4109
|
-
const entryPath =
|
|
4648
|
+
const entryPath = path30.join(pkgDir, entry);
|
|
4110
4649
|
try {
|
|
4111
|
-
await
|
|
4650
|
+
await fs32.access(entryPath);
|
|
4112
4651
|
} catch {
|
|
4113
4652
|
throw new Error(`Entry point "${entry}" not found for "${packageName}" at ${entryPath}`);
|
|
4114
4653
|
}
|
|
@@ -4201,10 +4740,10 @@ var install_context_exports = {};
|
|
|
4201
4740
|
__export(install_context_exports, {
|
|
4202
4741
|
createInstallContext: () => createInstallContext
|
|
4203
4742
|
});
|
|
4204
|
-
import
|
|
4743
|
+
import path31 from "path";
|
|
4205
4744
|
function createInstallContext(opts) {
|
|
4206
4745
|
const { pluginName, settingsManager, basePath, instanceRoot } = opts;
|
|
4207
|
-
const dataDir =
|
|
4746
|
+
const dataDir = path31.join(basePath, pluginName, "data");
|
|
4208
4747
|
return {
|
|
4209
4748
|
pluginName,
|
|
4210
4749
|
terminal: createTerminalIO(),
|
|
@@ -4232,14 +4771,14 @@ __export(api_client_exports, {
|
|
|
4232
4771
|
waitForApiReady: () => waitForApiReady,
|
|
4233
4772
|
waitForPortFile: () => waitForPortFile
|
|
4234
4773
|
});
|
|
4235
|
-
import * as
|
|
4236
|
-
import * as
|
|
4774
|
+
import * as fs33 from "fs";
|
|
4775
|
+
import * as path32 from "path";
|
|
4237
4776
|
import { setTimeout as sleep } from "timers/promises";
|
|
4238
4777
|
function defaultPortFile(root) {
|
|
4239
|
-
return
|
|
4778
|
+
return path32.join(root, "api.port");
|
|
4240
4779
|
}
|
|
4241
4780
|
function defaultSecretFile(root) {
|
|
4242
|
-
return
|
|
4781
|
+
return path32.join(root, "api-secret");
|
|
4243
4782
|
}
|
|
4244
4783
|
async function waitForPortFile(portFilePath, timeoutMs = 5e3, intervalMs = 100) {
|
|
4245
4784
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -4273,7 +4812,7 @@ async function waitForApiReady(instanceRoot, expectedInstanceId, timeoutMs = 1e4
|
|
|
4273
4812
|
function readApiPort(portFilePath, instanceRoot) {
|
|
4274
4813
|
const filePath = portFilePath ?? defaultPortFile(instanceRoot);
|
|
4275
4814
|
try {
|
|
4276
|
-
const content =
|
|
4815
|
+
const content = fs33.readFileSync(filePath, "utf-8").trim();
|
|
4277
4816
|
const port = parseInt(content, 10);
|
|
4278
4817
|
return isNaN(port) ? null : port;
|
|
4279
4818
|
} catch {
|
|
@@ -4283,7 +4822,7 @@ function readApiPort(portFilePath, instanceRoot) {
|
|
|
4283
4822
|
function readApiSecret(secretFilePath, instanceRoot) {
|
|
4284
4823
|
const filePath = secretFilePath ?? defaultSecretFile(instanceRoot);
|
|
4285
4824
|
try {
|
|
4286
|
-
const content =
|
|
4825
|
+
const content = fs33.readFileSync(filePath, "utf-8").trim();
|
|
4287
4826
|
return content || null;
|
|
4288
4827
|
} catch {
|
|
4289
4828
|
return null;
|
|
@@ -4292,7 +4831,7 @@ function readApiSecret(secretFilePath, instanceRoot) {
|
|
|
4292
4831
|
function removeStalePortFile(portFilePath, instanceRoot) {
|
|
4293
4832
|
const filePath = portFilePath ?? defaultPortFile(instanceRoot);
|
|
4294
4833
|
try {
|
|
4295
|
-
|
|
4834
|
+
fs33.unlinkSync(filePath);
|
|
4296
4835
|
} catch {
|
|
4297
4836
|
}
|
|
4298
4837
|
}
|
|
@@ -4429,8 +4968,8 @@ var init_notification = __esm({
|
|
|
4429
4968
|
});
|
|
4430
4969
|
|
|
4431
4970
|
// src/plugins/file-service/file-service.ts
|
|
4432
|
-
import
|
|
4433
|
-
import
|
|
4971
|
+
import fs35 from "fs";
|
|
4972
|
+
import path35 from "path";
|
|
4434
4973
|
import { OggOpusDecoder } from "ogg-opus-decoder";
|
|
4435
4974
|
import wav from "node-wav";
|
|
4436
4975
|
function classifyMime(mimeType) {
|
|
@@ -4487,14 +5026,14 @@ var init_file_service = __esm({
|
|
|
4487
5026
|
const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1e3;
|
|
4488
5027
|
let removed = 0;
|
|
4489
5028
|
try {
|
|
4490
|
-
const entries = await
|
|
5029
|
+
const entries = await fs35.promises.readdir(this.baseDir, { withFileTypes: true });
|
|
4491
5030
|
for (const entry of entries) {
|
|
4492
5031
|
if (!entry.isDirectory()) continue;
|
|
4493
|
-
const dirPath =
|
|
5032
|
+
const dirPath = path35.join(this.baseDir, entry.name);
|
|
4494
5033
|
try {
|
|
4495
|
-
const stat = await
|
|
5034
|
+
const stat = await fs35.promises.stat(dirPath);
|
|
4496
5035
|
if (stat.mtimeMs < cutoff) {
|
|
4497
|
-
await
|
|
5036
|
+
await fs35.promises.rm(dirPath, { recursive: true, force: true });
|
|
4498
5037
|
removed++;
|
|
4499
5038
|
}
|
|
4500
5039
|
} catch {
|
|
@@ -4513,11 +5052,11 @@ var init_file_service = __esm({
|
|
|
4513
5052
|
* so the user-facing name is not lost.
|
|
4514
5053
|
*/
|
|
4515
5054
|
async saveFile(sessionId, fileName, data, mimeType) {
|
|
4516
|
-
const sessionDir =
|
|
4517
|
-
await
|
|
5055
|
+
const sessionDir = path35.join(this.baseDir, sessionId);
|
|
5056
|
+
await fs35.promises.mkdir(sessionDir, { recursive: true });
|
|
4518
5057
|
const safeName = `${Date.now()}-${fileName.replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
4519
|
-
const filePath =
|
|
4520
|
-
await
|
|
5058
|
+
const filePath = path35.join(sessionDir, safeName);
|
|
5059
|
+
await fs35.promises.writeFile(filePath, data);
|
|
4521
5060
|
return {
|
|
4522
5061
|
type: classifyMime(mimeType),
|
|
4523
5062
|
filePath,
|
|
@@ -4532,14 +5071,14 @@ var init_file_service = __esm({
|
|
|
4532
5071
|
*/
|
|
4533
5072
|
async resolveFile(filePath) {
|
|
4534
5073
|
try {
|
|
4535
|
-
const stat = await
|
|
5074
|
+
const stat = await fs35.promises.stat(filePath);
|
|
4536
5075
|
if (!stat.isFile()) return null;
|
|
4537
|
-
const ext =
|
|
5076
|
+
const ext = path35.extname(filePath).toLowerCase();
|
|
4538
5077
|
const mimeType = EXT_TO_MIME[ext] || "application/octet-stream";
|
|
4539
5078
|
return {
|
|
4540
5079
|
type: classifyMime(mimeType),
|
|
4541
5080
|
filePath,
|
|
4542
|
-
fileName:
|
|
5081
|
+
fileName: path35.basename(filePath),
|
|
4543
5082
|
mimeType,
|
|
4544
5083
|
size: stat.size
|
|
4545
5084
|
};
|
|
@@ -4804,7 +5343,7 @@ function createAuthPreHandler(getSecret, getJwtSecret, tokenStore) {
|
|
|
4804
5343
|
const queryToken = request.query?.token;
|
|
4805
5344
|
const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : queryToken;
|
|
4806
5345
|
if (queryToken && !authHeader) {
|
|
4807
|
-
|
|
5346
|
+
log23.warn(
|
|
4808
5347
|
{ url: request.url.replace(/([?&]token=)[^&]+/, "$1[REDACTED]") },
|
|
4809
5348
|
"Token passed via URL query param \u2014 use Authorization: Bearer header to avoid token leakage in tunnel/proxy logs"
|
|
4810
5349
|
);
|
|
@@ -4858,7 +5397,7 @@ function requireRole(role) {
|
|
|
4858
5397
|
}
|
|
4859
5398
|
};
|
|
4860
5399
|
}
|
|
4861
|
-
var
|
|
5400
|
+
var log23;
|
|
4862
5401
|
var init_auth = __esm({
|
|
4863
5402
|
"src/plugins/api-server/middleware/auth.ts"() {
|
|
4864
5403
|
"use strict";
|
|
@@ -4866,7 +5405,7 @@ var init_auth = __esm({
|
|
|
4866
5405
|
init_jwt();
|
|
4867
5406
|
init_roles();
|
|
4868
5407
|
init_log();
|
|
4869
|
-
|
|
5408
|
+
log23 = createChildLogger({ module: "api-auth" });
|
|
4870
5409
|
}
|
|
4871
5410
|
});
|
|
4872
5411
|
|
|
@@ -5182,8 +5721,8 @@ data: ${JSON.stringify(data)}
|
|
|
5182
5721
|
});
|
|
5183
5722
|
|
|
5184
5723
|
// src/plugins/api-server/static-server.ts
|
|
5185
|
-
import * as
|
|
5186
|
-
import * as
|
|
5724
|
+
import * as fs36 from "fs";
|
|
5725
|
+
import * as path36 from "path";
|
|
5187
5726
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5188
5727
|
var MIME_TYPES, StaticServer;
|
|
5189
5728
|
var init_static_server = __esm({
|
|
@@ -5207,16 +5746,16 @@ var init_static_server = __esm({
|
|
|
5207
5746
|
this.uiDir = uiDir;
|
|
5208
5747
|
if (!this.uiDir) {
|
|
5209
5748
|
const __filename = fileURLToPath2(import.meta.url);
|
|
5210
|
-
const candidate =
|
|
5211
|
-
if (
|
|
5749
|
+
const candidate = path36.resolve(path36.dirname(__filename), "../../ui/dist");
|
|
5750
|
+
if (fs36.existsSync(path36.join(candidate, "index.html"))) {
|
|
5212
5751
|
this.uiDir = candidate;
|
|
5213
5752
|
}
|
|
5214
5753
|
if (!this.uiDir) {
|
|
5215
|
-
const publishCandidate =
|
|
5216
|
-
|
|
5754
|
+
const publishCandidate = path36.resolve(
|
|
5755
|
+
path36.dirname(__filename),
|
|
5217
5756
|
"../ui"
|
|
5218
5757
|
);
|
|
5219
|
-
if (
|
|
5758
|
+
if (fs36.existsSync(path36.join(publishCandidate, "index.html"))) {
|
|
5220
5759
|
this.uiDir = publishCandidate;
|
|
5221
5760
|
}
|
|
5222
5761
|
}
|
|
@@ -5234,23 +5773,23 @@ var init_static_server = __esm({
|
|
|
5234
5773
|
serve(req, res) {
|
|
5235
5774
|
if (!this.uiDir) return false;
|
|
5236
5775
|
const urlPath = (req.url || "/").split("?")[0];
|
|
5237
|
-
const safePath =
|
|
5238
|
-
const filePath =
|
|
5239
|
-
if (!filePath.startsWith(this.uiDir +
|
|
5776
|
+
const safePath = path36.normalize(urlPath);
|
|
5777
|
+
const filePath = path36.join(this.uiDir, safePath);
|
|
5778
|
+
if (!filePath.startsWith(this.uiDir + path36.sep) && filePath !== this.uiDir)
|
|
5240
5779
|
return false;
|
|
5241
5780
|
let realFilePath;
|
|
5242
5781
|
try {
|
|
5243
|
-
realFilePath =
|
|
5782
|
+
realFilePath = fs36.realpathSync(filePath);
|
|
5244
5783
|
} catch {
|
|
5245
5784
|
realFilePath = null;
|
|
5246
5785
|
}
|
|
5247
5786
|
if (realFilePath !== null) {
|
|
5248
|
-
const realUiDir =
|
|
5249
|
-
if (!realFilePath.startsWith(realUiDir +
|
|
5787
|
+
const realUiDir = fs36.realpathSync(this.uiDir);
|
|
5788
|
+
if (!realFilePath.startsWith(realUiDir + path36.sep) && realFilePath !== realUiDir)
|
|
5250
5789
|
return false;
|
|
5251
5790
|
}
|
|
5252
|
-
if (realFilePath !== null &&
|
|
5253
|
-
const ext =
|
|
5791
|
+
if (realFilePath !== null && fs36.existsSync(realFilePath) && fs36.statSync(realFilePath).isFile()) {
|
|
5792
|
+
const ext = path36.extname(filePath);
|
|
5254
5793
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
5255
5794
|
const isHashed = /\.[a-zA-Z0-9]{8,}\.(js|css)$/.test(filePath);
|
|
5256
5795
|
const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "no-cache";
|
|
@@ -5258,16 +5797,16 @@ var init_static_server = __esm({
|
|
|
5258
5797
|
"Content-Type": contentType,
|
|
5259
5798
|
"Cache-Control": cacheControl
|
|
5260
5799
|
});
|
|
5261
|
-
|
|
5800
|
+
fs36.createReadStream(realFilePath).pipe(res);
|
|
5262
5801
|
return true;
|
|
5263
5802
|
}
|
|
5264
|
-
const indexPath =
|
|
5265
|
-
if (
|
|
5803
|
+
const indexPath = path36.join(this.uiDir, "index.html");
|
|
5804
|
+
if (fs36.existsSync(indexPath)) {
|
|
5266
5805
|
res.writeHead(200, {
|
|
5267
5806
|
"Content-Type": "text/html; charset=utf-8",
|
|
5268
5807
|
"Cache-Control": "no-cache"
|
|
5269
5808
|
});
|
|
5270
|
-
|
|
5809
|
+
fs36.createReadStream(indexPath).pipe(res);
|
|
5271
5810
|
return true;
|
|
5272
5811
|
}
|
|
5273
5812
|
return false;
|
|
@@ -5390,8 +5929,8 @@ var init_exports = __esm({
|
|
|
5390
5929
|
});
|
|
5391
5930
|
|
|
5392
5931
|
// src/plugins/context/context-cache.ts
|
|
5393
|
-
import * as
|
|
5394
|
-
import * as
|
|
5932
|
+
import * as fs37 from "fs";
|
|
5933
|
+
import * as path37 from "path";
|
|
5395
5934
|
import * as crypto2 from "crypto";
|
|
5396
5935
|
var DEFAULT_TTL_MS, ContextCache;
|
|
5397
5936
|
var init_context_cache = __esm({
|
|
@@ -5402,7 +5941,7 @@ var init_context_cache = __esm({
|
|
|
5402
5941
|
constructor(cacheDir, ttlMs = DEFAULT_TTL_MS) {
|
|
5403
5942
|
this.cacheDir = cacheDir;
|
|
5404
5943
|
this.ttlMs = ttlMs;
|
|
5405
|
-
|
|
5944
|
+
fs37.mkdirSync(cacheDir, { recursive: true });
|
|
5406
5945
|
}
|
|
5407
5946
|
cacheDir;
|
|
5408
5947
|
ttlMs;
|
|
@@ -5410,23 +5949,23 @@ var init_context_cache = __esm({
|
|
|
5410
5949
|
return crypto2.createHash("sha256").update(`${repoPath}:${queryKey}`).digest("hex").slice(0, 16);
|
|
5411
5950
|
}
|
|
5412
5951
|
filePath(repoPath, queryKey) {
|
|
5413
|
-
return
|
|
5952
|
+
return path37.join(this.cacheDir, `${this.keyHash(repoPath, queryKey)}.json`);
|
|
5414
5953
|
}
|
|
5415
5954
|
get(repoPath, queryKey) {
|
|
5416
5955
|
const fp = this.filePath(repoPath, queryKey);
|
|
5417
5956
|
try {
|
|
5418
|
-
const stat =
|
|
5957
|
+
const stat = fs37.statSync(fp);
|
|
5419
5958
|
if (Date.now() - stat.mtimeMs > this.ttlMs) {
|
|
5420
|
-
|
|
5959
|
+
fs37.unlinkSync(fp);
|
|
5421
5960
|
return null;
|
|
5422
5961
|
}
|
|
5423
|
-
return JSON.parse(
|
|
5962
|
+
return JSON.parse(fs37.readFileSync(fp, "utf-8"));
|
|
5424
5963
|
} catch {
|
|
5425
5964
|
return null;
|
|
5426
5965
|
}
|
|
5427
5966
|
}
|
|
5428
5967
|
set(repoPath, queryKey, result) {
|
|
5429
|
-
|
|
5968
|
+
fs37.writeFileSync(this.filePath(repoPath, queryKey), JSON.stringify(result));
|
|
5430
5969
|
}
|
|
5431
5970
|
};
|
|
5432
5971
|
}
|
|
@@ -6400,8 +6939,8 @@ function formatToolSummary(name, rawInput, displaySummary) {
|
|
|
6400
6939
|
}
|
|
6401
6940
|
if (lowerName === "grep") {
|
|
6402
6941
|
const pattern = args.pattern ?? "";
|
|
6403
|
-
const
|
|
6404
|
-
return pattern ? `\u{1F50D} Grep "${pattern}"${
|
|
6942
|
+
const path38 = args.path ?? "";
|
|
6943
|
+
return pattern ? `\u{1F50D} Grep "${pattern}"${path38 ? ` in ${path38}` : ""}` : `\u{1F527} ${name}`;
|
|
6405
6944
|
}
|
|
6406
6945
|
if (lowerName === "glob") {
|
|
6407
6946
|
const pattern = args.pattern ?? "";
|
|
@@ -6437,8 +6976,8 @@ function formatToolTitle(name, rawInput, displayTitle) {
|
|
|
6437
6976
|
}
|
|
6438
6977
|
if (lowerName === "grep") {
|
|
6439
6978
|
const pattern = args.pattern ?? "";
|
|
6440
|
-
const
|
|
6441
|
-
return pattern ? `"${pattern}"${
|
|
6979
|
+
const path38 = args.path ?? "";
|
|
6980
|
+
return pattern ? `"${pattern}"${path38 ? ` in ${path38}` : ""}` : name;
|
|
6442
6981
|
}
|
|
6443
6982
|
if (lowerName === "glob") {
|
|
6444
6983
|
return String(args.pattern ?? name);
|
|
@@ -7890,7 +8429,7 @@ function setupDangerousModeCallbacks(bot, core) {
|
|
|
7890
8429
|
}).catch(() => {
|
|
7891
8430
|
});
|
|
7892
8431
|
}
|
|
7893
|
-
|
|
8432
|
+
log25.info({ sessionId, wantOn }, "Bypass permissions toggled via button");
|
|
7894
8433
|
try {
|
|
7895
8434
|
await ctx.editMessageText(buildSessionStatusText(session), {
|
|
7896
8435
|
parse_mode: "HTML",
|
|
@@ -7913,7 +8452,7 @@ function setupDangerousModeCallbacks(bot, core) {
|
|
|
7913
8452
|
const newDangerousMode = !(record.clientOverrides?.bypassPermissions ?? record.dangerousMode ?? false);
|
|
7914
8453
|
core.sessionManager.patchRecord(sessionId, { clientOverrides: { bypassPermissions: newDangerousMode } }).catch(() => {
|
|
7915
8454
|
});
|
|
7916
|
-
|
|
8455
|
+
log25.info(
|
|
7917
8456
|
{ sessionId, dangerousMode: newDangerousMode },
|
|
7918
8457
|
"Bypass permissions toggled via button (store-only, session not in memory)"
|
|
7919
8458
|
);
|
|
@@ -8103,14 +8642,14 @@ async function handleRestart(ctx, core) {
|
|
|
8103
8642
|
await new Promise((r) => setTimeout(r, 500));
|
|
8104
8643
|
await core.requestRestart();
|
|
8105
8644
|
}
|
|
8106
|
-
var
|
|
8645
|
+
var log25, OUTPUT_MODE_LABELS;
|
|
8107
8646
|
var init_admin = __esm({
|
|
8108
8647
|
"src/plugins/telegram/commands/admin.ts"() {
|
|
8109
8648
|
"use strict";
|
|
8110
8649
|
init_bypass_detection();
|
|
8111
8650
|
init_formatting();
|
|
8112
8651
|
init_log();
|
|
8113
|
-
|
|
8652
|
+
log25 = createChildLogger({ module: "telegram-cmd-admin" });
|
|
8114
8653
|
OUTPUT_MODE_LABELS = {
|
|
8115
8654
|
low: "\u{1F507} Low",
|
|
8116
8655
|
medium: "\u{1F4CA} Medium",
|
|
@@ -8125,7 +8664,7 @@ function botFromCtx(ctx) {
|
|
|
8125
8664
|
return { api: ctx.api };
|
|
8126
8665
|
}
|
|
8127
8666
|
async function createSessionDirect(ctx, core, chatId, agentName, workspace, onControlMessage) {
|
|
8128
|
-
|
|
8667
|
+
log26.info({ userId: ctx.from?.id, agentName, workspace }, "New session command (direct)");
|
|
8129
8668
|
let threadId;
|
|
8130
8669
|
try {
|
|
8131
8670
|
const topicName = `\u{1F504} New Session`;
|
|
@@ -8152,7 +8691,7 @@ async function createSessionDirect(ctx, core, chatId, agentName, workspace, onCo
|
|
|
8152
8691
|
onControlMessage?.(session.id, controlMsg.message_id);
|
|
8153
8692
|
return threadId ?? null;
|
|
8154
8693
|
} catch (err) {
|
|
8155
|
-
|
|
8694
|
+
log26.error({ err }, "Session creation failed");
|
|
8156
8695
|
if (threadId) {
|
|
8157
8696
|
try {
|
|
8158
8697
|
await ctx.api.deleteForumTopic(chatId, threadId);
|
|
@@ -8385,7 +8924,7 @@ Agent: <code>${escapeHtml(agentKey)}</code>
|
|
|
8385
8924
|
await _sendCustomPathPrompt(ctx, chatId, agentKey);
|
|
8386
8925
|
});
|
|
8387
8926
|
}
|
|
8388
|
-
var
|
|
8927
|
+
var log26, WS_CACHE_MAX, workspaceCache, nextWsId, _forceReplyMap;
|
|
8389
8928
|
var init_new_session = __esm({
|
|
8390
8929
|
"src/plugins/telegram/commands/new-session.ts"() {
|
|
8391
8930
|
"use strict";
|
|
@@ -8393,7 +8932,7 @@ var init_new_session = __esm({
|
|
|
8393
8932
|
init_topics();
|
|
8394
8933
|
init_log();
|
|
8395
8934
|
init_admin();
|
|
8396
|
-
|
|
8935
|
+
log26 = createChildLogger({ module: "telegram-cmd-new-session" });
|
|
8397
8936
|
WS_CACHE_MAX = 50;
|
|
8398
8937
|
workspaceCache = /* @__PURE__ */ new Map();
|
|
8399
8938
|
nextWsId = 0;
|
|
@@ -8458,7 +8997,7 @@ ${lines.join("\n")}${truncated}`,
|
|
|
8458
8997
|
{ parse_mode: "HTML", reply_markup: keyboard }
|
|
8459
8998
|
);
|
|
8460
8999
|
} catch (err) {
|
|
8461
|
-
|
|
9000
|
+
log27.error({ err }, "handleTopics error");
|
|
8462
9001
|
await ctx.reply("\u274C Failed to list sessions.", { parse_mode: "HTML" }).catch(() => {
|
|
8463
9002
|
});
|
|
8464
9003
|
}
|
|
@@ -8479,13 +9018,13 @@ async function handleCleanup(ctx, core, chatId, statuses) {
|
|
|
8479
9018
|
try {
|
|
8480
9019
|
await ctx.api.deleteForumTopic(chatId, topicId);
|
|
8481
9020
|
} catch (err) {
|
|
8482
|
-
|
|
9021
|
+
log27.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
|
|
8483
9022
|
}
|
|
8484
9023
|
}
|
|
8485
9024
|
await core.sessionManager.removeRecord(record.sessionId);
|
|
8486
9025
|
deleted++;
|
|
8487
9026
|
} catch (err) {
|
|
8488
|
-
|
|
9027
|
+
log27.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
|
|
8489
9028
|
failed++;
|
|
8490
9029
|
}
|
|
8491
9030
|
}
|
|
@@ -8556,7 +9095,7 @@ async function handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicId
|
|
|
8556
9095
|
try {
|
|
8557
9096
|
await core.sessionManager.cancelSession(record.sessionId);
|
|
8558
9097
|
} catch (err) {
|
|
8559
|
-
|
|
9098
|
+
log27.warn({ err, sessionId: record.sessionId }, "Failed to cancel session during cleanup");
|
|
8560
9099
|
}
|
|
8561
9100
|
}
|
|
8562
9101
|
const topicId = record.platform?.topicId;
|
|
@@ -8564,13 +9103,13 @@ async function handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicId
|
|
|
8564
9103
|
try {
|
|
8565
9104
|
await ctx.api.deleteForumTopic(chatId, topicId);
|
|
8566
9105
|
} catch (err) {
|
|
8567
|
-
|
|
9106
|
+
log27.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
|
|
8568
9107
|
}
|
|
8569
9108
|
}
|
|
8570
9109
|
await core.sessionManager.removeRecord(record.sessionId);
|
|
8571
9110
|
deleted++;
|
|
8572
9111
|
} catch (err) {
|
|
8573
|
-
|
|
9112
|
+
log27.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
|
|
8574
9113
|
failed++;
|
|
8575
9114
|
}
|
|
8576
9115
|
}
|
|
@@ -8638,13 +9177,13 @@ async function handleArchiveConfirm(ctx, core, chatId) {
|
|
|
8638
9177
|
}
|
|
8639
9178
|
}
|
|
8640
9179
|
}
|
|
8641
|
-
var
|
|
9180
|
+
var log27;
|
|
8642
9181
|
var init_session = __esm({
|
|
8643
9182
|
"src/plugins/telegram/commands/session.ts"() {
|
|
8644
9183
|
"use strict";
|
|
8645
9184
|
init_formatting();
|
|
8646
9185
|
init_log();
|
|
8647
|
-
|
|
9186
|
+
log27 = createChildLogger({ module: "telegram-cmd-session" });
|
|
8648
9187
|
}
|
|
8649
9188
|
});
|
|
8650
9189
|
|
|
@@ -9471,7 +10010,7 @@ var init_model = __esm({
|
|
|
9471
10010
|
// src/plugins/telegram/commands/resume.ts
|
|
9472
10011
|
function setupResumeCallbacks(_bot, _core, _chatId, _onControlMessage) {
|
|
9473
10012
|
}
|
|
9474
|
-
var
|
|
10013
|
+
var log28;
|
|
9475
10014
|
var init_resume = __esm({
|
|
9476
10015
|
"src/plugins/telegram/commands/resume.ts"() {
|
|
9477
10016
|
"use strict";
|
|
@@ -9481,7 +10020,7 @@ var init_resume = __esm({
|
|
|
9481
10020
|
init_topics();
|
|
9482
10021
|
init_admin();
|
|
9483
10022
|
init_log();
|
|
9484
|
-
|
|
10023
|
+
log28 = createChildLogger({ module: "telegram-cmd-resume" });
|
|
9485
10024
|
}
|
|
9486
10025
|
});
|
|
9487
10026
|
|
|
@@ -9644,7 +10183,7 @@ function setupSettingsCallbacks(bot, core, getAssistantSession) {
|
|
|
9644
10183
|
} catch {
|
|
9645
10184
|
}
|
|
9646
10185
|
} catch (err) {
|
|
9647
|
-
|
|
10186
|
+
log29.error({ err, fieldPath }, "Failed to toggle config");
|
|
9648
10187
|
try {
|
|
9649
10188
|
await ctx.answerCallbackQuery({ text: "\u274C Failed to update" });
|
|
9650
10189
|
} catch {
|
|
@@ -9725,7 +10264,7 @@ Tap to change:`, {
|
|
|
9725
10264
|
} catch {
|
|
9726
10265
|
}
|
|
9727
10266
|
} catch (err) {
|
|
9728
|
-
|
|
10267
|
+
log29.error({ err, fieldPath }, "Failed to set config");
|
|
9729
10268
|
try {
|
|
9730
10269
|
await ctx.answerCallbackQuery({ text: "\u274C Failed to update" });
|
|
9731
10270
|
} catch {
|
|
@@ -9783,13 +10322,13 @@ Tap to change:`, {
|
|
|
9783
10322
|
}
|
|
9784
10323
|
});
|
|
9785
10324
|
}
|
|
9786
|
-
var
|
|
10325
|
+
var log29;
|
|
9787
10326
|
var init_settings = __esm({
|
|
9788
10327
|
"src/plugins/telegram/commands/settings.ts"() {
|
|
9789
10328
|
"use strict";
|
|
9790
10329
|
init_config_registry();
|
|
9791
10330
|
init_log();
|
|
9792
|
-
|
|
10331
|
+
log29 = createChildLogger({ module: "telegram-settings" });
|
|
9793
10332
|
}
|
|
9794
10333
|
});
|
|
9795
10334
|
|
|
@@ -9836,7 +10375,7 @@ async function handleDoctor(ctx, core) {
|
|
|
9836
10375
|
reply_markup: keyboard
|
|
9837
10376
|
});
|
|
9838
10377
|
} catch (err) {
|
|
9839
|
-
|
|
10378
|
+
log30.error({ err }, "Doctor command failed");
|
|
9840
10379
|
await ctx.api.editMessageText(
|
|
9841
10380
|
ctx.chat.id,
|
|
9842
10381
|
statusMsg.message_id,
|
|
@@ -9885,7 +10424,7 @@ function setupDoctorCallbacks(bot, core) {
|
|
|
9885
10424
|
}
|
|
9886
10425
|
}
|
|
9887
10426
|
} catch (err) {
|
|
9888
|
-
|
|
10427
|
+
log30.error({ err, index }, "Doctor fix callback failed");
|
|
9889
10428
|
}
|
|
9890
10429
|
});
|
|
9891
10430
|
bot.callbackQuery("m:doctor", async (ctx) => {
|
|
@@ -9896,13 +10435,13 @@ function setupDoctorCallbacks(bot, core) {
|
|
|
9896
10435
|
await handleDoctor(ctx, core);
|
|
9897
10436
|
});
|
|
9898
10437
|
}
|
|
9899
|
-
var
|
|
10438
|
+
var log30, pendingFixesStore;
|
|
9900
10439
|
var init_doctor2 = __esm({
|
|
9901
10440
|
"src/plugins/telegram/commands/doctor.ts"() {
|
|
9902
10441
|
"use strict";
|
|
9903
10442
|
init_doctor();
|
|
9904
10443
|
init_log();
|
|
9905
|
-
|
|
10444
|
+
log30 = createChildLogger({ module: "telegram-cmd-doctor" });
|
|
9906
10445
|
pendingFixesStore = /* @__PURE__ */ new Map();
|
|
9907
10446
|
}
|
|
9908
10447
|
});
|
|
@@ -9961,13 +10500,13 @@ function setupTunnelCallbacks(bot, core) {
|
|
|
9961
10500
|
}
|
|
9962
10501
|
});
|
|
9963
10502
|
}
|
|
9964
|
-
var
|
|
10503
|
+
var log31;
|
|
9965
10504
|
var init_tunnel2 = __esm({
|
|
9966
10505
|
"src/plugins/telegram/commands/tunnel.ts"() {
|
|
9967
10506
|
"use strict";
|
|
9968
10507
|
init_formatting();
|
|
9969
10508
|
init_log();
|
|
9970
|
-
|
|
10509
|
+
log31 = createChildLogger({ module: "telegram-cmd-tunnel" });
|
|
9971
10510
|
}
|
|
9972
10511
|
});
|
|
9973
10512
|
|
|
@@ -9981,10 +10520,10 @@ async function executeSwitchAgent(ctx, core, sessionId, agentName) {
|
|
|
9981
10520
|
`Switched to <b>${escapeHtml(agentName)}</b> (${status})`,
|
|
9982
10521
|
{ parse_mode: "HTML" }
|
|
9983
10522
|
);
|
|
9984
|
-
|
|
10523
|
+
log32.info({ sessionId, agentName, resumed }, "Agent switched via /switch");
|
|
9985
10524
|
} catch (err) {
|
|
9986
10525
|
await ctx.reply(`Failed to switch agent: ${escapeHtml(String(err.message || err))}`);
|
|
9987
|
-
|
|
10526
|
+
log32.warn({ sessionId, agentName, err: err.message }, "Agent switch failed");
|
|
9988
10527
|
}
|
|
9989
10528
|
}
|
|
9990
10529
|
function setupSwitchCallbacks(bot, core) {
|
|
@@ -10029,13 +10568,13 @@ Switch to <b>${escapeHtml(agentName)}</b> anyway?`,
|
|
|
10029
10568
|
await executeSwitchAgent(ctx, core, session.id, data);
|
|
10030
10569
|
});
|
|
10031
10570
|
}
|
|
10032
|
-
var
|
|
10571
|
+
var log32;
|
|
10033
10572
|
var init_switch = __esm({
|
|
10034
10573
|
"src/plugins/telegram/commands/switch.ts"() {
|
|
10035
10574
|
"use strict";
|
|
10036
10575
|
init_formatting();
|
|
10037
10576
|
init_log();
|
|
10038
|
-
|
|
10577
|
+
log32 = createChildLogger({ module: "telegram-cmd-switch" });
|
|
10039
10578
|
}
|
|
10040
10579
|
});
|
|
10041
10580
|
|
|
@@ -10258,7 +10797,7 @@ function setupAllCallbacks(bot, core, chatId, systemTopicIds, getAssistantSessio
|
|
|
10258
10797
|
if (!menuRegistry) return;
|
|
10259
10798
|
const item = menuRegistry.getItem(itemId);
|
|
10260
10799
|
if (!item) {
|
|
10261
|
-
|
|
10800
|
+
log33.warn({ itemId }, "Menu item not found in registry");
|
|
10262
10801
|
return;
|
|
10263
10802
|
}
|
|
10264
10803
|
const topicId = ctx.callbackQuery.message?.message_thread_id;
|
|
@@ -10344,7 +10883,7 @@ ${lines}`, { parse_mode: "HTML" }).catch(() => {
|
|
|
10344
10883
|
}
|
|
10345
10884
|
});
|
|
10346
10885
|
}
|
|
10347
|
-
var
|
|
10886
|
+
var log33, STATIC_COMMANDS;
|
|
10348
10887
|
var init_commands = __esm({
|
|
10349
10888
|
"src/plugins/telegram/commands/index.ts"() {
|
|
10350
10889
|
"use strict";
|
|
@@ -10370,7 +10909,7 @@ var init_commands = __esm({
|
|
|
10370
10909
|
init_settings();
|
|
10371
10910
|
init_doctor2();
|
|
10372
10911
|
init_resume();
|
|
10373
|
-
|
|
10912
|
+
log33 = createChildLogger({ module: "telegram-menu-callbacks" });
|
|
10374
10913
|
STATIC_COMMANDS = [
|
|
10375
10914
|
{ command: "new", description: "Create new session" },
|
|
10376
10915
|
{ command: "newchat", description: "New chat, same agent & workspace" },
|
|
@@ -10386,6 +10925,7 @@ var init_commands = __esm({
|
|
|
10386
10925
|
{ command: "restart", description: "Restart OpenACP" },
|
|
10387
10926
|
{ command: "update", description: "Update to latest version and restart" },
|
|
10388
10927
|
{ command: "doctor", description: "Run system diagnostics" },
|
|
10928
|
+
{ command: "proxy", description: "Manage scoped proxy routing" },
|
|
10389
10929
|
{ command: "retry", description: "Re-check setup prerequisites (use if bot is stuck on startup)" },
|
|
10390
10930
|
{ command: "tunnel", description: "Create/stop tunnel for a local port" },
|
|
10391
10931
|
{ command: "tunnels", description: "List active tunnels" },
|
|
@@ -10406,14 +10946,14 @@ var init_commands = __esm({
|
|
|
10406
10946
|
// src/plugins/telegram/permissions.ts
|
|
10407
10947
|
import { InlineKeyboard as InlineKeyboard12 } from "grammy";
|
|
10408
10948
|
import { nanoid as nanoid4 } from "nanoid";
|
|
10409
|
-
var
|
|
10949
|
+
var log34, PermissionHandler;
|
|
10410
10950
|
var init_permissions = __esm({
|
|
10411
10951
|
"src/plugins/telegram/permissions.ts"() {
|
|
10412
10952
|
"use strict";
|
|
10413
10953
|
init_formatting();
|
|
10414
10954
|
init_topics();
|
|
10415
10955
|
init_log();
|
|
10416
|
-
|
|
10956
|
+
log34 = createChildLogger({ module: "telegram-permissions" });
|
|
10417
10957
|
PermissionHandler = class {
|
|
10418
10958
|
constructor(bot, chatId, getSession, sendNotification) {
|
|
10419
10959
|
this.bot = bot;
|
|
@@ -10491,7 +11031,7 @@ ${escapeHtml(request.description)}`,
|
|
|
10491
11031
|
}
|
|
10492
11032
|
const session = this.getSession(pending.sessionId);
|
|
10493
11033
|
const isAllow = pending.options.find((o) => o.id === optionId)?.isAllow ?? false;
|
|
10494
|
-
|
|
11034
|
+
log34.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
|
|
10495
11035
|
if (session?.permissionGate.requestId === pending.requestId) {
|
|
10496
11036
|
session.permissionGate.resolve(optionId);
|
|
10497
11037
|
}
|
|
@@ -10511,7 +11051,7 @@ ${escapeHtml(request.description)}`,
|
|
|
10511
11051
|
});
|
|
10512
11052
|
|
|
10513
11053
|
// src/plugins/telegram/activity.ts
|
|
10514
|
-
var
|
|
11054
|
+
var log35, THINKING_REFRESH_MS, THINKING_MAX_MS, ThinkingIndicator, ToolCard, ActivityTracker2;
|
|
10515
11055
|
var init_activity = __esm({
|
|
10516
11056
|
"src/plugins/telegram/activity.ts"() {
|
|
10517
11057
|
"use strict";
|
|
@@ -10521,7 +11061,7 @@ var init_activity = __esm({
|
|
|
10521
11061
|
init_stream_accumulator();
|
|
10522
11062
|
init_stream_accumulator();
|
|
10523
11063
|
init_display_spec_builder();
|
|
10524
|
-
|
|
11064
|
+
log35 = createChildLogger({ module: "telegram:activity" });
|
|
10525
11065
|
THINKING_REFRESH_MS = 15e3;
|
|
10526
11066
|
THINKING_MAX_MS = 3 * 60 * 1e3;
|
|
10527
11067
|
ThinkingIndicator = class {
|
|
@@ -10567,7 +11107,7 @@ var init_activity = __esm({
|
|
|
10567
11107
|
}
|
|
10568
11108
|
}
|
|
10569
11109
|
} catch (err) {
|
|
10570
|
-
|
|
11110
|
+
log35.warn({ err }, "ThinkingIndicator.show() failed");
|
|
10571
11111
|
} finally {
|
|
10572
11112
|
this.sending = false;
|
|
10573
11113
|
}
|
|
@@ -10744,7 +11284,7 @@ var init_activity = __esm({
|
|
|
10744
11284
|
this.tracer?.log("telegram", { action: "telegram:delete:overflow", sessionId: this.sessionId, msgId: staleId });
|
|
10745
11285
|
}
|
|
10746
11286
|
} catch (err) {
|
|
10747
|
-
|
|
11287
|
+
log35.warn({ err }, "[ToolCard] send/edit failed");
|
|
10748
11288
|
}
|
|
10749
11289
|
}
|
|
10750
11290
|
};
|
|
@@ -10852,7 +11392,7 @@ var init_activity = __esm({
|
|
|
10852
11392
|
const entry = this.toolStateMap.merge(id, status, rawInput, content, viewerLinks, diffStats);
|
|
10853
11393
|
if (!existed || !entry) return;
|
|
10854
11394
|
if (viewerLinks || entry.viewerLinks) {
|
|
10855
|
-
|
|
11395
|
+
log35.debug({ toolId: id, status, hasIncomingLinks: !!viewerLinks, hasEntryLinks: !!entry.viewerLinks, entryLinks: entry.viewerLinks }, "toolUpdate: viewer links trace");
|
|
10856
11396
|
}
|
|
10857
11397
|
const spec = this.specBuilder.buildToolSpec(entry, this._outputMode, this.sessionContext);
|
|
10858
11398
|
this.toolCard.updateFromSpec(spec);
|
|
@@ -11210,13 +11750,13 @@ var init_draft_manager = __esm({
|
|
|
11210
11750
|
});
|
|
11211
11751
|
|
|
11212
11752
|
// src/plugins/telegram/skill-command-manager.ts
|
|
11213
|
-
var
|
|
11753
|
+
var log36, SkillCommandManager;
|
|
11214
11754
|
var init_skill_command_manager = __esm({
|
|
11215
11755
|
"src/plugins/telegram/skill-command-manager.ts"() {
|
|
11216
11756
|
"use strict";
|
|
11217
11757
|
init_commands();
|
|
11218
11758
|
init_log();
|
|
11219
|
-
|
|
11759
|
+
log36 = createChildLogger({ module: "skill-commands" });
|
|
11220
11760
|
SkillCommandManager = class {
|
|
11221
11761
|
constructor(bot, chatId, sendQueue, sessionManager) {
|
|
11222
11762
|
this.bot = bot;
|
|
@@ -11291,7 +11831,7 @@ var init_skill_command_manager = __esm({
|
|
|
11291
11831
|
disable_notification: true
|
|
11292
11832
|
});
|
|
11293
11833
|
} catch (err) {
|
|
11294
|
-
|
|
11834
|
+
log36.error({ err, sessionId }, "Failed to send skill commands");
|
|
11295
11835
|
}
|
|
11296
11836
|
}
|
|
11297
11837
|
async cleanup(sessionId) {
|
|
@@ -11397,6 +11937,185 @@ var init_renderer2 = __esm({
|
|
|
11397
11937
|
}
|
|
11398
11938
|
});
|
|
11399
11939
|
|
|
11940
|
+
// src/plugins/identity/types.ts
|
|
11941
|
+
var init_types = __esm({
|
|
11942
|
+
"src/plugins/identity/types.ts"() {
|
|
11943
|
+
"use strict";
|
|
11944
|
+
}
|
|
11945
|
+
});
|
|
11946
|
+
|
|
11947
|
+
// src/core/commands/proxy.ts
|
|
11948
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
11949
|
+
function clearProxyDraftsForChannel(channelId) {
|
|
11950
|
+
const prefix = `${channelId}:`;
|
|
11951
|
+
for (const [id, draft] of drafts) if (draft.owner.startsWith(prefix)) drafts.delete(id);
|
|
11952
|
+
}
|
|
11953
|
+
var DRAFT_TTL_MS, drafts;
|
|
11954
|
+
var init_proxy2 = __esm({
|
|
11955
|
+
"src/core/commands/proxy.ts"() {
|
|
11956
|
+
"use strict";
|
|
11957
|
+
init_proxy_types();
|
|
11958
|
+
init_types();
|
|
11959
|
+
init_proxy_store();
|
|
11960
|
+
init_proxy_service();
|
|
11961
|
+
DRAFT_TTL_MS = 10 * 6e4;
|
|
11962
|
+
drafts = /* @__PURE__ */ new Map();
|
|
11963
|
+
}
|
|
11964
|
+
});
|
|
11965
|
+
|
|
11966
|
+
// src/plugins/telegram/command-sync.ts
|
|
11967
|
+
import { createHash as createHash4 } from "crypto";
|
|
11968
|
+
function validDescription(description) {
|
|
11969
|
+
if (typeof description !== "string") return false;
|
|
11970
|
+
const trimmed = description.trim();
|
|
11971
|
+
return trimmed.length > 0 && trimmed.length <= TELEGRAM_DESCRIPTION_LIMIT;
|
|
11972
|
+
}
|
|
11973
|
+
function prepareTelegramCommandBoundary(coreCommands, registryCommands) {
|
|
11974
|
+
if (coreCommands.length > TELEGRAM_COMMAND_LIMIT) {
|
|
11975
|
+
throw new TelegramCommandBoundaryError("OpenACP core defines more than 100 Telegram commands");
|
|
11976
|
+
}
|
|
11977
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11978
|
+
const commands = [];
|
|
11979
|
+
for (const command of coreCommands) {
|
|
11980
|
+
if (!TELEGRAM_COMMAND_NAME.test(command.command) || !validDescription(command.description)) {
|
|
11981
|
+
throw new TelegramCommandBoundaryError("OpenACP core contains an invalid Telegram command definition");
|
|
11982
|
+
}
|
|
11983
|
+
if (seen.has(command.command)) throw new TelegramCommandBoundaryError("OpenACP core contains a duplicate Telegram command definition");
|
|
11984
|
+
seen.add(command.command);
|
|
11985
|
+
commands.push({ command: command.command, description: command.description.trim() });
|
|
11986
|
+
}
|
|
11987
|
+
const skipped = { invalidName: 0, invalidDescription: 0, duplicate: 0, overflow: 0 };
|
|
11988
|
+
const validPlugins = [];
|
|
11989
|
+
for (const command of registryCommands) {
|
|
11990
|
+
if (command.category !== "plugin") continue;
|
|
11991
|
+
if (!TELEGRAM_COMMAND_NAME.test(command.name)) {
|
|
11992
|
+
skipped.invalidName++;
|
|
11993
|
+
continue;
|
|
11994
|
+
}
|
|
11995
|
+
if (!validDescription(command.description)) {
|
|
11996
|
+
skipped.invalidDescription++;
|
|
11997
|
+
continue;
|
|
11998
|
+
}
|
|
11999
|
+
if (seen.has(command.name) || validPlugins.some((entry) => entry.command === command.name)) {
|
|
12000
|
+
skipped.duplicate++;
|
|
12001
|
+
continue;
|
|
12002
|
+
}
|
|
12003
|
+
validPlugins.push({ command: command.name, description: command.description.trim() });
|
|
12004
|
+
}
|
|
12005
|
+
validPlugins.sort((a, b) => a.command.localeCompare(b.command) || a.description.localeCompare(b.description));
|
|
12006
|
+
const capacity = TELEGRAM_COMMAND_LIMIT - commands.length;
|
|
12007
|
+
skipped.overflow = Math.max(0, validPlugins.length - capacity);
|
|
12008
|
+
commands.push(...validPlugins.slice(0, capacity));
|
|
12009
|
+
return { commands, skipped };
|
|
12010
|
+
}
|
|
12011
|
+
function assertTelegramCommandBoundary(commands) {
|
|
12012
|
+
if (commands.length > TELEGRAM_COMMAND_LIMIT) throw new TelegramCommandBoundaryError("Telegram command list exceeds 100 entries");
|
|
12013
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12014
|
+
for (const command of commands) {
|
|
12015
|
+
if (!TELEGRAM_COMMAND_NAME.test(command.command) || !validDescription(command.description) || seen.has(command.command)) {
|
|
12016
|
+
throw new TelegramCommandBoundaryError("Telegram command list failed validation before synchronization");
|
|
12017
|
+
}
|
|
12018
|
+
seen.add(command.command);
|
|
12019
|
+
}
|
|
12020
|
+
}
|
|
12021
|
+
function commandsEqual(current, desired) {
|
|
12022
|
+
return current.length === desired.length && current.every((command, index) => {
|
|
12023
|
+
const expected = desired[index];
|
|
12024
|
+
return command.command === expected?.command && command.description === expected.description;
|
|
12025
|
+
});
|
|
12026
|
+
}
|
|
12027
|
+
function throwIfAborted(signal) {
|
|
12028
|
+
if (signal?.aborted) throw signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
12029
|
+
}
|
|
12030
|
+
function localeLabel(locale) {
|
|
12031
|
+
return locale || "neutral";
|
|
12032
|
+
}
|
|
12033
|
+
async function synchronizeTelegramCommands(api, chatId, desired, options) {
|
|
12034
|
+
if (!/^\d{1,20}$/.test(options.botId)) throw new Error("Telegram bot identity is invalid");
|
|
12035
|
+
assertTelegramCommandBoundary(desired);
|
|
12036
|
+
const chatKey = createHash4("sha256").update(String(chatId)).digest("hex").slice(0, 16);
|
|
12037
|
+
const scopes = [
|
|
12038
|
+
{ name: "default", scope: { type: "default" }, ledgerName: "default" },
|
|
12039
|
+
{ name: "chat", scope: { type: "chat", chat_id: chatId }, ledgerName: `chat:${chatKey}` },
|
|
12040
|
+
{
|
|
12041
|
+
name: "chat_administrators",
|
|
12042
|
+
scope: { type: "chat_administrators", chat_id: chatId },
|
|
12043
|
+
ledgerName: `chat_administrators:${chatKey}`,
|
|
12044
|
+
onlyWhenPresent: true
|
|
12045
|
+
}
|
|
12046
|
+
];
|
|
12047
|
+
const desiredNames = new Set(desired.map((command) => command.command));
|
|
12048
|
+
const result = { updated: [], unchanged: [] };
|
|
12049
|
+
const initialConservative = await options.ownershipStore.withLock(async ({ ledger, conservative }) => {
|
|
12050
|
+
options.ownershipStore.claimOwner(ledger, options.botId, options.ownerIdentity, options.allowOwnerTakeover);
|
|
12051
|
+
options.ownershipStore.save(ledger);
|
|
12052
|
+
return conservative;
|
|
12053
|
+
});
|
|
12054
|
+
options.onOwnerClaimed?.();
|
|
12055
|
+
for (const locale of TELEGRAM_COMMAND_LOCALES) {
|
|
12056
|
+
for (const entry of scopes) {
|
|
12057
|
+
throwIfAborted(options.signal);
|
|
12058
|
+
const label = `${entry.name}:${localeLabel(locale)}`;
|
|
12059
|
+
const scopeKey = `${entry.ledgerName}|${localeLabel(locale)}`;
|
|
12060
|
+
const apiOptions = {
|
|
12061
|
+
scope: entry.scope,
|
|
12062
|
+
...locale ? { language_code: locale } : {}
|
|
12063
|
+
};
|
|
12064
|
+
const current = await api.getMyCommands(apiOptions, options.signal);
|
|
12065
|
+
throwIfAborted(options.signal);
|
|
12066
|
+
const ownership = await options.ownershipStore.withLock(async ({ ledger, conservative }) => ({
|
|
12067
|
+
priorOwned: options.ownershipStore.getOwned(ledger, options.botId, scopeKey),
|
|
12068
|
+
conservative
|
|
12069
|
+
}));
|
|
12070
|
+
if (entry.onlyWhenPresent && current.length === 0) {
|
|
12071
|
+
await options.ownershipStore.withLock(async ({ ledger }) => {
|
|
12072
|
+
options.ownershipStore.claimOwner(ledger, options.botId, options.ownerIdentity);
|
|
12073
|
+
options.ownershipStore.setOwned(ledger, options.botId, scopeKey, []);
|
|
12074
|
+
options.ownershipStore.save(ledger);
|
|
12075
|
+
});
|
|
12076
|
+
result.unchanged.push(label);
|
|
12077
|
+
continue;
|
|
12078
|
+
}
|
|
12079
|
+
const owned = new Set(ownership.priorOwned ?? (initialConservative || ownership.conservative ? [] : options.historicalOwnedNames));
|
|
12080
|
+
const unmanaged = current.filter((command) => !desiredNames.has(command.command) && !owned.has(command.command));
|
|
12081
|
+
const merged = [...desired, ...unmanaged];
|
|
12082
|
+
if (merged.length > 100) {
|
|
12083
|
+
throw new Error(`Telegram command scope ${label} has no capacity for OpenACP commands without deleting unmanaged commands`);
|
|
12084
|
+
}
|
|
12085
|
+
if (!commandsEqual(current, merged)) {
|
|
12086
|
+
throwIfAborted(options.signal);
|
|
12087
|
+
await api.setMyCommands(merged, apiOptions, options.signal);
|
|
12088
|
+
result.updated.push(label);
|
|
12089
|
+
} else {
|
|
12090
|
+
result.unchanged.push(label);
|
|
12091
|
+
}
|
|
12092
|
+
throwIfAborted(options.signal);
|
|
12093
|
+
await options.ownershipStore.withLock(async ({ ledger }) => {
|
|
12094
|
+
options.ownershipStore.claimOwner(ledger, options.botId, options.ownerIdentity);
|
|
12095
|
+
options.ownershipStore.setOwned(ledger, options.botId, scopeKey, [...desiredNames]);
|
|
12096
|
+
options.ownershipStore.save(ledger);
|
|
12097
|
+
});
|
|
12098
|
+
}
|
|
12099
|
+
}
|
|
12100
|
+
return result;
|
|
12101
|
+
}
|
|
12102
|
+
var TelegramCommandBoundaryError, TELEGRAM_COMMAND_NAME, TELEGRAM_COMMAND_LIMIT, TELEGRAM_DESCRIPTION_LIMIT;
|
|
12103
|
+
var init_command_sync = __esm({
|
|
12104
|
+
"src/plugins/telegram/command-sync.ts"() {
|
|
12105
|
+
"use strict";
|
|
12106
|
+
init_telegram_command_scopes();
|
|
12107
|
+
TelegramCommandBoundaryError = class extends Error {
|
|
12108
|
+
constructor(message) {
|
|
12109
|
+
super(message);
|
|
12110
|
+
this.name = "TelegramCommandBoundaryError";
|
|
12111
|
+
}
|
|
12112
|
+
};
|
|
12113
|
+
TELEGRAM_COMMAND_NAME = /^[a-z0-9_]{1,32}$/;
|
|
12114
|
+
TELEGRAM_COMMAND_LIMIT = 100;
|
|
12115
|
+
TELEGRAM_DESCRIPTION_LIMIT = 256;
|
|
12116
|
+
}
|
|
12117
|
+
});
|
|
12118
|
+
|
|
11400
12119
|
// src/plugins/telegram/validators.ts
|
|
11401
12120
|
var validators_exports = {};
|
|
11402
12121
|
__export(validators_exports, {
|
|
@@ -11471,7 +12190,7 @@ async function validateBotAdmin(token, chatId, telegramFetch) {
|
|
|
11471
12190
|
};
|
|
11472
12191
|
}
|
|
11473
12192
|
const { status } = data.result;
|
|
11474
|
-
|
|
12193
|
+
log37.info(
|
|
11475
12194
|
{ status, can_manage_topics: data.result.can_manage_topics, raw_result: data.result },
|
|
11476
12195
|
"validateBotAdmin: getChatMember raw result"
|
|
11477
12196
|
);
|
|
@@ -11491,7 +12210,7 @@ async function validateBotAdmin(token, chatId, telegramFetch) {
|
|
|
11491
12210
|
}
|
|
11492
12211
|
async function checkTopicsPrerequisites(token, chatId, telegramFetch) {
|
|
11493
12212
|
const issues = [];
|
|
11494
|
-
|
|
12213
|
+
log37.info({ chatId }, "checkTopicsPrerequisites: starting checks");
|
|
11495
12214
|
try {
|
|
11496
12215
|
const res = await telegramFetch(`https://api.telegram.org/bot${token}/getChat`, {
|
|
11497
12216
|
method: "POST",
|
|
@@ -11499,7 +12218,7 @@ async function checkTopicsPrerequisites(token, chatId, telegramFetch) {
|
|
|
11499
12218
|
body: JSON.stringify({ chat_id: chatId })
|
|
11500
12219
|
});
|
|
11501
12220
|
const data = await res.json();
|
|
11502
|
-
|
|
12221
|
+
log37.info(
|
|
11503
12222
|
{ chatId, apiOk: data.ok, is_forum: data.result?.is_forum, type: data.result?.type, title: data.result?.title },
|
|
11504
12223
|
"checkTopicsPrerequisites: getChat result"
|
|
11505
12224
|
);
|
|
@@ -11509,11 +12228,11 @@ async function checkTopicsPrerequisites(token, chatId, telegramFetch) {
|
|
|
11509
12228
|
);
|
|
11510
12229
|
}
|
|
11511
12230
|
} catch (err) {
|
|
11512
|
-
|
|
12231
|
+
log37.warn({ err, chatId }, "checkTopicsPrerequisites: getChat failed (network error)");
|
|
11513
12232
|
issues.push("\u274C Could not check if Topics are enabled (network error).");
|
|
11514
12233
|
}
|
|
11515
12234
|
const adminResult = await validateBotAdmin(token, chatId, telegramFetch);
|
|
11516
|
-
|
|
12235
|
+
log37.info(
|
|
11517
12236
|
{ chatId, adminOk: adminResult.ok, canManageTopics: adminResult.ok ? adminResult.canManageTopics : void 0, error: !adminResult.ok ? adminResult.error : void 0 },
|
|
11518
12237
|
"checkTopicsPrerequisites: validateBotAdmin result"
|
|
11519
12238
|
);
|
|
@@ -11528,17 +12247,17 @@ async function checkTopicsPrerequisites(token, chatId, telegramFetch) {
|
|
|
11528
12247
|
'\u274C Bot cannot manage topics.\n\u2192 Go to Group Settings \u2192 Administrators \u2192 tap the bot \u2192 enable "Manage Topics" \u2192 tap Save/Done (the checkmark)'
|
|
11529
12248
|
);
|
|
11530
12249
|
}
|
|
11531
|
-
|
|
12250
|
+
log37.info({ chatId, issueCount: issues.length, ok: issues.length === 0 }, "checkTopicsPrerequisites: result");
|
|
11532
12251
|
if (issues.length > 0) return { ok: false, issues };
|
|
11533
12252
|
return { ok: true };
|
|
11534
12253
|
}
|
|
11535
|
-
var
|
|
12254
|
+
var log37;
|
|
11536
12255
|
var init_validators = __esm({
|
|
11537
12256
|
"src/plugins/telegram/validators.ts"() {
|
|
11538
12257
|
"use strict";
|
|
11539
12258
|
init_log();
|
|
11540
12259
|
init_network_redaction();
|
|
11541
|
-
|
|
12260
|
+
log37 = createChildLogger({ module: "telegram-validators" });
|
|
11542
12261
|
}
|
|
11543
12262
|
});
|
|
11544
12263
|
|
|
@@ -11557,7 +12276,7 @@ function patchedFetch(input2, init, delegate) {
|
|
|
11557
12276
|
}
|
|
11558
12277
|
return delegate(input2, init);
|
|
11559
12278
|
}
|
|
11560
|
-
var
|
|
12279
|
+
var log38, HISTORICAL_OPENACP_COMMAND_NAMES, TelegramAdapter;
|
|
11561
12280
|
var init_adapter = __esm({
|
|
11562
12281
|
"src/plugins/telegram/adapter.ts"() {
|
|
11563
12282
|
"use strict";
|
|
@@ -11577,7 +12296,19 @@ var init_adapter = __esm({
|
|
|
11577
12296
|
init_messaging_adapter();
|
|
11578
12297
|
init_renderer2();
|
|
11579
12298
|
init_output_mode_resolver();
|
|
11580
|
-
|
|
12299
|
+
init_proxy2();
|
|
12300
|
+
init_network_redaction();
|
|
12301
|
+
init_command_sync();
|
|
12302
|
+
init_command_ownership_store();
|
|
12303
|
+
init_instance_context();
|
|
12304
|
+
log38 = createChildLogger({ module: "telegram" });
|
|
12305
|
+
HISTORICAL_OPENACP_COMMAND_NAMES = /* @__PURE__ */ new Set([
|
|
12306
|
+
...STATIC_COMMANDS.map((command) => command.command),
|
|
12307
|
+
"clear",
|
|
12308
|
+
"bypass",
|
|
12309
|
+
"usage",
|
|
12310
|
+
"summary"
|
|
12311
|
+
]);
|
|
11581
12312
|
TelegramAdapter = class extends MessagingAdapter {
|
|
11582
12313
|
name = "telegram";
|
|
11583
12314
|
renderer = new TelegramRenderer();
|
|
@@ -11606,6 +12337,7 @@ var init_adapter = __esm({
|
|
|
11606
12337
|
sessionTrackers = /* @__PURE__ */ new Map();
|
|
11607
12338
|
callbackCache = /* @__PURE__ */ new Map();
|
|
11608
12339
|
callbackCounter = 0;
|
|
12340
|
+
pendingCommandInputs = /* @__PURE__ */ new Map();
|
|
11609
12341
|
/** Pending skill commands queued when session.threadId was not yet set */
|
|
11610
12342
|
_pendingSkillCommands = /* @__PURE__ */ new Map();
|
|
11611
12343
|
/** Control message IDs per session (for updating status text/buttons) */
|
|
@@ -11630,6 +12362,23 @@ var init_adapter = __esm({
|
|
|
11630
12362
|
/** Set during normal shutdown so bot.stop() does not trigger a self-restart. */
|
|
11631
12363
|
_stopping = false;
|
|
11632
12364
|
unregisterProxyTester;
|
|
12365
|
+
/** Serializes command-list reconciliations triggered by startup and plugin hot reloads. */
|
|
12366
|
+
_commandSyncChain = Promise.resolve();
|
|
12367
|
+
_pendingCommandSnapshot;
|
|
12368
|
+
_commandSyncWorkerRunning = false;
|
|
12369
|
+
_commandSyncGeneration = 0;
|
|
12370
|
+
_commandSyncAbort;
|
|
12371
|
+
_commandSnapshotVersion = 0;
|
|
12372
|
+
_commandsReadyHandler;
|
|
12373
|
+
_latestCommandSnapshot;
|
|
12374
|
+
_commandApiReady = false;
|
|
12375
|
+
_initialCommandSyncScheduled = false;
|
|
12376
|
+
commandOwnershipStore = new TelegramCommandOwnershipStore(getGlobalRoot());
|
|
12377
|
+
commandOwnerIdentity;
|
|
12378
|
+
commandOwnerBotId;
|
|
12379
|
+
commandOwnerTakeoverRequested;
|
|
12380
|
+
_commandOwnerClaimed = false;
|
|
12381
|
+
_commandOwnerHeartbeat;
|
|
11633
12382
|
telegramFetch() {
|
|
11634
12383
|
return this.core.proxyService.createFetch("channels.telegram");
|
|
11635
12384
|
}
|
|
@@ -11704,6 +12453,14 @@ var init_adapter = __esm({
|
|
|
11704
12453
|
});
|
|
11705
12454
|
this.core = core;
|
|
11706
12455
|
this.telegramConfig = config;
|
|
12456
|
+
this.commandOwnerBotId = this.telegramConfig.botToken.split(":", 1)[0] ?? "";
|
|
12457
|
+
this.commandOwnerIdentity = {
|
|
12458
|
+
instanceId: this.core.instanceContext.id,
|
|
12459
|
+
instanceKey: telegramCommandInstanceKey(this.core.instanceContext.root),
|
|
12460
|
+
hostId: telegramCommandHostId(),
|
|
12461
|
+
pid: process.pid
|
|
12462
|
+
};
|
|
12463
|
+
this.commandOwnerTakeoverRequested = process.env.OPENACP_TELEGRAM_COMMAND_TAKEOVER === "1";
|
|
11707
12464
|
this.saveTopicIds = saveTopicIds;
|
|
11708
12465
|
this.unregisterProxyTester = this.core.proxyService.registerRouteTester(
|
|
11709
12466
|
"channels.telegram",
|
|
@@ -11716,6 +12473,35 @@ var init_adapter = __esm({
|
|
|
11716
12473
|
}
|
|
11717
12474
|
);
|
|
11718
12475
|
}
|
|
12476
|
+
/**
|
|
12477
|
+
* Capture command snapshots once startup begins. The normal boot path emits
|
|
12478
|
+
* SYSTEM_COMMANDS_READY before core.start(), so initial reconciliation reads the
|
|
12479
|
+
* authoritative registry state instead of relying on replay of that event. Once
|
|
12480
|
+
* the API is ready, later snapshots drive hot reloads.
|
|
12481
|
+
*/
|
|
12482
|
+
subscribeToCommandSnapshots() {
|
|
12483
|
+
if (this._commandsReadyHandler) return;
|
|
12484
|
+
this._commandsReadyHandler = ({ commands }) => {
|
|
12485
|
+
this._latestCommandSnapshot = commands;
|
|
12486
|
+
if (this._commandApiReady) this.syncCommandsWithRetry(commands);
|
|
12487
|
+
};
|
|
12488
|
+
this.core.eventBus.on(BusEvent.SYSTEM_COMMANDS_READY, this._commandsReadyHandler);
|
|
12489
|
+
}
|
|
12490
|
+
/** Schedule one authoritative initial reconciliation after grammY is available. */
|
|
12491
|
+
scheduleInitialCommandSync() {
|
|
12492
|
+
if (this._initialCommandSyncScheduled) return;
|
|
12493
|
+
this._initialCommandSyncScheduled = true;
|
|
12494
|
+
const registry = this.core.lifecycleManager?.serviceRegistry?.get("command-registry");
|
|
12495
|
+
const commands = registry?.getAll() ?? this._latestCommandSnapshot;
|
|
12496
|
+
if (!commands) {
|
|
12497
|
+
log38.warn(
|
|
12498
|
+
{ operation: "telegram-command-sync" },
|
|
12499
|
+
"Telegram command registry is not ready; waiting for a commands-ready snapshot"
|
|
12500
|
+
);
|
|
12501
|
+
return;
|
|
12502
|
+
}
|
|
12503
|
+
this.syncCommandsWithRetry(commands);
|
|
12504
|
+
}
|
|
11719
12505
|
/**
|
|
11720
12506
|
* Set up the grammY bot, register all callback and message handlers, then perform
|
|
11721
12507
|
* two-phase startup: Phase 1 starts polling immediately; Phase 2 checks group
|
|
@@ -11724,6 +12510,11 @@ var init_adapter = __esm({
|
|
|
11724
12510
|
*/
|
|
11725
12511
|
async start() {
|
|
11726
12512
|
this._stopping = false;
|
|
12513
|
+
this._commandSyncGeneration++;
|
|
12514
|
+
this._commandSyncAbort = new AbortController();
|
|
12515
|
+
this._pendingCommandSnapshot = void 0;
|
|
12516
|
+
this._commandSyncWorkerRunning = false;
|
|
12517
|
+
this.subscribeToCommandSnapshots();
|
|
11727
12518
|
this.bot = new Bot(this.telegramConfig.botToken, {
|
|
11728
12519
|
client: {
|
|
11729
12520
|
baseFetchConfig: { duplex: "half" },
|
|
@@ -11748,7 +12539,7 @@ var init_adapter = __esm({
|
|
|
11748
12539
|
);
|
|
11749
12540
|
this.bot.catch((err) => {
|
|
11750
12541
|
const rootCause = err.error instanceof Error ? err.error : err;
|
|
11751
|
-
|
|
12542
|
+
log38.error({ err: rootCause }, "Telegram bot error");
|
|
11752
12543
|
});
|
|
11753
12544
|
this.bot.api.config.use(async (prev, method, payload, signal) => {
|
|
11754
12545
|
const maxRetries = 3;
|
|
@@ -11766,7 +12557,7 @@ var init_adapter = __esm({
|
|
|
11766
12557
|
if (rateLimitedMethods.includes(method)) {
|
|
11767
12558
|
this.sendQueue.onRateLimited();
|
|
11768
12559
|
}
|
|
11769
|
-
|
|
12560
|
+
log38.warn(
|
|
11770
12561
|
{ method, retryAfter, attempt: attempt + 1 },
|
|
11771
12562
|
"Rate limited by Telegram, retrying"
|
|
11772
12563
|
);
|
|
@@ -11784,10 +12575,6 @@ var init_adapter = __esm({
|
|
|
11784
12575
|
}
|
|
11785
12576
|
return prev(method, payload, signal);
|
|
11786
12577
|
});
|
|
11787
|
-
const onCommandsReady = ({ commands }) => {
|
|
11788
|
-
this.syncCommandsWithRetry(commands);
|
|
11789
|
-
};
|
|
11790
|
-
this.core.eventBus.on(BusEvent.SYSTEM_COMMANDS_READY, onCommandsReady);
|
|
11791
12578
|
this.bot.use((ctx, next) => {
|
|
11792
12579
|
const chatId = ctx.chat?.id ?? ctx.callbackQuery?.message?.chat?.id;
|
|
11793
12580
|
if (chatId !== this.telegramConfig.chatId) return;
|
|
@@ -11801,7 +12588,52 @@ var init_adapter = __esm({
|
|
|
11801
12588
|
);
|
|
11802
12589
|
this.bot.on("message:text", async (ctx, next) => {
|
|
11803
12590
|
const text3 = ctx.message?.text;
|
|
11804
|
-
if (!text3
|
|
12591
|
+
if (!text3) return next();
|
|
12592
|
+
const topicIdForInput = ctx.message.message_thread_id;
|
|
12593
|
+
const inputKey = `${ctx.chat.id}:${ctx.from?.id ?? 0}:${topicIdForInput ?? 0}`;
|
|
12594
|
+
const pending = this.pendingCommandInputs.get(inputKey);
|
|
12595
|
+
if (!text3.startsWith("/") && pending) {
|
|
12596
|
+
if (pending.promptMessageId !== void 0 && ctx.message.reply_to_message?.message_id !== pending.promptMessageId) {
|
|
12597
|
+
return next();
|
|
12598
|
+
}
|
|
12599
|
+
this.pendingCommandInputs.delete(inputKey);
|
|
12600
|
+
if (pending.expiresAt <= Date.now()) {
|
|
12601
|
+
await ctx.reply("This input request expired. Start the operation again.").catch(() => {
|
|
12602
|
+
});
|
|
12603
|
+
return;
|
|
12604
|
+
}
|
|
12605
|
+
if (pending.sensitive) {
|
|
12606
|
+
try {
|
|
12607
|
+
await ctx.deleteMessage();
|
|
12608
|
+
} catch {
|
|
12609
|
+
await ctx.reply(`\u26A0\uFE0F I could not securely remove that message, so its value was not used. ${pending.fallback}`).catch(() => {
|
|
12610
|
+
});
|
|
12611
|
+
return;
|
|
12612
|
+
}
|
|
12613
|
+
}
|
|
12614
|
+
const registry2 = this.core.lifecycleManager?.serviceRegistry?.get("command-registry");
|
|
12615
|
+
if (!registry2) return;
|
|
12616
|
+
const sessionId = topicIdForInput != null ? (await this.core.getOrResumeSession("telegram", String(topicIdForInput)))?.id ?? null : null;
|
|
12617
|
+
const response = await registry2.execute(pending.command, {
|
|
12618
|
+
raw: "",
|
|
12619
|
+
sessionId,
|
|
12620
|
+
channelId: "telegram",
|
|
12621
|
+
userId: String(ctx.from?.id),
|
|
12622
|
+
conversationId: `${ctx.chat.id}:${topicIdForInput ?? 0}`,
|
|
12623
|
+
interaction: {
|
|
12624
|
+
textInput: true,
|
|
12625
|
+
secureInput: "delete-after-capture",
|
|
12626
|
+
capturedInput: { value: text3, sensitive: pending.sensitive }
|
|
12627
|
+
},
|
|
12628
|
+
reply: async () => {
|
|
12629
|
+
}
|
|
12630
|
+
});
|
|
12631
|
+
if (response.type !== "silent" && response.type !== "delegated") {
|
|
12632
|
+
await this.renderCommandResponse(response, ctx.chat.id, topicIdForInput, String(ctx.from?.id));
|
|
12633
|
+
}
|
|
12634
|
+
return;
|
|
12635
|
+
}
|
|
12636
|
+
if (!text3.startsWith("/")) return next();
|
|
11805
12637
|
const rawCmd = text3.split(" ")[0].slice(1).split("@")[0].toLowerCase();
|
|
11806
12638
|
if (rawCmd === "retry") {
|
|
11807
12639
|
await this.handleRetryCommand(ctx);
|
|
@@ -11847,6 +12679,8 @@ var init_adapter = __esm({
|
|
|
11847
12679
|
sessionId,
|
|
11848
12680
|
channelId: "telegram",
|
|
11849
12681
|
userId: String(ctx.from?.id),
|
|
12682
|
+
conversationId: `${chatId}:${topicId ?? 0}`,
|
|
12683
|
+
interaction: { textInput: true, secureInput: "delete-after-capture" },
|
|
11850
12684
|
reply: async (content) => {
|
|
11851
12685
|
if (typeof content === "string") {
|
|
11852
12686
|
await ctx.reply(content);
|
|
@@ -11854,7 +12688,8 @@ var init_adapter = __esm({
|
|
|
11854
12688
|
await this.renderCommandResponse(
|
|
11855
12689
|
content,
|
|
11856
12690
|
chatId,
|
|
11857
|
-
topicId
|
|
12691
|
+
topicId,
|
|
12692
|
+
String(ctx.from?.id)
|
|
11858
12693
|
);
|
|
11859
12694
|
}
|
|
11860
12695
|
}
|
|
@@ -11865,7 +12700,7 @@ var init_adapter = __esm({
|
|
|
11865
12700
|
if (response.type === "silent") {
|
|
11866
12701
|
return next();
|
|
11867
12702
|
}
|
|
11868
|
-
await this.renderCommandResponse(response, chatId, topicId);
|
|
12703
|
+
await this.renderCommandResponse(response, chatId, topicId, String(ctx.from?.id));
|
|
11869
12704
|
} catch (err) {
|
|
11870
12705
|
await ctx.reply(`\u26A0\uFE0F Command failed: ${String(err)}`);
|
|
11871
12706
|
}
|
|
@@ -11894,6 +12729,8 @@ var init_adapter = __esm({
|
|
|
11894
12729
|
sessionId,
|
|
11895
12730
|
channelId: "telegram",
|
|
11896
12731
|
userId: String(ctx.from?.id),
|
|
12732
|
+
conversationId: `${chatId}:${topicId ?? 0}`,
|
|
12733
|
+
interaction: { textInput: true, secureInput: "delete-after-capture" },
|
|
11897
12734
|
reply: async (content) => {
|
|
11898
12735
|
if (typeof content === "string") {
|
|
11899
12736
|
await ctx.editMessageText(content).catch(() => {
|
|
@@ -11902,7 +12739,11 @@ var init_adapter = __esm({
|
|
|
11902
12739
|
}
|
|
11903
12740
|
});
|
|
11904
12741
|
await ctx.answerCallbackQuery();
|
|
11905
|
-
if (response.type !== "silent") {
|
|
12742
|
+
if (response.type !== "silent" && response.type !== "delegated") {
|
|
12743
|
+
if (response.type === "input") {
|
|
12744
|
+
await this.renderCommandResponse(response, chatId, topicId, String(ctx.from?.id));
|
|
12745
|
+
return;
|
|
12746
|
+
}
|
|
11906
12747
|
if (response.type === "menu") {
|
|
11907
12748
|
const keyboard = response.options.map((opt) => [
|
|
11908
12749
|
{
|
|
@@ -12010,32 +12851,34 @@ ${p}` : p;
|
|
|
12010
12851
|
}
|
|
12011
12852
|
);
|
|
12012
12853
|
this.setupRoutes();
|
|
12854
|
+
this._commandApiReady = true;
|
|
12855
|
+
this.scheduleInitialCommandSync();
|
|
12013
12856
|
void this.bot.start({
|
|
12014
12857
|
allowed_updates: ["message", "callback_query"],
|
|
12015
|
-
onStart: () =>
|
|
12858
|
+
onStart: () => log38.info({ chatId: this.telegramConfig.chatId }, "Telegram bot started")
|
|
12016
12859
|
}).catch((err) => {
|
|
12017
12860
|
if (this._stopping) return;
|
|
12018
12861
|
const inner = err?.error;
|
|
12019
12862
|
const rootCause = err instanceof Error ? err : inner instanceof Error ? inner : err;
|
|
12020
|
-
|
|
12863
|
+
log38.error({ err: rootCause }, "Telegram polling stopped unexpectedly");
|
|
12021
12864
|
if (this.core.requestRestart) {
|
|
12022
12865
|
const requestRestart = this.core.requestRestart;
|
|
12023
|
-
|
|
12866
|
+
log38.error("Restarting OpenACP because Telegram polling stopped");
|
|
12024
12867
|
setImmediate(() => {
|
|
12025
12868
|
if (this._stopping) return;
|
|
12026
12869
|
void requestRestart().catch((restartErr) => {
|
|
12027
|
-
|
|
12870
|
+
log38.error({ err: restartErr }, "OpenACP restart request failed after Telegram polling stopped");
|
|
12028
12871
|
});
|
|
12029
12872
|
});
|
|
12030
12873
|
} else {
|
|
12031
|
-
|
|
12874
|
+
log38.error("Exiting because Telegram polling stopped and no restart hook is available");
|
|
12032
12875
|
setImmediate(() => {
|
|
12033
12876
|
if (this._stopping) return;
|
|
12034
12877
|
process.exit(1);
|
|
12035
12878
|
});
|
|
12036
12879
|
}
|
|
12037
12880
|
});
|
|
12038
|
-
|
|
12881
|
+
log38.info(
|
|
12039
12882
|
{
|
|
12040
12883
|
chatId: this.telegramConfig.chatId,
|
|
12041
12884
|
notificationTopicId: this.telegramConfig.notificationTopicId,
|
|
@@ -12050,12 +12893,12 @@ ${p}` : p;
|
|
|
12050
12893
|
this.telegramFetch()
|
|
12051
12894
|
);
|
|
12052
12895
|
if (prereqResult.ok) {
|
|
12053
|
-
|
|
12896
|
+
log38.info("Telegram adapter: prerequisites OK, initializing topic-dependent features");
|
|
12054
12897
|
await this.initTopicDependentFeatures();
|
|
12055
12898
|
} else {
|
|
12056
|
-
|
|
12899
|
+
log38.warn({ issues: prereqResult.issues }, "Telegram adapter: prerequisites NOT met, starting watcher");
|
|
12057
12900
|
for (const issue of prereqResult.issues) {
|
|
12058
|
-
|
|
12901
|
+
log38.warn({ issue }, "Telegram prerequisite not met");
|
|
12059
12902
|
}
|
|
12060
12903
|
this.startPrerequisiteWatcher(prereqResult.issues);
|
|
12061
12904
|
}
|
|
@@ -12071,7 +12914,7 @@ ${p}` : p;
|
|
|
12071
12914
|
} catch (err) {
|
|
12072
12915
|
if (attempt === maxRetries) throw err;
|
|
12073
12916
|
const delay = baseDelayMs * Math.pow(2, attempt - 1);
|
|
12074
|
-
|
|
12917
|
+
log38.warn(
|
|
12075
12918
|
{ err, attempt, maxRetries, delayMs: delay, operation: label },
|
|
12076
12919
|
`${label} failed, retrying in ${delay}ms`
|
|
12077
12920
|
);
|
|
@@ -12086,21 +12929,126 @@ ${p}` : p;
|
|
|
12086
12929
|
* from the registry, deduplicating by command name. Non-critical.
|
|
12087
12930
|
*/
|
|
12088
12931
|
syncCommandsWithRetry(registryCommands) {
|
|
12089
|
-
|
|
12090
|
-
|
|
12091
|
-
|
|
12092
|
-
this.
|
|
12093
|
-
|
|
12094
|
-
|
|
12095
|
-
|
|
12096
|
-
|
|
12097
|
-
|
|
12098
|
-
|
|
12932
|
+
this._pendingCommandSnapshot = registryCommands;
|
|
12933
|
+
this._commandSnapshotVersion++;
|
|
12934
|
+
if (this._commandSyncWorkerRunning) return;
|
|
12935
|
+
this._commandSyncWorkerRunning = true;
|
|
12936
|
+
const generation = this._commandSyncGeneration;
|
|
12937
|
+
const signal = this._commandSyncAbort?.signal;
|
|
12938
|
+
this._commandSyncChain = (async () => {
|
|
12939
|
+
while (this._pendingCommandSnapshot && generation === this._commandSyncGeneration && !signal?.aborted) {
|
|
12940
|
+
const snapshot = this._pendingCommandSnapshot;
|
|
12941
|
+
const snapshotVersion = this._commandSnapshotVersion;
|
|
12942
|
+
this._pendingCommandSnapshot = void 0;
|
|
12943
|
+
let allCommands;
|
|
12944
|
+
try {
|
|
12945
|
+
const boundary = prepareTelegramCommandBoundary(STATIC_COMMANDS, snapshot);
|
|
12946
|
+
allCommands = boundary.commands;
|
|
12947
|
+
const { invalidName, invalidDescription, duplicate, overflow } = boundary.skipped;
|
|
12948
|
+
if (invalidName || invalidDescription || duplicate || overflow) {
|
|
12949
|
+
log38.warn(
|
|
12950
|
+
{ invalidName, invalidDescription, duplicate, overflow, operation: "telegram-command-boundary" },
|
|
12951
|
+
"Skipped plugin Telegram commands before Bot API sync. Names must match [a-z0-9_]{1,32}, descriptions must contain 1-256 characters, and only the first valid commands up to the 100-command limit are published."
|
|
12952
|
+
);
|
|
12953
|
+
}
|
|
12954
|
+
if (!allCommands.some((command) => command.command === "proxy")) {
|
|
12955
|
+
throw new TelegramCommandBoundaryError("Required OpenACP command /proxy is missing");
|
|
12956
|
+
}
|
|
12957
|
+
} catch (error) {
|
|
12958
|
+
log38.error(
|
|
12959
|
+
{ error: redactNetworkSecrets(error instanceof Error ? error.message : String(error)), operation: "telegram-command-boundary" },
|
|
12960
|
+
"OpenACP core Telegram command definitions are invalid; command sync was skipped without changing Telegram state"
|
|
12961
|
+
);
|
|
12962
|
+
continue;
|
|
12963
|
+
}
|
|
12964
|
+
const maxAttempts = 5;
|
|
12965
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
12966
|
+
if (generation !== this._commandSyncGeneration || signal?.aborted) return;
|
|
12967
|
+
try {
|
|
12968
|
+
const result = await synchronizeTelegramCommands(
|
|
12969
|
+
this.bot.api,
|
|
12970
|
+
this.telegramConfig.chatId,
|
|
12971
|
+
allCommands,
|
|
12972
|
+
{
|
|
12973
|
+
ownershipStore: this.commandOwnershipStore,
|
|
12974
|
+
botId: this.commandOwnerBotId,
|
|
12975
|
+
ownerIdentity: this.commandOwnerIdentity,
|
|
12976
|
+
allowOwnerTakeover: this.commandOwnerTakeoverRequested,
|
|
12977
|
+
onOwnerClaimed: () => {
|
|
12978
|
+
this._commandOwnerClaimed = true;
|
|
12979
|
+
this.startCommandOwnerHeartbeat();
|
|
12980
|
+
},
|
|
12981
|
+
historicalOwnedNames: HISTORICAL_OPENACP_COMMAND_NAMES,
|
|
12982
|
+
signal
|
|
12983
|
+
}
|
|
12984
|
+
);
|
|
12985
|
+
if (generation !== this._commandSyncGeneration || signal?.aborted) return;
|
|
12986
|
+
log38.info(
|
|
12987
|
+
{ updatedScopes: result.updated, unchangedScopes: result.unchanged },
|
|
12988
|
+
"Telegram command menus synchronized"
|
|
12989
|
+
);
|
|
12990
|
+
break;
|
|
12991
|
+
} catch (err) {
|
|
12992
|
+
if (generation !== this._commandSyncGeneration || signal?.aborted) return;
|
|
12993
|
+
if (err instanceof TelegramCommandOwnerConflictError) {
|
|
12994
|
+
log38.warn(
|
|
12995
|
+
{ code: err.code, ownerInstanceId: err.ownerInstanceId, instanceId: this.commandOwnerIdentity.instanceId, operation: "telegram-command-sync" },
|
|
12996
|
+
"Telegram command sync is owned by another OpenACP instance. This instance did not inspect or mutate Telegram command lists. Configure a unique bot per instance, or stop the same-host owner and request an explicit takeover."
|
|
12997
|
+
);
|
|
12998
|
+
break;
|
|
12999
|
+
}
|
|
13000
|
+
if (snapshotVersion !== this._commandSnapshotVersion && this._pendingCommandSnapshot) break;
|
|
13001
|
+
const safeError2 = redactNetworkSecrets(err instanceof Error ? err.message : String(err));
|
|
13002
|
+
if (attempt === maxAttempts) {
|
|
13003
|
+
log38.warn(
|
|
13004
|
+
{ error: safeError2, attempt, maxAttempts, operation: "telegram-command-sync" },
|
|
13005
|
+
"Telegram command menu sync failed; bot remains online. Run /doctor and inspect Telegram connectivity"
|
|
13006
|
+
);
|
|
13007
|
+
break;
|
|
13008
|
+
}
|
|
13009
|
+
const delayMs = 2e3 * Math.pow(2, attempt - 1);
|
|
13010
|
+
log38.warn(
|
|
13011
|
+
{ error: safeError2, attempt, maxAttempts, delayMs, operation: "telegram-command-sync" },
|
|
13012
|
+
"Telegram command menu sync failed, retrying"
|
|
13013
|
+
);
|
|
13014
|
+
await new Promise((resolve7) => {
|
|
13015
|
+
const timer = setTimeout(resolve7, delayMs);
|
|
13016
|
+
signal?.addEventListener("abort", () => {
|
|
13017
|
+
clearTimeout(timer);
|
|
13018
|
+
resolve7();
|
|
13019
|
+
}, { once: true });
|
|
13020
|
+
});
|
|
13021
|
+
}
|
|
13022
|
+
}
|
|
13023
|
+
}
|
|
13024
|
+
})().finally(() => {
|
|
13025
|
+
this._commandSyncWorkerRunning = false;
|
|
13026
|
+
if (this._pendingCommandSnapshot && generation === this._commandSyncGeneration && !signal?.aborted) {
|
|
13027
|
+
this.syncCommandsWithRetry(this._pendingCommandSnapshot);
|
|
13028
|
+
}
|
|
12099
13029
|
});
|
|
12100
13030
|
}
|
|
13031
|
+
startCommandOwnerHeartbeat() {
|
|
13032
|
+
if (this._commandOwnerHeartbeat) return;
|
|
13033
|
+
this._commandOwnerHeartbeat = setInterval(() => {
|
|
13034
|
+
void this.commandOwnershipStore.heartbeatOwner(this.commandOwnerBotId, this.commandOwnerIdentity).then((owned) => {
|
|
13035
|
+
if (!owned) {
|
|
13036
|
+
this._commandOwnerClaimed = false;
|
|
13037
|
+
log38.warn(
|
|
13038
|
+
{ instanceId: this.commandOwnerIdentity.instanceId, operation: "telegram-command-owner-heartbeat" },
|
|
13039
|
+
"Telegram command-sync ownership was lost; no further command-list writes will be attempted until ownership is resolved."
|
|
13040
|
+
);
|
|
13041
|
+
}
|
|
13042
|
+
}).catch((error) => log38.warn(
|
|
13043
|
+
{ error: redactNetworkSecrets(error instanceof Error ? error.message : String(error)), operation: "telegram-command-owner-heartbeat" },
|
|
13044
|
+
"Could not refresh Telegram command-sync ownership heartbeat"
|
|
13045
|
+
));
|
|
13046
|
+
}, 3e4);
|
|
13047
|
+
this._commandOwnerHeartbeat.unref?.();
|
|
13048
|
+
}
|
|
12101
13049
|
async initTopicDependentFeatures() {
|
|
12102
13050
|
if (this._topicsInitialized) return;
|
|
12103
|
-
|
|
13051
|
+
log38.info(
|
|
12104
13052
|
{ notificationTopicId: this.telegramConfig.notificationTopicId, assistantTopicId: this.telegramConfig.assistantTopicId },
|
|
12105
13053
|
"initTopicDependentFeatures: starting (existing IDs in config)"
|
|
12106
13054
|
);
|
|
@@ -12125,7 +13073,7 @@ ${p}` : p;
|
|
|
12125
13073
|
this.assistantTopicId = topics.assistantTopicId;
|
|
12126
13074
|
this._systemTopicIds.notificationTopicId = topics.notificationTopicId;
|
|
12127
13075
|
this._systemTopicIds.assistantTopicId = topics.assistantTopicId;
|
|
12128
|
-
|
|
13076
|
+
log38.info(
|
|
12129
13077
|
{ notificationTopicId: this.notificationTopicId, assistantTopicId: this.assistantTopicId },
|
|
12130
13078
|
"initTopicDependentFeatures: topics ready"
|
|
12131
13079
|
);
|
|
@@ -12156,7 +13104,7 @@ ${p}` : p;
|
|
|
12156
13104
|
).then((msg) => {
|
|
12157
13105
|
if (msg) this.storeControlMsgId(sessionId, msg.message_id);
|
|
12158
13106
|
}).catch((err) => {
|
|
12159
|
-
|
|
13107
|
+
log38.warn({ err, sessionId }, "Failed to send initial messages for new session");
|
|
12160
13108
|
});
|
|
12161
13109
|
};
|
|
12162
13110
|
this.core.eventBus.on(BusEvent.SESSION_THREAD_READY, this._threadReadyHandler);
|
|
@@ -12198,7 +13146,7 @@ ${p}` : p;
|
|
|
12198
13146
|
this._queueNotifications.delete(data.turnId);
|
|
12199
13147
|
if (result) {
|
|
12200
13148
|
this.bot.api.deleteMessage(this.telegramConfig.chatId, result.message_id).catch((err) => {
|
|
12201
|
-
|
|
13149
|
+
log38.warn({ err }, "Failed to delete queue notification after race");
|
|
12202
13150
|
});
|
|
12203
13151
|
}
|
|
12204
13152
|
} else if (result) {
|
|
@@ -12208,7 +13156,7 @@ ${p}` : p;
|
|
|
12208
13156
|
}
|
|
12209
13157
|
} catch (err) {
|
|
12210
13158
|
this._queueNotifications.delete(data.turnId);
|
|
12211
|
-
|
|
13159
|
+
log38.warn({ err }, "Failed to send queue notification");
|
|
12212
13160
|
}
|
|
12213
13161
|
});
|
|
12214
13162
|
this.core.eventBus.on(BusEvent.MESSAGE_PROCESSING, async (data) => {
|
|
@@ -12217,13 +13165,13 @@ ${p}` : p;
|
|
|
12217
13165
|
if (typeof entry === "number") {
|
|
12218
13166
|
this._queueNotifications.delete(data.turnId);
|
|
12219
13167
|
this.bot.api.deleteMessage(this.telegramConfig.chatId, entry).catch((err) => {
|
|
12220
|
-
|
|
13168
|
+
log38.warn({ err }, "Failed to delete queue notification on processing");
|
|
12221
13169
|
});
|
|
12222
13170
|
} else if (entry === "pending") {
|
|
12223
13171
|
this._queueNotifications.set(data.turnId, "delete");
|
|
12224
13172
|
}
|
|
12225
13173
|
});
|
|
12226
|
-
|
|
13174
|
+
log38.info({ assistantTopicId: this.assistantTopicId }, "initTopicDependentFeatures: sending welcome message");
|
|
12227
13175
|
try {
|
|
12228
13176
|
const config = this.core.configManager.get();
|
|
12229
13177
|
const agents = this.core.agentManager.getAvailableAgents();
|
|
@@ -12245,17 +13193,17 @@ ${p}` : p;
|
|
|
12245
13193
|
this.core.lifecycleManager?.serviceRegistry?.get("menu-registry")
|
|
12246
13194
|
)
|
|
12247
13195
|
});
|
|
12248
|
-
|
|
13196
|
+
log38.info("initTopicDependentFeatures: welcome message sent");
|
|
12249
13197
|
} catch (err) {
|
|
12250
|
-
|
|
13198
|
+
log38.warn({ err }, "Failed to send welcome message");
|
|
12251
13199
|
}
|
|
12252
13200
|
try {
|
|
12253
13201
|
await this.core.assistantManager.getOrSpawn("telegram", String(this.assistantTopicId));
|
|
12254
13202
|
} catch (err) {
|
|
12255
|
-
|
|
13203
|
+
log38.error({ err }, "Failed to spawn assistant");
|
|
12256
13204
|
}
|
|
12257
13205
|
this._topicsInitialized = true;
|
|
12258
|
-
|
|
13206
|
+
log38.info("Telegram adapter fully initialized");
|
|
12259
13207
|
}
|
|
12260
13208
|
/**
|
|
12261
13209
|
* Handle the /retry command — re-runs prerequisite checks immediately.
|
|
@@ -12292,7 +13240,7 @@ ${p}` : p;
|
|
|
12292
13240
|
).catch(() => {
|
|
12293
13241
|
});
|
|
12294
13242
|
} catch (err) {
|
|
12295
|
-
|
|
13243
|
+
log38.error({ err }, "handleRetryCommand: init failed after prerequisites met");
|
|
12296
13244
|
await ctx.reply("\u26A0\uFE0F Prerequisites are met but initialization failed. Check server logs and try /retry again.").catch(() => {
|
|
12297
13245
|
});
|
|
12298
13246
|
}
|
|
@@ -12318,7 +13266,7 @@ OpenACP will automatically retry every 30 seconds. After fixing the issues above
|
|
|
12318
13266
|
this.bot.api.sendMessage(this.telegramConfig.chatId, setupMessage, {
|
|
12319
13267
|
parse_mode: "HTML"
|
|
12320
13268
|
}).catch((err) => {
|
|
12321
|
-
|
|
13269
|
+
log38.warn({ err }, "Failed to send setup guidance to General topic");
|
|
12322
13270
|
});
|
|
12323
13271
|
const schedule = [5e3, 1e4, 3e4];
|
|
12324
13272
|
let attempt = 1;
|
|
@@ -12331,7 +13279,7 @@ OpenACP will automatically retry every 30 seconds. After fixing the issues above
|
|
|
12331
13279
|
this.telegramFetch()
|
|
12332
13280
|
);
|
|
12333
13281
|
if (result.ok) {
|
|
12334
|
-
|
|
13282
|
+
log38.info("Prerequisites met \u2014 completing Telegram adapter initialization");
|
|
12335
13283
|
try {
|
|
12336
13284
|
await this.initTopicDependentFeatures();
|
|
12337
13285
|
this._prerequisiteWatcher = null;
|
|
@@ -12344,14 +13292,14 @@ System topics have been created. Tap${assistantTopicLink} to get started.`,
|
|
|
12344
13292
|
{ parse_mode: "HTML" }
|
|
12345
13293
|
);
|
|
12346
13294
|
} catch (err) {
|
|
12347
|
-
|
|
13295
|
+
log38.error({ err }, "Failed to complete initialization after prerequisites met \u2014 will retry");
|
|
12348
13296
|
const delay2 = schedule[Math.min(attempt, schedule.length - 1)];
|
|
12349
13297
|
attempt++;
|
|
12350
13298
|
this._prerequisiteWatcher = setTimeout(retry, delay2);
|
|
12351
13299
|
}
|
|
12352
13300
|
return;
|
|
12353
13301
|
}
|
|
12354
|
-
|
|
13302
|
+
log38.debug({ issues: result.issues }, "Prerequisites not yet met, retrying");
|
|
12355
13303
|
const delay = schedule[Math.min(attempt, schedule.length - 1)];
|
|
12356
13304
|
attempt++;
|
|
12357
13305
|
this._prerequisiteWatcher = setTimeout(retry, delay);
|
|
@@ -12367,6 +13315,14 @@ System topics have been created. Tap${assistantTopicLink} to get started.`,
|
|
|
12367
13315
|
*/
|
|
12368
13316
|
async stop() {
|
|
12369
13317
|
this._stopping = true;
|
|
13318
|
+
this._commandSyncGeneration++;
|
|
13319
|
+
this._commandSyncAbort?.abort();
|
|
13320
|
+
this._commandSyncAbort = void 0;
|
|
13321
|
+
this._pendingCommandSnapshot = void 0;
|
|
13322
|
+
if (this._commandOwnerHeartbeat) {
|
|
13323
|
+
clearInterval(this._commandOwnerHeartbeat);
|
|
13324
|
+
this._commandOwnerHeartbeat = void 0;
|
|
13325
|
+
}
|
|
12370
13326
|
this.unregisterProxyTester?.();
|
|
12371
13327
|
this.unregisterProxyTester = void 0;
|
|
12372
13328
|
if (this._prerequisiteWatcher !== null) {
|
|
@@ -12385,12 +13341,36 @@ System topics have been created. Tap${assistantTopicLink} to get started.`,
|
|
|
12385
13341
|
this.core.eventBus.off("session:configChanged", this._configChangedHandler);
|
|
12386
13342
|
this._configChangedHandler = void 0;
|
|
12387
13343
|
}
|
|
13344
|
+
if (this._commandsReadyHandler) {
|
|
13345
|
+
this.core.eventBus.off(BusEvent.SYSTEM_COMMANDS_READY, this._commandsReadyHandler);
|
|
13346
|
+
this._commandsReadyHandler = void 0;
|
|
13347
|
+
}
|
|
13348
|
+
this._commandApiReady = false;
|
|
13349
|
+
this._initialCommandSyncScheduled = false;
|
|
13350
|
+
this._latestCommandSnapshot = void 0;
|
|
12388
13351
|
this.sendQueue.clear();
|
|
12389
|
-
|
|
12390
|
-
|
|
13352
|
+
this.pendingCommandInputs.clear();
|
|
13353
|
+
this.callbackCache.clear();
|
|
13354
|
+
clearProxyDraftsForChannel("telegram");
|
|
13355
|
+
try {
|
|
13356
|
+
await this.bot.stop();
|
|
13357
|
+
} finally {
|
|
13358
|
+
if (this._commandOwnerClaimed) {
|
|
13359
|
+
try {
|
|
13360
|
+
await this.commandOwnershipStore.releaseOwner(this.commandOwnerBotId, this.commandOwnerIdentity);
|
|
13361
|
+
} catch (error) {
|
|
13362
|
+
log38.warn(
|
|
13363
|
+
{ error: redactNetworkSecrets(error instanceof Error ? error.message : String(error)), operation: "telegram-command-owner-release" },
|
|
13364
|
+
"Could not mark Telegram command-sync ownership as stopped; a same-host explicit takeover may be required on restart"
|
|
13365
|
+
);
|
|
13366
|
+
}
|
|
13367
|
+
this._commandOwnerClaimed = false;
|
|
13368
|
+
}
|
|
13369
|
+
}
|
|
13370
|
+
log38.info("Telegram bot stopped");
|
|
12391
13371
|
}
|
|
12392
13372
|
// --- CommandRegistry response rendering ---
|
|
12393
|
-
async renderCommandResponse(response, chatId, topicId) {
|
|
13373
|
+
async renderCommandResponse(response, chatId, topicId, userId) {
|
|
12394
13374
|
switch (response.type) {
|
|
12395
13375
|
case "text":
|
|
12396
13376
|
await this.bot.api.sendMessage(chatId, response.text, {
|
|
@@ -12458,6 +13438,34 @@ ${lines.join("\n")}`;
|
|
|
12458
13438
|
});
|
|
12459
13439
|
break;
|
|
12460
13440
|
}
|
|
13441
|
+
case "input": {
|
|
13442
|
+
if (!userId) {
|
|
13443
|
+
await this.bot.api.sendMessage(chatId, response.fallback, { message_thread_id: topicId });
|
|
13444
|
+
break;
|
|
13445
|
+
}
|
|
13446
|
+
const key = `${chatId}:${userId}:${topicId ?? 0}`;
|
|
13447
|
+
for (const [pendingKey, pending] of this.pendingCommandInputs) {
|
|
13448
|
+
if (pending.expiresAt <= Date.now()) this.pendingCommandInputs.delete(pendingKey);
|
|
13449
|
+
}
|
|
13450
|
+
while (this.pendingCommandInputs.size >= 1e3) {
|
|
13451
|
+
const oldest = this.pendingCommandInputs.keys().next().value;
|
|
13452
|
+
if (!oldest) break;
|
|
13453
|
+
this.pendingCommandInputs.delete(oldest);
|
|
13454
|
+
}
|
|
13455
|
+
const warning = response.sensitive ? "\n\n\u{1F510} Your reply will be deleted before the value is processed. If deletion fails, the value will not be used." : "";
|
|
13456
|
+
const promptMessage = await this.bot.api.sendMessage(chatId, `${response.prompt}${warning}`, {
|
|
13457
|
+
message_thread_id: topicId,
|
|
13458
|
+
reply_markup: { force_reply: true, selective: true }
|
|
13459
|
+
});
|
|
13460
|
+
this.pendingCommandInputs.set(key, {
|
|
13461
|
+
command: response.command,
|
|
13462
|
+
sensitive: response.sensitive === true,
|
|
13463
|
+
fallback: response.fallback,
|
|
13464
|
+
expiresAt: Date.now() + (response.expiresInMs ?? 10 * 6e4),
|
|
13465
|
+
promptMessageId: promptMessage.message_id
|
|
13466
|
+
});
|
|
13467
|
+
break;
|
|
13468
|
+
}
|
|
12461
13469
|
case "silent":
|
|
12462
13470
|
break;
|
|
12463
13471
|
}
|
|
@@ -12532,7 +13540,7 @@ ${lines.join("\n")}`;
|
|
|
12532
13540
|
username: ctx.from.username
|
|
12533
13541
|
}
|
|
12534
13542
|
}
|
|
12535
|
-
).catch((err) =>
|
|
13543
|
+
).catch((err) => log38.error({ err }, "handleMessage error"));
|
|
12536
13544
|
});
|
|
12537
13545
|
this.bot.on("message:photo", async (ctx) => {
|
|
12538
13546
|
const threadId = ctx.message.message_thread_id;
|
|
@@ -12638,7 +13646,7 @@ ${lines.join("\n")}`;
|
|
|
12638
13646
|
if (session.archiving) return;
|
|
12639
13647
|
const threadId = Number(session.threadId);
|
|
12640
13648
|
if (!threadId || isNaN(threadId)) {
|
|
12641
|
-
|
|
13649
|
+
log38.warn(
|
|
12642
13650
|
{ sessionId, threadId: session.threadId },
|
|
12643
13651
|
"Session has no valid threadId, skipping message"
|
|
12644
13652
|
);
|
|
@@ -12654,7 +13662,7 @@ ${lines.join("\n")}`;
|
|
|
12654
13662
|
this._sessionThreadIds.delete(sessionId);
|
|
12655
13663
|
}
|
|
12656
13664
|
}).catch((err) => {
|
|
12657
|
-
|
|
13665
|
+
log38.warn({ err, sessionId }, "Dispatch queue error");
|
|
12658
13666
|
});
|
|
12659
13667
|
this._dispatchQueues.set(sessionId, next);
|
|
12660
13668
|
await next;
|
|
@@ -12784,7 +13792,7 @@ ${lines.join("\n")}`;
|
|
|
12784
13792
|
usageMsgId = result?.message_id;
|
|
12785
13793
|
if (usageMsgId) this.usageMsgIds.set(sessionId, usageMsgId);
|
|
12786
13794
|
} catch (err) {
|
|
12787
|
-
|
|
13795
|
+
log38.warn({ err, sessionId }, "Failed to send usage message");
|
|
12788
13796
|
}
|
|
12789
13797
|
}
|
|
12790
13798
|
async handleAttachment(sessionId, content) {
|
|
@@ -12793,7 +13801,7 @@ ${lines.join("\n")}`;
|
|
|
12793
13801
|
if (!content.attachment) return;
|
|
12794
13802
|
const { attachment } = content;
|
|
12795
13803
|
if (attachment.size > 50 * 1024 * 1024) {
|
|
12796
|
-
|
|
13804
|
+
log38.warn(
|
|
12797
13805
|
{
|
|
12798
13806
|
sessionId,
|
|
12799
13807
|
fileName: attachment.fileName,
|
|
@@ -12837,7 +13845,7 @@ ${lines.join("\n")}`;
|
|
|
12837
13845
|
);
|
|
12838
13846
|
}
|
|
12839
13847
|
} catch (err) {
|
|
12840
|
-
|
|
13848
|
+
log38.error(
|
|
12841
13849
|
{ err, sessionId, fileName: attachment.fileName },
|
|
12842
13850
|
"Failed to send attachment"
|
|
12843
13851
|
);
|
|
@@ -12962,7 +13970,7 @@ Task completed.
|
|
|
12962
13970
|
*/
|
|
12963
13971
|
async sendPermissionRequest(sessionId, request) {
|
|
12964
13972
|
this.getTracer(sessionId)?.log("telegram", { action: "permission:send", sessionId, requestId: request.id, description: request.description });
|
|
12965
|
-
|
|
13973
|
+
log38.info({ sessionId, requestId: request.id }, "Permission request sent");
|
|
12966
13974
|
const session = this.core.sessionManager.getSession(sessionId);
|
|
12967
13975
|
if (!session) return;
|
|
12968
13976
|
await this.sendQueue.enqueue(
|
|
@@ -12977,7 +13985,7 @@ Task completed.
|
|
|
12977
13985
|
async sendNotification(notification) {
|
|
12978
13986
|
this.getTracer(notification.sessionId)?.log("telegram", { action: "notification:send", sessionId: notification.sessionId, type: notification.type });
|
|
12979
13987
|
if (notification.sessionId === this.core.assistantManager?.get("telegram")?.id) return;
|
|
12980
|
-
|
|
13988
|
+
log38.info(
|
|
12981
13989
|
{ sessionId: notification.sessionId, type: notification.type },
|
|
12982
13990
|
"Notification sent"
|
|
12983
13991
|
);
|
|
@@ -13023,14 +14031,14 @@ Task completed.
|
|
|
13023
14031
|
*/
|
|
13024
14032
|
async createSessionThread(sessionId, name) {
|
|
13025
14033
|
this.getTracer(sessionId)?.log("telegram", { action: "thread:create", sessionId, name });
|
|
13026
|
-
|
|
14034
|
+
log38.info({ sessionId, name }, "Session topic created");
|
|
13027
14035
|
const threadId = await createSessionTopic(this.bot, this.telegramConfig.chatId, name);
|
|
13028
14036
|
this.bot.api.sendMessage(
|
|
13029
14037
|
this.telegramConfig.chatId,
|
|
13030
14038
|
"\u23F3 Setting up session, please wait...",
|
|
13031
14039
|
{ message_thread_id: threadId, parse_mode: "HTML" }
|
|
13032
14040
|
).catch((err) => {
|
|
13033
|
-
|
|
14041
|
+
log38.warn({ err, sessionId, threadId }, "Failed to send setup message to new topic");
|
|
13034
14042
|
});
|
|
13035
14043
|
return String(threadId);
|
|
13036
14044
|
}
|
|
@@ -13044,7 +14052,7 @@ Task completed.
|
|
|
13044
14052
|
if (!session) return;
|
|
13045
14053
|
const threadId = Number(session.threadId);
|
|
13046
14054
|
if (!threadId) {
|
|
13047
|
-
|
|
14055
|
+
log38.debug({ sessionId, newName }, "Cannot rename thread \u2014 threadId not set yet");
|
|
13048
14056
|
return;
|
|
13049
14057
|
}
|
|
13050
14058
|
await renameSessionTopic(
|
|
@@ -13064,7 +14072,7 @@ Task completed.
|
|
|
13064
14072
|
try {
|
|
13065
14073
|
await this.bot.api.deleteForumTopic(this.telegramConfig.chatId, topicId);
|
|
13066
14074
|
} catch (err) {
|
|
13067
|
-
|
|
14075
|
+
log38.warn(
|
|
13068
14076
|
{ err, sessionId, topicId },
|
|
13069
14077
|
"Failed to delete forum topic (may already be deleted)"
|
|
13070
14078
|
);
|
|
@@ -13113,7 +14121,7 @@ Task completed.
|
|
|
13113
14121
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
13114
14122
|
return { buffer, filePath: file.file_path };
|
|
13115
14123
|
} catch (err) {
|
|
13116
|
-
|
|
14124
|
+
log38.error({ err }, "Failed to download file from Telegram");
|
|
13117
14125
|
return null;
|
|
13118
14126
|
}
|
|
13119
14127
|
}
|
|
@@ -13134,7 +14142,7 @@ Task completed.
|
|
|
13134
14142
|
try {
|
|
13135
14143
|
buffer = await this.fileService.convertOggToWav(buffer);
|
|
13136
14144
|
} catch (err) {
|
|
13137
|
-
|
|
14145
|
+
log38.warn({ err }, "OGG\u2192WAV conversion failed, saving original OGG");
|
|
13138
14146
|
fileName = "voice.ogg";
|
|
13139
14147
|
mimeType = "audio/ogg";
|
|
13140
14148
|
originalFilePath = void 0;
|
|
@@ -13169,7 +14177,7 @@ Task completed.
|
|
|
13169
14177
|
userId: String(userId),
|
|
13170
14178
|
text: text3,
|
|
13171
14179
|
attachments: [att]
|
|
13172
|
-
}).catch((err) =>
|
|
14180
|
+
}).catch((err) => log38.error({ err }, "handleMessage error"));
|
|
13173
14181
|
}
|
|
13174
14182
|
/**
|
|
13175
14183
|
* Remove skill slash commands from the Telegram bot command list for a session.
|
|
@@ -15275,7 +16283,7 @@ var TTS_BLOCK_REGEX = /\[TTS\]([\s\S]*?)\[\/TTS\]/;
|
|
|
15275
16283
|
var TTS_MAX_LENGTH = 5e3;
|
|
15276
16284
|
var TTS_TIMEOUT_MS = 3e4;
|
|
15277
16285
|
var VALID_TRANSITIONS = {
|
|
15278
|
-
initializing: /* @__PURE__ */ new Set(["active", "error"]),
|
|
16286
|
+
initializing: /* @__PURE__ */ new Set(["active", "error", "cancelled"]),
|
|
15279
16287
|
active: /* @__PURE__ */ new Set(["error", "finished", "cancelled"]),
|
|
15280
16288
|
error: /* @__PURE__ */ new Set(["active", "cancelled"]),
|
|
15281
16289
|
cancelled: /* @__PURE__ */ new Set(["active"]),
|
|
@@ -15337,6 +16345,9 @@ var Session = class extends TypedEmitter {
|
|
|
15337
16345
|
_abortedTurnIds = /* @__PURE__ */ new Set();
|
|
15338
16346
|
/** Last completed turnId — used by abortPrompt() to retroactively mark a just-finished turn as interrupted */
|
|
15339
16347
|
_lastCompletedTurnId = null;
|
|
16348
|
+
/** Idempotent teardown checkpoints allow cleanup retry after a partial failure. */
|
|
16349
|
+
_destroyedAgent = false;
|
|
16350
|
+
_closedLogger = false;
|
|
15340
16351
|
_autoApprovedCommands = [];
|
|
15341
16352
|
get autoApprovedCommands() {
|
|
15342
16353
|
return this._autoApprovedCommands;
|
|
@@ -15415,7 +16426,7 @@ var Session = class extends TypedEmitter {
|
|
|
15415
16426
|
this.transition("finished");
|
|
15416
16427
|
this.emit(SessionEv.SESSION_END, reason ?? "completed");
|
|
15417
16428
|
}
|
|
15418
|
-
/** Transition to cancelled — from active or error
|
|
16429
|
+
/** Transition to cancelled — from initializing, active, or error. */
|
|
15419
16430
|
markCancelled() {
|
|
15420
16431
|
this.transition("cancelled");
|
|
15421
16432
|
}
|
|
@@ -15932,8 +16943,14 @@ ${result.text}` : result.text;
|
|
|
15932
16943
|
this.permissionGate.reject("Session destroyed");
|
|
15933
16944
|
}
|
|
15934
16945
|
this.queue.clear();
|
|
15935
|
-
|
|
15936
|
-
|
|
16946
|
+
if (!this._destroyedAgent) {
|
|
16947
|
+
await this.agentInstance.destroy();
|
|
16948
|
+
this._destroyedAgent = true;
|
|
16949
|
+
}
|
|
16950
|
+
if (!this._closedLogger) {
|
|
16951
|
+
await closeSessionLogger(this.log);
|
|
16952
|
+
this._closedLogger = true;
|
|
16953
|
+
}
|
|
15937
16954
|
}
|
|
15938
16955
|
};
|
|
15939
16956
|
|
|
@@ -16451,6 +17468,9 @@ var MessageTransformer = class {
|
|
|
16451
17468
|
|
|
16452
17469
|
// src/core/sessions/session-manager.ts
|
|
16453
17470
|
init_events();
|
|
17471
|
+
init_log();
|
|
17472
|
+
init_network_redaction();
|
|
17473
|
+
var log7 = createChildLogger({ module: "session-manager" });
|
|
16454
17474
|
var SessionManager = class {
|
|
16455
17475
|
sessions = /* @__PURE__ */ new Map();
|
|
16456
17476
|
store;
|
|
@@ -16460,6 +17480,7 @@ var SessionManager = class {
|
|
|
16460
17480
|
// Prevents SessionBridge STATUS_CHANGE listeners from overwriting the flushed state
|
|
16461
17481
|
// with transient error status from in-flight prompts that fail after shutdown.
|
|
16462
17482
|
_shutdownComplete = false;
|
|
17483
|
+
cancellationOps = /* @__PURE__ */ new Map();
|
|
16463
17484
|
/**
|
|
16464
17485
|
* Inject the EventBus after construction. Deferred because EventBus is created
|
|
16465
17486
|
* after SessionManager during bootstrap, so it cannot be passed to the constructor.
|
|
@@ -16560,30 +17581,76 @@ var SessionManager = class {
|
|
|
16560
17581
|
getSessionRecord(sessionId) {
|
|
16561
17582
|
return this.store?.get(sessionId);
|
|
16562
17583
|
}
|
|
16563
|
-
/**
|
|
17584
|
+
/**
|
|
17585
|
+
* Cancel a session exactly once. Concurrent callers share one operation;
|
|
17586
|
+
* terminal sessions return an idempotent result instead of throwing.
|
|
17587
|
+
*/
|
|
16564
17588
|
async cancelSession(sessionId) {
|
|
17589
|
+
const existing = this.cancellationOps.get(sessionId);
|
|
17590
|
+
if (existing) return existing;
|
|
17591
|
+
const operation = this.performCancelSession(sessionId);
|
|
17592
|
+
this.cancellationOps.set(sessionId, operation);
|
|
17593
|
+
try {
|
|
17594
|
+
return await operation;
|
|
17595
|
+
} finally {
|
|
17596
|
+
if (this.cancellationOps.get(sessionId) === operation) this.cancellationOps.delete(sessionId);
|
|
17597
|
+
}
|
|
17598
|
+
}
|
|
17599
|
+
async performCancelSession(sessionId) {
|
|
16565
17600
|
const session = this.sessions.get(sessionId);
|
|
16566
|
-
|
|
16567
|
-
|
|
16568
|
-
|
|
16569
|
-
}
|
|
16570
|
-
|
|
16571
|
-
|
|
16572
|
-
session.markCancelled();
|
|
16573
|
-
}
|
|
16574
|
-
await session.destroy();
|
|
16575
|
-
this.sessions.delete(sessionId);
|
|
17601
|
+
const recordBefore = this.store?.get(sessionId);
|
|
17602
|
+
const previousStatus = session?.status ?? recordBefore?.status;
|
|
17603
|
+
if (!previousStatus) {
|
|
17604
|
+
const error = new Error(`Session "${sessionId}" not found`);
|
|
17605
|
+
error.code = "SESSION_NOT_FOUND";
|
|
17606
|
+
throw error;
|
|
16576
17607
|
}
|
|
16577
|
-
|
|
17608
|
+
const alreadyTerminal = previousStatus === "cancelled" || previousStatus === "finished";
|
|
17609
|
+
const finalStatus = previousStatus === "finished" ? "finished" : "cancelled";
|
|
17610
|
+
if (this.store && finalStatus === "cancelled") {
|
|
16578
17611
|
const record = this.store.get(sessionId);
|
|
16579
|
-
if (record && record.status !== "cancelled"
|
|
17612
|
+
if (record && record.status !== "cancelled") {
|
|
16580
17613
|
await this.store.save({ ...record, status: "cancelled" });
|
|
17614
|
+
this.store.flush();
|
|
16581
17615
|
}
|
|
16582
17616
|
}
|
|
16583
|
-
|
|
17617
|
+
let cleanupPending = false;
|
|
17618
|
+
if (session) {
|
|
17619
|
+
if (!alreadyTerminal) {
|
|
17620
|
+
session.markCancelled();
|
|
17621
|
+
try {
|
|
17622
|
+
await session.abortPrompt();
|
|
17623
|
+
} catch (error) {
|
|
17624
|
+
log7.warn(
|
|
17625
|
+
{ sessionId, error: redactNetworkSecrets(error instanceof Error ? error.message : String(error)) },
|
|
17626
|
+
"Session prompt abort failed after cancellation became durable; continuing cleanup"
|
|
17627
|
+
);
|
|
17628
|
+
}
|
|
17629
|
+
}
|
|
17630
|
+
try {
|
|
17631
|
+
await session.destroy();
|
|
17632
|
+
this.sessions.delete(sessionId);
|
|
17633
|
+
} catch (error) {
|
|
17634
|
+
cleanupPending = true;
|
|
17635
|
+
log7.warn(
|
|
17636
|
+
{ sessionId, error: redactNetworkSecrets(error instanceof Error ? error.message : String(error)) },
|
|
17637
|
+
"Session is durably cancelled but agent/logger cleanup failed; repeat cancellation to retry cleanup"
|
|
17638
|
+
);
|
|
17639
|
+
}
|
|
17640
|
+
}
|
|
17641
|
+
if (!cleanupPending && this.middlewareChain) {
|
|
16584
17642
|
this.middlewareChain.execute(Hook.SESSION_AFTER_DESTROY, { sessionId }, async (p) => p).catch(() => {
|
|
16585
17643
|
});
|
|
16586
17644
|
}
|
|
17645
|
+
return {
|
|
17646
|
+
sessionId,
|
|
17647
|
+
cancelled: !alreadyTerminal,
|
|
17648
|
+
previousStatus,
|
|
17649
|
+
status: finalStatus,
|
|
17650
|
+
alreadyTerminal,
|
|
17651
|
+
cleanupPending,
|
|
17652
|
+
...cleanupPending ? { warning: "Session state is cancelled and persisted; process cleanup is pending. Repeat cancellation to retry cleanup." } : {}
|
|
17653
|
+
};
|
|
16587
17654
|
}
|
|
16588
17655
|
/** List live (in-memory) sessions, optionally filtered by channel. Excludes assistant sessions. */
|
|
16589
17656
|
listSessions(channelId) {
|
|
@@ -16752,7 +17819,7 @@ init_bypass_detection();
|
|
|
16752
17819
|
import micromatch from "micromatch";
|
|
16753
17820
|
init_events();
|
|
16754
17821
|
var { isMatch } = micromatch;
|
|
16755
|
-
var
|
|
17822
|
+
var log8 = createChildLogger({ module: "session-bridge" });
|
|
16756
17823
|
var SessionBridge = class {
|
|
16757
17824
|
constructor(session, adapter, deps, adapterId) {
|
|
16758
17825
|
this.session = session;
|
|
@@ -16784,16 +17851,16 @@ var SessionBridge = class {
|
|
|
16784
17851
|
if (!result) return;
|
|
16785
17852
|
this.tracer?.log("core", { step: "dispatch", sessionId, message: result.message });
|
|
16786
17853
|
this.adapter.sendMessage(sessionId, result.message).catch((err) => {
|
|
16787
|
-
|
|
17854
|
+
log8.error({ err, sessionId }, "Failed to send message to adapter");
|
|
16788
17855
|
});
|
|
16789
17856
|
} else {
|
|
16790
17857
|
this.tracer?.log("core", { step: "dispatch", sessionId, message });
|
|
16791
17858
|
this.adapter.sendMessage(sessionId, message).catch((err) => {
|
|
16792
|
-
|
|
17859
|
+
log8.error({ err, sessionId }, "Failed to send message to adapter");
|
|
16793
17860
|
});
|
|
16794
17861
|
}
|
|
16795
17862
|
} catch (err) {
|
|
16796
|
-
|
|
17863
|
+
log8.error({ err, sessionId }, "Error in sendMessage middleware");
|
|
16797
17864
|
}
|
|
16798
17865
|
}
|
|
16799
17866
|
/**
|
|
@@ -16839,7 +17906,7 @@ var SessionBridge = class {
|
|
|
16839
17906
|
try {
|
|
16840
17907
|
await this.adapter.sendPermissionRequest(this.session.id, request);
|
|
16841
17908
|
} catch (err) {
|
|
16842
|
-
|
|
17909
|
+
log8.error({ err, sessionId: this.session.id, adapterId: this.adapterId }, "Failed to send permission request to adapter");
|
|
16843
17910
|
}
|
|
16844
17911
|
});
|
|
16845
17912
|
this.listen(this.session, SessionEv.STATUS_CHANGE, (from, to) => {
|
|
@@ -16924,14 +17991,14 @@ var SessionBridge = class {
|
|
|
16924
17991
|
try {
|
|
16925
17992
|
this.handleAgentEvent(event);
|
|
16926
17993
|
} catch (err) {
|
|
16927
|
-
|
|
17994
|
+
log8.error({ err, sessionId: this.session.id }, "Error handling agent event (middleware fallback)");
|
|
16928
17995
|
}
|
|
16929
17996
|
}
|
|
16930
17997
|
} else {
|
|
16931
17998
|
try {
|
|
16932
17999
|
this.handleAgentEvent(event);
|
|
16933
18000
|
} catch (err) {
|
|
16934
|
-
|
|
18001
|
+
log8.error({ err, sessionId: this.session.id }, "Error handling agent event");
|
|
16935
18002
|
}
|
|
16936
18003
|
}
|
|
16937
18004
|
}
|
|
@@ -16984,40 +18051,40 @@ var SessionBridge = class {
|
|
|
16984
18051
|
break;
|
|
16985
18052
|
case "image_content": {
|
|
16986
18053
|
if (this.deps.fileService) {
|
|
16987
|
-
const
|
|
18054
|
+
const fs38 = this.deps.fileService;
|
|
16988
18055
|
const sid = this.session.id;
|
|
16989
18056
|
const { data, mimeType } = event;
|
|
16990
18057
|
const buffer = Buffer.from(data, "base64");
|
|
16991
|
-
const ext =
|
|
16992
|
-
|
|
18058
|
+
const ext = fs38.extensionFromMime(mimeType);
|
|
18059
|
+
fs38.saveFile(sid, `agent-image${ext}`, buffer, mimeType).then((att) => {
|
|
16993
18060
|
this.sendMessage(sid, {
|
|
16994
18061
|
type: "attachment",
|
|
16995
18062
|
text: "",
|
|
16996
18063
|
attachment: att
|
|
16997
18064
|
});
|
|
16998
|
-
}).catch((err) =>
|
|
18065
|
+
}).catch((err) => log8.error({ err }, "Failed to save agent image"));
|
|
16999
18066
|
}
|
|
17000
18067
|
break;
|
|
17001
18068
|
}
|
|
17002
18069
|
case "audio_content": {
|
|
17003
18070
|
if (this.deps.fileService) {
|
|
17004
|
-
const
|
|
18071
|
+
const fs38 = this.deps.fileService;
|
|
17005
18072
|
const sid = this.session.id;
|
|
17006
18073
|
const { data, mimeType } = event;
|
|
17007
18074
|
const buffer = Buffer.from(data, "base64");
|
|
17008
|
-
const ext =
|
|
17009
|
-
|
|
18075
|
+
const ext = fs38.extensionFromMime(mimeType);
|
|
18076
|
+
fs38.saveFile(sid, `agent-audio${ext}`, buffer, mimeType).then((att) => {
|
|
17010
18077
|
this.sendMessage(sid, {
|
|
17011
18078
|
type: "attachment",
|
|
17012
18079
|
text: "",
|
|
17013
18080
|
attachment: att
|
|
17014
18081
|
});
|
|
17015
|
-
}).catch((err) =>
|
|
18082
|
+
}).catch((err) => log8.error({ err }, "Failed to save agent audio"));
|
|
17016
18083
|
}
|
|
17017
18084
|
break;
|
|
17018
18085
|
}
|
|
17019
18086
|
case "commands_update":
|
|
17020
|
-
|
|
18087
|
+
log8.debug({ commands: event.commands }, "Commands available");
|
|
17021
18088
|
this.adapter.sendSkillCommands?.(this.session.id, event.commands);
|
|
17022
18089
|
break;
|
|
17023
18090
|
case "system_message":
|
|
@@ -17114,7 +18181,7 @@ var SessionBridge = class {
|
|
|
17114
18181
|
if (isAgentBypass || isClientBypass) {
|
|
17115
18182
|
const allowOption = request.options.find((o) => o.isAllow);
|
|
17116
18183
|
if (allowOption) {
|
|
17117
|
-
|
|
18184
|
+
log8.info(
|
|
17118
18185
|
{ sessionId: this.session.id, requestId: request.id, optionId: allowOption.id, agentBypass: !!isAgentBypass, clientBypass: !!isClientBypass },
|
|
17119
18186
|
"Bypass mode: auto-approving permission"
|
|
17120
18187
|
);
|
|
@@ -17127,7 +18194,7 @@ var SessionBridge = class {
|
|
|
17127
18194
|
if (isMatch(normalized, patterns, { dot: true })) {
|
|
17128
18195
|
const allowOption = request.options.find((o) => o.isAllow);
|
|
17129
18196
|
if (allowOption) {
|
|
17130
|
-
|
|
18197
|
+
log8.info(
|
|
17131
18198
|
{ sessionId: this.session.id, requestId: request.id, command: request.description },
|
|
17132
18199
|
"autoApprovedCommands: auto-approving matching command"
|
|
17133
18200
|
);
|
|
@@ -17155,7 +18222,7 @@ var SessionBridge = class {
|
|
|
17155
18222
|
// src/core/sessions/session-factory.ts
|
|
17156
18223
|
init_log();
|
|
17157
18224
|
init_events();
|
|
17158
|
-
var
|
|
18225
|
+
var log9 = createChildLogger({ module: "session-factory" });
|
|
17159
18226
|
function toSetConfigOptionValue(option) {
|
|
17160
18227
|
return option.type === "boolean" ? { type: "boolean", value: option.currentValue } : { type: "select", value: option.currentValue };
|
|
17161
18228
|
}
|
|
@@ -17229,7 +18296,7 @@ var SessionFactory = class {
|
|
|
17229
18296
|
configAllowedPaths
|
|
17230
18297
|
);
|
|
17231
18298
|
} catch (resumeErr) {
|
|
17232
|
-
|
|
18299
|
+
log9.warn(
|
|
17233
18300
|
{ agentName: createParams.agentName, resumeErr },
|
|
17234
18301
|
"Agent session resume failed, falling back to fresh spawn"
|
|
17235
18302
|
);
|
|
@@ -17382,10 +18449,10 @@ var SessionFactory = class {
|
|
|
17382
18449
|
}
|
|
17383
18450
|
}
|
|
17384
18451
|
}
|
|
17385
|
-
|
|
18452
|
+
log9.info({ sessionId }, "Lazy resume by ID successful");
|
|
17386
18453
|
return session;
|
|
17387
18454
|
} catch (err) {
|
|
17388
|
-
|
|
18455
|
+
log9.error({ err, sessionId }, "Lazy resume by ID failed");
|
|
17389
18456
|
return null;
|
|
17390
18457
|
} finally {
|
|
17391
18458
|
this.resumeLocks.delete(sessionId);
|
|
@@ -17410,19 +18477,19 @@ var SessionFactory = class {
|
|
|
17410
18477
|
(p) => String(p.topicId) === threadId || String(p.threadId ?? "") === threadId
|
|
17411
18478
|
);
|
|
17412
18479
|
if (!record) {
|
|
17413
|
-
|
|
18480
|
+
log9.debug({ threadId, channelId }, "No session record found for thread");
|
|
17414
18481
|
return null;
|
|
17415
18482
|
}
|
|
17416
18483
|
if (record.isAssistant) return null;
|
|
17417
18484
|
if (record.status === "error" || record.status === "cancelled") {
|
|
17418
|
-
|
|
18485
|
+
log9.warn(
|
|
17419
18486
|
{ threadId, sessionId: record.sessionId, status: record.status },
|
|
17420
18487
|
"Session record found but skipped (status: %s) \u2014 use /new to start a fresh session",
|
|
17421
18488
|
record.status
|
|
17422
18489
|
);
|
|
17423
18490
|
return null;
|
|
17424
18491
|
}
|
|
17425
|
-
|
|
18492
|
+
log9.info({ threadId, sessionId: record.sessionId, status: record.status }, "Lazy resume: found record, attempting resume");
|
|
17426
18493
|
const resumePromise = (async () => {
|
|
17427
18494
|
try {
|
|
17428
18495
|
const session = await this.createFullSession({
|
|
@@ -17474,7 +18541,7 @@ var SessionFactory = class {
|
|
|
17474
18541
|
}
|
|
17475
18542
|
const resumeFalledBack = record.agentSessionId && session.agentSessionId !== record.agentSessionId;
|
|
17476
18543
|
if (resumeFalledBack) {
|
|
17477
|
-
|
|
18544
|
+
log9.info({ sessionId: session.id }, "Resume fell back to fresh spawn \u2014 injecting conversation history");
|
|
17478
18545
|
const contextManager = this.getContextManager?.();
|
|
17479
18546
|
if (contextManager) {
|
|
17480
18547
|
try {
|
|
@@ -17491,10 +18558,10 @@ var SessionFactory = class {
|
|
|
17491
18558
|
}
|
|
17492
18559
|
}
|
|
17493
18560
|
}
|
|
17494
|
-
|
|
18561
|
+
log9.info({ sessionId: session.id, threadId }, "Lazy resume successful");
|
|
17495
18562
|
return session;
|
|
17496
18563
|
} catch (err) {
|
|
17497
|
-
|
|
18564
|
+
log9.error({ err, record }, "Lazy resume failed");
|
|
17498
18565
|
const adapter = this.adapters?.get(channelId);
|
|
17499
18566
|
if (adapter) {
|
|
17500
18567
|
try {
|
|
@@ -17520,7 +18587,7 @@ var SessionFactory = class {
|
|
|
17520
18587
|
}
|
|
17521
18588
|
const config = this.configManager.get();
|
|
17522
18589
|
const resolvedAgent = agentName || config.defaultAgent;
|
|
17523
|
-
|
|
18590
|
+
log9.info({ channelId, agentName: resolvedAgent }, "New session request");
|
|
17524
18591
|
const agentDef = this.agentCatalog.resolve(resolvedAgent);
|
|
17525
18592
|
const resolvedWorkspace = this.configManager.resolveWorkspace(
|
|
17526
18593
|
workspacePath || agentDef?.workingDirectory
|
|
@@ -17570,7 +18637,7 @@ var SessionFactory = class {
|
|
|
17570
18637
|
params.contextOptions
|
|
17571
18638
|
);
|
|
17572
18639
|
} catch (err) {
|
|
17573
|
-
|
|
18640
|
+
log9.warn({ err }, "Context building failed, proceeding without context");
|
|
17574
18641
|
}
|
|
17575
18642
|
}
|
|
17576
18643
|
const session = await this.createFullSession({
|
|
@@ -17625,7 +18692,7 @@ import { nanoid as nanoid3 } from "nanoid";
|
|
|
17625
18692
|
init_log();
|
|
17626
18693
|
import fs9 from "fs";
|
|
17627
18694
|
import path8 from "path";
|
|
17628
|
-
var
|
|
18695
|
+
var log10 = createChildLogger({ module: "session-store" });
|
|
17629
18696
|
var DEBOUNCE_MS = 2e3;
|
|
17630
18697
|
var JsonFileSessionStore = class {
|
|
17631
18698
|
records = /* @__PURE__ */ new Map();
|
|
@@ -17739,7 +18806,7 @@ var JsonFileSessionStore = class {
|
|
|
17739
18806
|
fs9.readFileSync(this.filePath, "utf-8")
|
|
17740
18807
|
);
|
|
17741
18808
|
if (raw.version !== 1) {
|
|
17742
|
-
|
|
18809
|
+
log10.warn(
|
|
17743
18810
|
{ version: raw.version },
|
|
17744
18811
|
"Unknown session store version, skipping load"
|
|
17745
18812
|
);
|
|
@@ -17748,9 +18815,9 @@ var JsonFileSessionStore = class {
|
|
|
17748
18815
|
for (const [id, record] of Object.entries(raw.sessions)) {
|
|
17749
18816
|
this.records.set(id, this.migrateRecord(record));
|
|
17750
18817
|
}
|
|
17751
|
-
|
|
18818
|
+
log10.debug({ count: this.records.size }, "Loaded session records");
|
|
17752
18819
|
} catch (err) {
|
|
17753
|
-
|
|
18820
|
+
log10.error({ err }, "Failed to load session store, backing up corrupt file");
|
|
17754
18821
|
try {
|
|
17755
18822
|
fs9.renameSync(this.filePath, `${this.filePath}.bak`);
|
|
17756
18823
|
} catch {
|
|
@@ -17793,7 +18860,7 @@ var JsonFileSessionStore = class {
|
|
|
17793
18860
|
}
|
|
17794
18861
|
}
|
|
17795
18862
|
if (removed > 0) {
|
|
17796
|
-
|
|
18863
|
+
log10.info({ removed }, "Cleaned up expired session records");
|
|
17797
18864
|
this.scheduleDiskWrite();
|
|
17798
18865
|
}
|
|
17799
18866
|
}
|
|
@@ -17812,7 +18879,7 @@ init_agent_registry();
|
|
|
17812
18879
|
init_agent_registry();
|
|
17813
18880
|
init_log();
|
|
17814
18881
|
init_events();
|
|
17815
|
-
var
|
|
18882
|
+
var log11 = createChildLogger({ module: "agent-switch" });
|
|
17816
18883
|
var AgentSwitchHandler = class {
|
|
17817
18884
|
constructor(deps) {
|
|
17818
18885
|
this.deps = deps;
|
|
@@ -17893,7 +18960,7 @@ var AgentSwitchHandler = class {
|
|
|
17893
18960
|
resumed = true;
|
|
17894
18961
|
return instance2;
|
|
17895
18962
|
} catch {
|
|
17896
|
-
|
|
18963
|
+
log11.warn({ sessionId, toAgent }, "Resume failed, falling back to new agent with context injection");
|
|
17897
18964
|
}
|
|
17898
18965
|
}
|
|
17899
18966
|
const instance = await agentManager.spawn(toAgent, session.workingDirectory, configAllowedPaths);
|
|
@@ -17962,10 +19029,10 @@ var AgentSwitchHandler = class {
|
|
|
17962
19029
|
createBridge(session, adapter, adapterId).connect();
|
|
17963
19030
|
}
|
|
17964
19031
|
}
|
|
17965
|
-
|
|
19032
|
+
log11.warn({ sessionId, fromAgent, toAgent, err }, "Agent switch failed, rolled back to previous agent");
|
|
17966
19033
|
} catch (rollbackErr) {
|
|
17967
19034
|
session.fail(`Switch failed and rollback failed: ${rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr)}`);
|
|
17968
|
-
|
|
19035
|
+
log11.error({ sessionId, fromAgent, toAgent, err, rollbackErr }, "Agent switch failed and rollback also failed");
|
|
17969
19036
|
}
|
|
17970
19037
|
throw err;
|
|
17971
19038
|
}
|
|
@@ -17975,7 +19042,7 @@ var AgentSwitchHandler = class {
|
|
|
17975
19042
|
if (adapter) {
|
|
17976
19043
|
createBridge(session, adapter, adapterId).connect();
|
|
17977
19044
|
} else {
|
|
17978
|
-
|
|
19045
|
+
log11.warn({ sessionId, adapterId }, "Adapter not available during switch reconnect, skipping bridge");
|
|
17979
19046
|
}
|
|
17980
19047
|
}
|
|
17981
19048
|
}
|
|
@@ -18003,7 +19070,7 @@ init_agent_dependencies();
|
|
|
18003
19070
|
init_log();
|
|
18004
19071
|
import * as fs12 from "fs";
|
|
18005
19072
|
import * as path11 from "path";
|
|
18006
|
-
var
|
|
19073
|
+
var log13 = createChildLogger({ module: "agent-catalog" });
|
|
18007
19074
|
var REGISTRY_URL = "https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json";
|
|
18008
19075
|
var DEFAULT_TTL_HOURS = 24;
|
|
18009
19076
|
var AgentCatalog = class {
|
|
@@ -18037,7 +19104,7 @@ var AgentCatalog = class {
|
|
|
18037
19104
|
/** Fetch the latest agent registry from the CDN and update the local cache. */
|
|
18038
19105
|
async fetchRegistry() {
|
|
18039
19106
|
try {
|
|
18040
|
-
|
|
19107
|
+
log13.info("Fetching agent registry from CDN...");
|
|
18041
19108
|
const response = await this.scopedFetch(this.registryUrl);
|
|
18042
19109
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
18043
19110
|
const data = await response.json();
|
|
@@ -18050,9 +19117,9 @@ var AgentCatalog = class {
|
|
|
18050
19117
|
fs12.mkdirSync(path11.dirname(this.cachePath), { recursive: true });
|
|
18051
19118
|
fs12.writeFileSync(this.cachePath, JSON.stringify(cache, null, 2), { mode: 384 });
|
|
18052
19119
|
this.enrichInstalledFromRegistry();
|
|
18053
|
-
|
|
19120
|
+
log13.info({ count: this.registryAgents.length }, "Registry updated");
|
|
18054
19121
|
} catch (err) {
|
|
18055
|
-
|
|
19122
|
+
log13.warn({ err }, "Failed to fetch registry, using cached data");
|
|
18056
19123
|
}
|
|
18057
19124
|
}
|
|
18058
19125
|
/** Re-fetch registry only if the local cache has expired (24-hour TTL). */
|
|
@@ -18260,7 +19327,7 @@ var AgentCatalog = class {
|
|
|
18260
19327
|
}
|
|
18261
19328
|
}
|
|
18262
19329
|
if (changed) {
|
|
18263
|
-
|
|
19330
|
+
log13.info("Enriched installed agents with registry data");
|
|
18264
19331
|
}
|
|
18265
19332
|
}
|
|
18266
19333
|
isCacheStale() {
|
|
@@ -18280,11 +19347,11 @@ var AgentCatalog = class {
|
|
|
18280
19347
|
const raw = JSON.parse(fs12.readFileSync(this.cachePath, "utf-8"));
|
|
18281
19348
|
if (raw.data?.agents) {
|
|
18282
19349
|
this.registryAgents = raw.data.agents;
|
|
18283
|
-
|
|
19350
|
+
log13.debug({ count: this.registryAgents.length }, "Loaded registry from cache");
|
|
18284
19351
|
return;
|
|
18285
19352
|
}
|
|
18286
19353
|
} catch {
|
|
18287
|
-
|
|
19354
|
+
log13.warn("Failed to load registry cache");
|
|
18288
19355
|
}
|
|
18289
19356
|
}
|
|
18290
19357
|
try {
|
|
@@ -18297,13 +19364,13 @@ var AgentCatalog = class {
|
|
|
18297
19364
|
if (fs12.existsSync(candidate)) {
|
|
18298
19365
|
const raw = JSON.parse(fs12.readFileSync(candidate, "utf-8"));
|
|
18299
19366
|
this.registryAgents = raw.agents ?? [];
|
|
18300
|
-
|
|
19367
|
+
log13.debug({ count: this.registryAgents.length }, "Loaded registry from bundled snapshot");
|
|
18301
19368
|
return;
|
|
18302
19369
|
}
|
|
18303
19370
|
}
|
|
18304
|
-
|
|
19371
|
+
log13.warn("No registry data available (no cache, no snapshot)");
|
|
18305
19372
|
} catch {
|
|
18306
|
-
|
|
19373
|
+
log13.warn("Failed to load bundled registry snapshot");
|
|
18307
19374
|
}
|
|
18308
19375
|
}
|
|
18309
19376
|
};
|
|
@@ -18332,7 +19399,7 @@ init_log();
|
|
|
18332
19399
|
import * as fs13 from "fs";
|
|
18333
19400
|
import * as path12 from "path";
|
|
18334
19401
|
import { z as z2 } from "zod";
|
|
18335
|
-
var
|
|
19402
|
+
var log14 = createChildLogger({ module: "agent-store" });
|
|
18336
19403
|
var InstalledAgentSchema = z2.object({
|
|
18337
19404
|
registryId: z2.string().nullable(),
|
|
18338
19405
|
name: z2.string(),
|
|
@@ -18368,11 +19435,11 @@ var AgentStore = class {
|
|
|
18368
19435
|
if (result.success) {
|
|
18369
19436
|
this.data = result.data;
|
|
18370
19437
|
} else {
|
|
18371
|
-
|
|
19438
|
+
log14.warn({ errors: result.error.issues }, "Invalid agents.json, starting fresh");
|
|
18372
19439
|
this.data = { version: 1, installed: {} };
|
|
18373
19440
|
}
|
|
18374
19441
|
} catch (err) {
|
|
18375
|
-
|
|
19442
|
+
log14.warn({ err }, "Failed to read agents.json, starting fresh");
|
|
18376
19443
|
this.data = { version: 1, installed: {} };
|
|
18377
19444
|
}
|
|
18378
19445
|
}
|
|
@@ -18827,7 +19894,7 @@ function createPluginContext(opts) {
|
|
|
18827
19894
|
}
|
|
18828
19895
|
};
|
|
18829
19896
|
const baseLog = opts.log ?? noopLog;
|
|
18830
|
-
const
|
|
19897
|
+
const log39 = typeof baseLog.child === "function" ? baseLog.child({ plugin: pluginName }) : baseLog;
|
|
18831
19898
|
const storageImpl = new PluginStorageImpl(storagePath);
|
|
18832
19899
|
const storage = {
|
|
18833
19900
|
async get(key) {
|
|
@@ -18897,7 +19964,7 @@ function createPluginContext(opts) {
|
|
|
18897
19964
|
const ctx = {
|
|
18898
19965
|
pluginName,
|
|
18899
19966
|
pluginConfig,
|
|
18900
|
-
log:
|
|
19967
|
+
log: log39,
|
|
18901
19968
|
storage,
|
|
18902
19969
|
on(event, handler) {
|
|
18903
19970
|
requirePermission(permissions, "events:read", "on()");
|
|
@@ -18932,7 +19999,7 @@ function createPluginContext(opts) {
|
|
|
18932
19999
|
const registry = serviceRegistry.get("command-registry");
|
|
18933
20000
|
if (registry && typeof registry.register === "function") {
|
|
18934
20001
|
registry.register(def, pluginName);
|
|
18935
|
-
|
|
20002
|
+
log39.debug(`Command '/${def.name}' registered`);
|
|
18936
20003
|
}
|
|
18937
20004
|
},
|
|
18938
20005
|
async sendMessage(_sessionId, _content) {
|
|
@@ -18995,7 +20062,7 @@ function createPluginContext(opts) {
|
|
|
18995
20062
|
const registry = serviceRegistry.get("field-registry");
|
|
18996
20063
|
if (registry && typeof registry.register === "function") {
|
|
18997
20064
|
registry.register(pluginName, fields);
|
|
18998
|
-
|
|
20065
|
+
log39.debug(`Registered ${fields.length} editable field(s) for ${pluginName}`);
|
|
18999
20066
|
}
|
|
19000
20067
|
},
|
|
19001
20068
|
get sessions() {
|
|
@@ -19335,7 +20402,7 @@ var LifecycleManager = class {
|
|
|
19335
20402
|
|
|
19336
20403
|
// src/core/menu-registry.ts
|
|
19337
20404
|
init_log();
|
|
19338
|
-
var
|
|
20405
|
+
var log15 = createChildLogger({ module: "menu-registry" });
|
|
19339
20406
|
var MenuRegistry = class {
|
|
19340
20407
|
items = /* @__PURE__ */ new Map();
|
|
19341
20408
|
/** Register or replace a menu item by its unique ID. */
|
|
@@ -19357,7 +20424,7 @@ var MenuRegistry = class {
|
|
|
19357
20424
|
try {
|
|
19358
20425
|
return item.visible();
|
|
19359
20426
|
} catch (err) {
|
|
19360
|
-
|
|
20427
|
+
log15.warn({ err, id: item.id }, "MenuItem visible() threw, hiding item");
|
|
19361
20428
|
return false;
|
|
19362
20429
|
}
|
|
19363
20430
|
}).sort((a, b) => a.priority - b.priority);
|
|
@@ -19425,7 +20492,7 @@ openacp api new claude ~/project
|
|
|
19425
20492
|
}
|
|
19426
20493
|
|
|
19427
20494
|
// src/core/assistant/assistant-registry.ts
|
|
19428
|
-
var
|
|
20495
|
+
var log16 = createChildLogger({ module: "assistant-registry" });
|
|
19429
20496
|
var AssistantRegistry = class {
|
|
19430
20497
|
sections = /* @__PURE__ */ new Map();
|
|
19431
20498
|
_instanceRoot = "";
|
|
@@ -19436,7 +20503,7 @@ var AssistantRegistry = class {
|
|
|
19436
20503
|
/** Register a prompt section. Overwrites any existing section with the same id. */
|
|
19437
20504
|
register(section) {
|
|
19438
20505
|
if (this.sections.has(section.id)) {
|
|
19439
|
-
|
|
20506
|
+
log16.warn({ id: section.id }, "Assistant section overwritten");
|
|
19440
20507
|
}
|
|
19441
20508
|
this.sections.set(section.id, section);
|
|
19442
20509
|
}
|
|
@@ -19472,7 +20539,7 @@ ${context}`);
|
|
|
19472
20539
|
parts.push("```bash\n" + cmds + "\n```");
|
|
19473
20540
|
}
|
|
19474
20541
|
} catch (err) {
|
|
19475
|
-
|
|
20542
|
+
log16.warn({ err, sectionId: section.id }, "Assistant section buildContext() failed, skipping");
|
|
19476
20543
|
}
|
|
19477
20544
|
}
|
|
19478
20545
|
parts.push(buildAssistantGuidelines(this._instanceRoot));
|
|
@@ -19482,7 +20549,7 @@ ${context}`);
|
|
|
19482
20549
|
|
|
19483
20550
|
// src/core/assistant/assistant-manager.ts
|
|
19484
20551
|
init_log();
|
|
19485
|
-
var
|
|
20552
|
+
var log17 = createChildLogger({ module: "assistant-manager" });
|
|
19486
20553
|
var AssistantManager = class {
|
|
19487
20554
|
constructor(core, registry) {
|
|
19488
20555
|
this.core = core;
|
|
@@ -19513,7 +20580,7 @@ var AssistantManager = class {
|
|
|
19513
20580
|
this.sessions.set(channelId, session);
|
|
19514
20581
|
const systemPrompt = this.registry.buildSystemPrompt(channelId);
|
|
19515
20582
|
this.pendingSystemPrompts.set(channelId, systemPrompt);
|
|
19516
|
-
|
|
20583
|
+
log17.info(
|
|
19517
20584
|
{ sessionId: session.id, channelId, reused: !!existing },
|
|
19518
20585
|
existing ? "Assistant session reused (system prompt deferred)" : "Assistant spawned (system prompt deferred)"
|
|
19519
20586
|
);
|
|
@@ -19704,6 +20771,13 @@ function registerCoreMenuItems(registry) {
|
|
|
19704
20771
|
group: "config",
|
|
19705
20772
|
action: { type: "command", command: "/integrate" }
|
|
19706
20773
|
});
|
|
20774
|
+
registry.register({
|
|
20775
|
+
id: "core:proxy",
|
|
20776
|
+
label: "\u{1F310} Proxy Routing",
|
|
20777
|
+
priority: 32,
|
|
20778
|
+
group: "config",
|
|
20779
|
+
action: { type: "command", command: "/proxy" }
|
|
20780
|
+
});
|
|
19707
20781
|
registry.register({
|
|
19708
20782
|
id: "core:restart",
|
|
19709
20783
|
label: "\u{1F504} Restart",
|
|
@@ -19739,7 +20813,7 @@ init_log();
|
|
|
19739
20813
|
init_native_stt();
|
|
19740
20814
|
init_events();
|
|
19741
20815
|
init_proxy_service();
|
|
19742
|
-
var
|
|
20816
|
+
var log18 = createChildLogger({ module: "core" });
|
|
19743
20817
|
var OpenACPCore = class {
|
|
19744
20818
|
configManager;
|
|
19745
20819
|
agentCatalog;
|
|
@@ -19822,7 +20896,7 @@ var OpenACPCore = class {
|
|
|
19822
20896
|
this.proxyService.onRouteChanged(async (scope) => {
|
|
19823
20897
|
if (scope === "global" || scope === "agents.default" || scope.startsWith("agents.")) {
|
|
19824
20898
|
await this.agentManager.destroyWarm();
|
|
19825
|
-
|
|
20899
|
+
log18.info({ scope }, "Proxy route changed; idle agent warm pool invalidated");
|
|
19826
20900
|
}
|
|
19827
20901
|
});
|
|
19828
20902
|
const storePath = ctx.paths.sessions;
|
|
@@ -19884,7 +20958,7 @@ var OpenACPCore = class {
|
|
|
19884
20958
|
if (configPath === "logging.level" && typeof value === "string") {
|
|
19885
20959
|
const { setLogLevel: setLogLevel2 } = await Promise.resolve().then(() => (init_log(), log_exports));
|
|
19886
20960
|
setLogLevel2(value);
|
|
19887
|
-
|
|
20961
|
+
log18.info({ level: value }, "Log level changed at runtime");
|
|
19888
20962
|
}
|
|
19889
20963
|
if (configPath.startsWith("speech.")) {
|
|
19890
20964
|
const speechSvc = this.lifecycleManager.serviceRegistry.get("speech");
|
|
@@ -19894,7 +20968,7 @@ var OpenACPCore = class {
|
|
|
19894
20968
|
const pluginCfg = await settingsMgr.loadSettings("@openacp/speech");
|
|
19895
20969
|
const newSpeechConfig = buildSpeechServiceConfig(pluginCfg);
|
|
19896
20970
|
speechSvc.refreshProviders(newSpeechConfig);
|
|
19897
|
-
|
|
20971
|
+
log18.info("Speech service config updated at runtime (from plugin settings)");
|
|
19898
20972
|
}
|
|
19899
20973
|
}
|
|
19900
20974
|
}
|
|
@@ -19935,14 +21009,14 @@ var OpenACPCore = class {
|
|
|
19935
21009
|
*/
|
|
19936
21010
|
async start() {
|
|
19937
21011
|
this.agentCatalog.refreshRegistryIfStale().catch((err) => {
|
|
19938
|
-
|
|
21012
|
+
log18.warn({ err }, "Background registry refresh failed");
|
|
19939
21013
|
});
|
|
19940
21014
|
const failures = [];
|
|
19941
21015
|
for (const [name, adapter] of this.adapters.entries()) {
|
|
19942
21016
|
try {
|
|
19943
21017
|
await adapter.start();
|
|
19944
21018
|
} catch (err) {
|
|
19945
|
-
|
|
21019
|
+
log18.error({ err, adapter: name }, `Adapter "${name}" failed to start`);
|
|
19946
21020
|
failures.push({ name, error: err });
|
|
19947
21021
|
}
|
|
19948
21022
|
}
|
|
@@ -20023,7 +21097,7 @@ var OpenACPCore = class {
|
|
|
20023
21097
|
* If no session is found, the user is told to start one with /new.
|
|
20024
21098
|
*/
|
|
20025
21099
|
async handleMessage(message, initialMeta) {
|
|
20026
|
-
|
|
21100
|
+
log18.debug(
|
|
20027
21101
|
{
|
|
20028
21102
|
channelId: message.channelId,
|
|
20029
21103
|
threadId: message.threadId,
|
|
@@ -20044,7 +21118,7 @@ var OpenACPCore = class {
|
|
|
20044
21118
|
}
|
|
20045
21119
|
const access2 = await this.securityGuard.checkAccess(message);
|
|
20046
21120
|
if (!access2.allowed) {
|
|
20047
|
-
|
|
21121
|
+
log18.warn({ userId: message.userId, reason: access2.reason }, "Access denied");
|
|
20048
21122
|
if (access2.reason.includes("Session limit")) {
|
|
20049
21123
|
const adapter = this.adapters.get(message.channelId);
|
|
20050
21124
|
if (adapter) {
|
|
@@ -20058,7 +21132,7 @@ var OpenACPCore = class {
|
|
|
20058
21132
|
}
|
|
20059
21133
|
let session = await this.sessionFactory.getOrResume(message.channelId, message.threadId);
|
|
20060
21134
|
if (!session) {
|
|
20061
|
-
|
|
21135
|
+
log18.warn(
|
|
20062
21136
|
{ channelId: message.channelId, threadId: message.threadId },
|
|
20063
21137
|
"No session found for thread (in-memory miss + lazy resume returned null)"
|
|
20064
21138
|
);
|
|
@@ -20110,7 +21184,7 @@ ${text3}`;
|
|
|
20110
21184
|
});
|
|
20111
21185
|
session.enqueuePrompt(text3, attachments, routing, turnId, meta).catch((err) => {
|
|
20112
21186
|
const reason = err instanceof Error ? err.message : String(err);
|
|
20113
|
-
|
|
21187
|
+
log18.warn({ err, sessionId: session.id, turnId, reason }, "enqueuePrompt failed \u2014 emitting message:failed");
|
|
20114
21188
|
this.eventBus.emit(BusEvent.MESSAGE_FAILED, {
|
|
20115
21189
|
sessionId: session.id,
|
|
20116
21190
|
turnId,
|
|
@@ -20227,7 +21301,7 @@ ${text3}`;
|
|
|
20227
21301
|
const bridge = this.createBridge(session, adapter, session.channelId);
|
|
20228
21302
|
bridge.connect();
|
|
20229
21303
|
adapter.flushPendingSkillCommands?.(session.id).catch((err) => {
|
|
20230
|
-
|
|
21304
|
+
log18.warn({ err, sessionId: session.id }, "Failed to flush pending skill commands");
|
|
20231
21305
|
});
|
|
20232
21306
|
if (params.createThread && session.threadId) {
|
|
20233
21307
|
this.eventBus.emit(BusEvent.SESSION_THREAD_READY, {
|
|
@@ -20241,14 +21315,14 @@ ${text3}`;
|
|
|
20241
21315
|
session.agentInstance.onPermissionRequest = async (permRequest) => {
|
|
20242
21316
|
const allowOption = permRequest.options.find((o) => o.isAllow);
|
|
20243
21317
|
if (!allowOption) {
|
|
20244
|
-
|
|
21318
|
+
log18.warn(
|
|
20245
21319
|
{ sessionId: session.id, permissionId: permRequest.id, description: permRequest.description },
|
|
20246
21320
|
"Headless session has no allow option for permission request \u2014 skipping auto-approve, will time out"
|
|
20247
21321
|
);
|
|
20248
21322
|
return new Promise(() => {
|
|
20249
21323
|
});
|
|
20250
21324
|
}
|
|
20251
|
-
|
|
21325
|
+
log18.warn(
|
|
20252
21326
|
{ sessionId: session.id, permissionId: permRequest.id, option: allowOption.id },
|
|
20253
21327
|
`Auto-approving permission "${permRequest.description}" for headless session \u2014 no adapter connected`
|
|
20254
21328
|
);
|
|
@@ -20302,7 +21376,7 @@ ${text3}`;
|
|
|
20302
21376
|
notificationManager: this.notificationManager,
|
|
20303
21377
|
tunnelService: this._tunnelService
|
|
20304
21378
|
});
|
|
20305
|
-
|
|
21379
|
+
log18.info(
|
|
20306
21380
|
{ sessionId: session.id, agentName: params.agentName },
|
|
20307
21381
|
"Session created via pipeline"
|
|
20308
21382
|
);
|
|
@@ -20721,33 +21795,33 @@ init_doctor();
|
|
|
20721
21795
|
init_config_registry();
|
|
20722
21796
|
|
|
20723
21797
|
// src/core/config/config-editor.ts
|
|
20724
|
-
import * as
|
|
21798
|
+
import * as path33 from "path";
|
|
20725
21799
|
import * as clack2 from "@clack/prompts";
|
|
20726
21800
|
|
|
20727
21801
|
// src/cli/autostart.ts
|
|
20728
21802
|
init_log();
|
|
20729
21803
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
20730
|
-
import * as
|
|
20731
|
-
import * as
|
|
20732
|
-
import * as
|
|
20733
|
-
var
|
|
20734
|
-
var LEGACY_LAUNCHD_PLIST_PATH =
|
|
20735
|
-
var LEGACY_SYSTEMD_SERVICE_PATH =
|
|
21804
|
+
import * as fs29 from "fs";
|
|
21805
|
+
import * as path27 from "path";
|
|
21806
|
+
import * as os9 from "os";
|
|
21807
|
+
var log20 = createChildLogger({ module: "autostart" });
|
|
21808
|
+
var LEGACY_LAUNCHD_PLIST_PATH = path27.join(os9.homedir(), "Library", "LaunchAgents", "com.openacp.daemon.plist");
|
|
21809
|
+
var LEGACY_SYSTEMD_SERVICE_PATH = path27.join(os9.homedir(), ".config", "systemd", "user", "openacp.service");
|
|
20736
21810
|
function getLaunchdLabel(instanceId) {
|
|
20737
21811
|
return `com.openacp.daemon.${instanceId}`;
|
|
20738
21812
|
}
|
|
20739
21813
|
function getLaunchdPlistPath(instanceId) {
|
|
20740
|
-
return
|
|
21814
|
+
return path27.join(os9.homedir(), "Library", "LaunchAgents", `${getLaunchdLabel(instanceId)}.plist`);
|
|
20741
21815
|
}
|
|
20742
21816
|
function getSystemdServiceName(instanceId) {
|
|
20743
21817
|
return `openacp-${instanceId}`;
|
|
20744
21818
|
}
|
|
20745
21819
|
function getSystemdServicePath(instanceId) {
|
|
20746
|
-
return
|
|
21820
|
+
return path27.join(os9.homedir(), ".config", "systemd", "user", `${getSystemdServiceName(instanceId)}.service`);
|
|
20747
21821
|
}
|
|
20748
21822
|
function getAutoStartState(instanceId) {
|
|
20749
21823
|
if (process.platform === "linux") {
|
|
20750
|
-
const installed =
|
|
21824
|
+
const installed = fs29.existsSync(getSystemdServicePath(instanceId));
|
|
20751
21825
|
if (!installed) return { installed: false, manager: null, active: false };
|
|
20752
21826
|
try {
|
|
20753
21827
|
const output = execFileSync6("systemctl", [
|
|
@@ -20769,7 +21843,7 @@ function getAutoStartState(instanceId) {
|
|
|
20769
21843
|
}
|
|
20770
21844
|
}
|
|
20771
21845
|
if (process.platform === "darwin") {
|
|
20772
|
-
const installed =
|
|
21846
|
+
const installed = fs29.existsSync(getLaunchdPlistPath(instanceId));
|
|
20773
21847
|
if (!installed) return { installed: false, manager: null, active: false };
|
|
20774
21848
|
try {
|
|
20775
21849
|
const uid = process.getuid();
|
|
@@ -20799,7 +21873,7 @@ function escapeSystemdValue(str) {
|
|
|
20799
21873
|
}
|
|
20800
21874
|
function generateLaunchdPlist(nodePath, cliPath, logDir2, instanceRoot, instanceId) {
|
|
20801
21875
|
const label = getLaunchdLabel(instanceId);
|
|
20802
|
-
const logFile =
|
|
21876
|
+
const logFile = path27.join(logDir2, "openacp.log");
|
|
20803
21877
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
20804
21878
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
20805
21879
|
<plist version="1.0">
|
|
@@ -20851,32 +21925,32 @@ WantedBy=default.target
|
|
|
20851
21925
|
`;
|
|
20852
21926
|
}
|
|
20853
21927
|
function migrateLegacy() {
|
|
20854
|
-
if (process.platform === "darwin" &&
|
|
21928
|
+
if (process.platform === "darwin" && fs29.existsSync(LEGACY_LAUNCHD_PLIST_PATH)) {
|
|
20855
21929
|
try {
|
|
20856
21930
|
const uid = process.getuid();
|
|
20857
21931
|
execFileSync6("launchctl", ["bootout", `gui/${uid}`, "com.openacp.daemon"], { stdio: "pipe" });
|
|
20858
21932
|
} catch {
|
|
20859
21933
|
}
|
|
20860
21934
|
try {
|
|
20861
|
-
|
|
21935
|
+
fs29.unlinkSync(LEGACY_LAUNCHD_PLIST_PATH);
|
|
20862
21936
|
} catch {
|
|
20863
21937
|
}
|
|
20864
|
-
|
|
21938
|
+
log20.info("Removed legacy single-instance LaunchAgent");
|
|
20865
21939
|
}
|
|
20866
|
-
if (process.platform === "linux" &&
|
|
21940
|
+
if (process.platform === "linux" && fs29.existsSync(LEGACY_SYSTEMD_SERVICE_PATH)) {
|
|
20867
21941
|
try {
|
|
20868
21942
|
execFileSync6("systemctl", ["--user", "disable", "openacp"], { stdio: "pipe" });
|
|
20869
21943
|
} catch {
|
|
20870
21944
|
}
|
|
20871
21945
|
try {
|
|
20872
|
-
|
|
21946
|
+
fs29.unlinkSync(LEGACY_SYSTEMD_SERVICE_PATH);
|
|
20873
21947
|
} catch {
|
|
20874
21948
|
}
|
|
20875
21949
|
try {
|
|
20876
21950
|
execFileSync6("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
|
|
20877
21951
|
} catch {
|
|
20878
21952
|
}
|
|
20879
|
-
|
|
21953
|
+
log20.info("Removed legacy single-instance systemd service");
|
|
20880
21954
|
}
|
|
20881
21955
|
}
|
|
20882
21956
|
function installAutoStart(logDir2, instanceRoot, instanceId) {
|
|
@@ -20884,26 +21958,26 @@ function installAutoStart(logDir2, instanceRoot, instanceId) {
|
|
|
20884
21958
|
return { success: false, error: "Auto-start not supported on this platform" };
|
|
20885
21959
|
}
|
|
20886
21960
|
const nodePath = process.execPath;
|
|
20887
|
-
const cliPath =
|
|
20888
|
-
const resolvedLogDir = logDir2.startsWith("~") ?
|
|
21961
|
+
const cliPath = path27.resolve(process.argv[1]);
|
|
21962
|
+
const resolvedLogDir = logDir2.startsWith("~") ? path27.join(os9.homedir(), logDir2.slice(1)) : logDir2;
|
|
20889
21963
|
try {
|
|
20890
21964
|
migrateLegacy();
|
|
20891
21965
|
if (process.platform === "darwin") {
|
|
20892
21966
|
const plistPath = getLaunchdPlistPath(instanceId);
|
|
20893
21967
|
const plist = generateLaunchdPlist(nodePath, cliPath, resolvedLogDir, instanceRoot, instanceId);
|
|
20894
|
-
const dir =
|
|
20895
|
-
|
|
21968
|
+
const dir = path27.dirname(plistPath);
|
|
21969
|
+
fs29.mkdirSync(dir, { recursive: true });
|
|
20896
21970
|
const uid = process.getuid();
|
|
20897
21971
|
const domain = `gui/${uid}`;
|
|
20898
|
-
const previous =
|
|
21972
|
+
const previous = fs29.existsSync(plistPath) ? fs29.readFileSync(plistPath, "utf8") : void 0;
|
|
20899
21973
|
const previousState = getAutoStartState(instanceId);
|
|
20900
21974
|
const tmp = `${plistPath}.${process.pid}.tmp`;
|
|
20901
21975
|
let replaced = false;
|
|
20902
21976
|
let bootedOut = false;
|
|
20903
21977
|
try {
|
|
20904
|
-
|
|
21978
|
+
fs29.writeFileSync(tmp, plist, { mode: 384 });
|
|
20905
21979
|
execFileSync6("plutil", ["-lint", tmp], { stdio: "pipe" });
|
|
20906
|
-
|
|
21980
|
+
fs29.renameSync(tmp, plistPath);
|
|
20907
21981
|
replaced = true;
|
|
20908
21982
|
if (previousState.active) {
|
|
20909
21983
|
execFileSync6("launchctl", ["bootout", domain, plistPath], { stdio: "pipe" });
|
|
@@ -20912,7 +21986,7 @@ function installAutoStart(logDir2, instanceRoot, instanceId) {
|
|
|
20912
21986
|
execFileSync6("launchctl", ["bootstrap", domain, plistPath], { stdio: "pipe" });
|
|
20913
21987
|
} catch (error) {
|
|
20914
21988
|
try {
|
|
20915
|
-
|
|
21989
|
+
fs29.rmSync(tmp, { force: true });
|
|
20916
21990
|
} catch {
|
|
20917
21991
|
}
|
|
20918
21992
|
if (replaced) {
|
|
@@ -20922,48 +21996,48 @@ function installAutoStart(logDir2, instanceRoot, instanceId) {
|
|
|
20922
21996
|
rollbackBootedOut = true;
|
|
20923
21997
|
} catch {
|
|
20924
21998
|
}
|
|
20925
|
-
if (previous === void 0)
|
|
21999
|
+
if (previous === void 0) fs29.rmSync(plistPath, { force: true });
|
|
20926
22000
|
else {
|
|
20927
22001
|
const rollbackTmp = `${plistPath}.${process.pid}.rollback.tmp`;
|
|
20928
|
-
|
|
20929
|
-
|
|
22002
|
+
fs29.writeFileSync(rollbackTmp, previous, { mode: 384 });
|
|
22003
|
+
fs29.renameSync(rollbackTmp, plistPath);
|
|
20930
22004
|
if (previousState.active || bootedOut || rollbackBootedOut) execFileSync6("launchctl", ["bootstrap", domain, plistPath], { stdio: "pipe" });
|
|
20931
22005
|
}
|
|
20932
22006
|
}
|
|
20933
22007
|
throw error;
|
|
20934
22008
|
}
|
|
20935
|
-
|
|
22009
|
+
log20.info({ instanceId }, "LaunchAgent installed");
|
|
20936
22010
|
return { success: true };
|
|
20937
22011
|
}
|
|
20938
22012
|
if (process.platform === "linux") {
|
|
20939
22013
|
const servicePath = getSystemdServicePath(instanceId);
|
|
20940
22014
|
const serviceName = getSystemdServiceName(instanceId);
|
|
20941
22015
|
const unit = generateSystemdUnit(nodePath, cliPath, instanceRoot, instanceId);
|
|
20942
|
-
const dir =
|
|
20943
|
-
|
|
20944
|
-
const previous =
|
|
22016
|
+
const dir = path27.dirname(servicePath);
|
|
22017
|
+
fs29.mkdirSync(dir, { recursive: true });
|
|
22018
|
+
const previous = fs29.existsSync(servicePath) ? fs29.readFileSync(servicePath, "utf8") : void 0;
|
|
20945
22019
|
const tmp = `${servicePath}.${process.pid}.tmp`;
|
|
20946
|
-
|
|
20947
|
-
|
|
22020
|
+
fs29.writeFileSync(tmp, unit, { mode: 384 });
|
|
22021
|
+
fs29.renameSync(tmp, servicePath);
|
|
20948
22022
|
try {
|
|
20949
22023
|
execFileSync6("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
|
|
20950
22024
|
execFileSync6("systemctl", ["--user", "enable", serviceName], { stdio: "pipe" });
|
|
20951
22025
|
} catch (error) {
|
|
20952
|
-
if (previous === void 0)
|
|
20953
|
-
else
|
|
22026
|
+
if (previous === void 0) fs29.unlinkSync(servicePath);
|
|
22027
|
+
else fs29.writeFileSync(servicePath, previous);
|
|
20954
22028
|
try {
|
|
20955
22029
|
execFileSync6("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
|
|
20956
22030
|
} catch {
|
|
20957
22031
|
}
|
|
20958
22032
|
throw error;
|
|
20959
22033
|
}
|
|
20960
|
-
|
|
22034
|
+
log20.info({ instanceId }, "systemd user service installed");
|
|
20961
22035
|
return { success: true };
|
|
20962
22036
|
}
|
|
20963
22037
|
return { success: false, error: "Unsupported platform" };
|
|
20964
22038
|
} catch (e) {
|
|
20965
22039
|
const msg = e.message;
|
|
20966
|
-
|
|
22040
|
+
log20.error({ err: msg }, "Failed to install auto-start");
|
|
20967
22041
|
return { success: false, error: msg };
|
|
20968
22042
|
}
|
|
20969
22043
|
}
|
|
@@ -20974,44 +22048,44 @@ function uninstallAutoStart(instanceId) {
|
|
|
20974
22048
|
try {
|
|
20975
22049
|
if (process.platform === "darwin") {
|
|
20976
22050
|
const plistPath = getLaunchdPlistPath(instanceId);
|
|
20977
|
-
if (
|
|
22051
|
+
if (fs29.existsSync(plistPath)) {
|
|
20978
22052
|
const uid = process.getuid();
|
|
20979
22053
|
try {
|
|
20980
22054
|
execFileSync6("launchctl", ["bootout", `gui/${uid}`, plistPath], { stdio: "pipe" });
|
|
20981
22055
|
} catch {
|
|
20982
22056
|
}
|
|
20983
|
-
|
|
20984
|
-
|
|
22057
|
+
fs29.unlinkSync(plistPath);
|
|
22058
|
+
log20.info({ instanceId }, "LaunchAgent removed");
|
|
20985
22059
|
}
|
|
20986
22060
|
return { success: true };
|
|
20987
22061
|
}
|
|
20988
22062
|
if (process.platform === "linux") {
|
|
20989
22063
|
const servicePath = getSystemdServicePath(instanceId);
|
|
20990
22064
|
const serviceName = getSystemdServiceName(instanceId);
|
|
20991
|
-
if (
|
|
22065
|
+
if (fs29.existsSync(servicePath)) {
|
|
20992
22066
|
try {
|
|
20993
22067
|
execFileSync6("systemctl", ["--user", "disable", serviceName], { stdio: "pipe" });
|
|
20994
22068
|
} catch {
|
|
20995
22069
|
}
|
|
20996
|
-
|
|
22070
|
+
fs29.unlinkSync(servicePath);
|
|
20997
22071
|
execFileSync6("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
|
|
20998
|
-
|
|
22072
|
+
log20.info({ instanceId }, "systemd user service removed");
|
|
20999
22073
|
}
|
|
21000
22074
|
return { success: true };
|
|
21001
22075
|
}
|
|
21002
22076
|
return { success: false, error: "Unsupported platform" };
|
|
21003
22077
|
} catch (e) {
|
|
21004
22078
|
const msg = e.message;
|
|
21005
|
-
|
|
22079
|
+
log20.error({ err: msg }, "Failed to uninstall auto-start");
|
|
21006
22080
|
return { success: false, error: msg };
|
|
21007
22081
|
}
|
|
21008
22082
|
}
|
|
21009
22083
|
function isAutoStartInstalled(instanceId) {
|
|
21010
22084
|
if (process.platform === "darwin") {
|
|
21011
|
-
return
|
|
22085
|
+
return fs29.existsSync(getLaunchdPlistPath(instanceId));
|
|
21012
22086
|
}
|
|
21013
22087
|
if (process.platform === "linux") {
|
|
21014
|
-
return
|
|
22088
|
+
return fs29.existsSync(getSystemdServicePath(instanceId));
|
|
21015
22089
|
}
|
|
21016
22090
|
return false;
|
|
21017
22091
|
}
|
|
@@ -21020,25 +22094,25 @@ function isAutoStartInstalled(instanceId) {
|
|
|
21020
22094
|
init_instance_context();
|
|
21021
22095
|
init_instance_registry();
|
|
21022
22096
|
init_log();
|
|
21023
|
-
import
|
|
21024
|
-
import
|
|
21025
|
-
var
|
|
22097
|
+
import fs31 from "fs";
|
|
22098
|
+
import path29 from "path";
|
|
22099
|
+
var log21 = createChildLogger({ module: "resolve-instance-id" });
|
|
21026
22100
|
function resolveInstanceId(instanceRoot) {
|
|
21027
22101
|
try {
|
|
21028
|
-
const configPath =
|
|
21029
|
-
const raw = JSON.parse(
|
|
22102
|
+
const configPath = path29.join(instanceRoot, "config.json");
|
|
22103
|
+
const raw = JSON.parse(fs31.readFileSync(configPath, "utf-8"));
|
|
21030
22104
|
if (raw.id && typeof raw.id === "string") return raw.id;
|
|
21031
22105
|
} catch {
|
|
21032
22106
|
}
|
|
21033
22107
|
try {
|
|
21034
|
-
const reg = new InstanceRegistry(
|
|
22108
|
+
const reg = new InstanceRegistry(path29.join(getGlobalRoot(), "instances.json"));
|
|
21035
22109
|
reg.load();
|
|
21036
22110
|
const entry = reg.getByRoot(instanceRoot);
|
|
21037
22111
|
if (entry?.id) return entry.id;
|
|
21038
22112
|
} catch (err) {
|
|
21039
|
-
|
|
22113
|
+
log21.debug({ err: err.message, instanceRoot }, "Could not read instance registry, using fallback id");
|
|
21040
22114
|
}
|
|
21041
|
-
return
|
|
22115
|
+
return path29.basename(path29.dirname(instanceRoot)).replace(/[^a-zA-Z0-9-]/g, "-") || "default";
|
|
21042
22116
|
}
|
|
21043
22117
|
|
|
21044
22118
|
// src/core/config/config-editor.ts
|
|
@@ -21610,7 +22684,7 @@ async function runConfigEditor(configManager, mode = "file", apiPort, settingsMa
|
|
|
21610
22684
|
await configManager.load();
|
|
21611
22685
|
const config = configManager.get();
|
|
21612
22686
|
const updates = {};
|
|
21613
|
-
const instanceRoot =
|
|
22687
|
+
const instanceRoot = path33.dirname(configManager.getConfigPath());
|
|
21614
22688
|
console.log(`
|
|
21615
22689
|
${c.cyan}${c.bold}OpenACP Config Editor${c.reset}`);
|
|
21616
22690
|
console.log(dim(`Config: ${configManager.getConfigPath()}`));
|
|
@@ -21667,17 +22741,17 @@ ${c.cyan}${c.bold}OpenACP Config Editor${c.reset}`);
|
|
|
21667
22741
|
async function sendConfigViaApi(port, updates) {
|
|
21668
22742
|
const { apiCall: call } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
21669
22743
|
const paths = flattenToPaths(updates);
|
|
21670
|
-
for (const { path:
|
|
22744
|
+
for (const { path: path38, value } of paths) {
|
|
21671
22745
|
const res = await call(port, "/api/config", {
|
|
21672
22746
|
method: "PATCH",
|
|
21673
22747
|
headers: { "Content-Type": "application/json" },
|
|
21674
|
-
body: JSON.stringify({ path:
|
|
22748
|
+
body: JSON.stringify({ path: path38, value })
|
|
21675
22749
|
});
|
|
21676
22750
|
const data = await res.json();
|
|
21677
22751
|
if (!res.ok) {
|
|
21678
|
-
console.log(warn(`Failed to update ${
|
|
22752
|
+
console.log(warn(`Failed to update ${path38}: ${data.error}`));
|
|
21679
22753
|
} else if (data.needsRestart) {
|
|
21680
|
-
console.log(warn(`${
|
|
22754
|
+
console.log(warn(`${path38} updated \u2014 restart required`));
|
|
21681
22755
|
}
|
|
21682
22756
|
}
|
|
21683
22757
|
}
|
|
@@ -21697,25 +22771,25 @@ function flattenToPaths(obj, prefix = "") {
|
|
|
21697
22771
|
// src/cli/daemon.ts
|
|
21698
22772
|
init_config();
|
|
21699
22773
|
import { spawn as spawn3 } from "child_process";
|
|
21700
|
-
import * as
|
|
21701
|
-
import * as
|
|
22774
|
+
import * as fs34 from "fs";
|
|
22775
|
+
import * as path34 from "path";
|
|
21702
22776
|
function getPidPath(root) {
|
|
21703
|
-
return
|
|
22777
|
+
return path34.join(root, "openacp.pid");
|
|
21704
22778
|
}
|
|
21705
22779
|
function getLogDir(root) {
|
|
21706
|
-
return
|
|
22780
|
+
return path34.join(root, "logs");
|
|
21707
22781
|
}
|
|
21708
22782
|
function getRunningMarker(root) {
|
|
21709
|
-
return
|
|
22783
|
+
return path34.join(root, "running");
|
|
21710
22784
|
}
|
|
21711
22785
|
function writePidFile(pidPath, pid) {
|
|
21712
|
-
const dir =
|
|
21713
|
-
|
|
21714
|
-
|
|
22786
|
+
const dir = path34.dirname(pidPath);
|
|
22787
|
+
fs34.mkdirSync(dir, { recursive: true });
|
|
22788
|
+
fs34.writeFileSync(pidPath, String(pid));
|
|
21715
22789
|
}
|
|
21716
22790
|
function readPidFile(pidPath) {
|
|
21717
22791
|
try {
|
|
21718
|
-
const content =
|
|
22792
|
+
const content = fs34.readFileSync(pidPath, "utf-8").trim();
|
|
21719
22793
|
const pid = parseInt(content, 10);
|
|
21720
22794
|
return isNaN(pid) ? null : pid;
|
|
21721
22795
|
} catch {
|
|
@@ -21724,7 +22798,7 @@ function readPidFile(pidPath) {
|
|
|
21724
22798
|
}
|
|
21725
22799
|
function removePidFile(pidPath) {
|
|
21726
22800
|
try {
|
|
21727
|
-
|
|
22801
|
+
fs34.unlinkSync(pidPath);
|
|
21728
22802
|
} catch {
|
|
21729
22803
|
}
|
|
21730
22804
|
}
|
|
@@ -21757,12 +22831,12 @@ function startDaemon(pidPath, logDir2, instanceRoot) {
|
|
|
21757
22831
|
return { error: `Already running (PID ${pid})` };
|
|
21758
22832
|
}
|
|
21759
22833
|
const resolvedLogDir = logDir2 ? expandHome2(logDir2) : getLogDir(instanceRoot);
|
|
21760
|
-
|
|
21761
|
-
const logFile =
|
|
21762
|
-
const cliPath =
|
|
22834
|
+
fs34.mkdirSync(resolvedLogDir, { recursive: true });
|
|
22835
|
+
const logFile = path34.join(resolvedLogDir, "openacp.log");
|
|
22836
|
+
const cliPath = path34.resolve(process.argv[1]);
|
|
21763
22837
|
const nodePath = process.execPath;
|
|
21764
|
-
const out =
|
|
21765
|
-
const err =
|
|
22838
|
+
const out = fs34.openSync(logFile, "a");
|
|
22839
|
+
const err = fs34.openSync(logFile, "a");
|
|
21766
22840
|
const child = spawn3(nodePath, [cliPath, "--daemon-child"], {
|
|
21767
22841
|
detached: true,
|
|
21768
22842
|
stdio: ["ignore", out, err],
|
|
@@ -21771,8 +22845,8 @@ function startDaemon(pidPath, logDir2, instanceRoot) {
|
|
|
21771
22845
|
...instanceRoot ? { OPENACP_INSTANCE_ROOT: instanceRoot } : {}
|
|
21772
22846
|
}
|
|
21773
22847
|
});
|
|
21774
|
-
|
|
21775
|
-
|
|
22848
|
+
fs34.closeSync(out);
|
|
22849
|
+
fs34.closeSync(err);
|
|
21776
22850
|
if (!child.pid) {
|
|
21777
22851
|
return { error: "Failed to spawn daemon process" };
|
|
21778
22852
|
}
|
|
@@ -21843,12 +22917,12 @@ async function stopDaemon(pidPath, instanceRoot) {
|
|
|
21843
22917
|
}
|
|
21844
22918
|
function markRunning(root) {
|
|
21845
22919
|
const marker = getRunningMarker(root);
|
|
21846
|
-
|
|
21847
|
-
|
|
22920
|
+
fs34.mkdirSync(path34.dirname(marker), { recursive: true });
|
|
22921
|
+
fs34.writeFileSync(marker, "");
|
|
21848
22922
|
}
|
|
21849
22923
|
function clearRunning(root) {
|
|
21850
22924
|
try {
|
|
21851
|
-
|
|
22925
|
+
fs34.unlinkSync(getRunningMarker(root));
|
|
21852
22926
|
} catch {
|
|
21853
22927
|
}
|
|
21854
22928
|
}
|
|
@@ -21864,7 +22938,7 @@ init_static_server();
|
|
|
21864
22938
|
|
|
21865
22939
|
// src/plugins/telegram/topic-manager.ts
|
|
21866
22940
|
init_log();
|
|
21867
|
-
var
|
|
22941
|
+
var log24 = createChildLogger({ module: "topic-manager" });
|
|
21868
22942
|
var TopicManager = class {
|
|
21869
22943
|
constructor(sessionManager, adapter, systemTopicIds) {
|
|
21870
22944
|
this.sessionManager = sessionManager;
|
|
@@ -21916,7 +22990,7 @@ var TopicManager = class {
|
|
|
21916
22990
|
try {
|
|
21917
22991
|
await this.adapter.deleteSessionThread?.(sessionId);
|
|
21918
22992
|
} catch (err) {
|
|
21919
|
-
|
|
22993
|
+
log24.warn({ err, sessionId, topicId }, "Failed to delete platform thread, removing record anyway");
|
|
21920
22994
|
}
|
|
21921
22995
|
}
|
|
21922
22996
|
await this.sessionManager.removeRecord(sessionId);
|
|
@@ -21943,7 +23017,7 @@ var TopicManager = class {
|
|
|
21943
23017
|
try {
|
|
21944
23018
|
await this.adapter.deleteSessionThread?.(record.sessionId);
|
|
21945
23019
|
} catch (err) {
|
|
21946
|
-
|
|
23020
|
+
log24.warn({ err, sessionId: record.sessionId }, "Failed to delete platform thread during cleanup");
|
|
21947
23021
|
}
|
|
21948
23022
|
}
|
|
21949
23023
|
await this.sessionManager.removeRecord(record.sessionId);
|