@n1creator/openacp-cli 2026.712.9 → 2026.712.11
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 +24 -3
- package/dist/cli.js +2813 -1005
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +98 -7
- package/dist/index.js +1644 -496
- 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,38 @@ var init_resume = __esm({
|
|
|
9481
10020
|
init_topics();
|
|
9482
10021
|
init_admin();
|
|
9483
10022
|
init_log();
|
|
9484
|
-
|
|
10023
|
+
log28 = createChildLogger({ module: "telegram-cmd-resume" });
|
|
10024
|
+
}
|
|
10025
|
+
});
|
|
10026
|
+
|
|
10027
|
+
// src/plugins/telegram/callback-navigation.ts
|
|
10028
|
+
function contextualCommandCallback(command, target) {
|
|
10029
|
+
if (!command.startsWith("/")) throw new Error("Telegram navigation commands must start with /");
|
|
10030
|
+
const data = target === "settings" ? `${SETTINGS_PREFIX}${command}` : void 0;
|
|
10031
|
+
return data && Buffer.byteLength(data, "utf8") <= 64 ? data : void 0;
|
|
10032
|
+
}
|
|
10033
|
+
function settingsCommandCallback(command) {
|
|
10034
|
+
const data = contextualCommandCallback(command, "settings");
|
|
10035
|
+
if (!data) throw new Error("Telegram navigation callback exceeds 64 bytes");
|
|
10036
|
+
return data;
|
|
10037
|
+
}
|
|
10038
|
+
function decodeCommandCallback(data) {
|
|
10039
|
+
if (data.startsWith(SETTINGS_PREFIX)) {
|
|
10040
|
+
return { command: data.slice(SETTINGS_PREFIX.length), returnTarget: "settings" };
|
|
10041
|
+
}
|
|
10042
|
+
return { command: data.slice(2) };
|
|
10043
|
+
}
|
|
10044
|
+
function returnButton(target) {
|
|
10045
|
+
switch (target) {
|
|
10046
|
+
case "settings":
|
|
10047
|
+
return { text: "\u25C0\uFE0F Back to Settings", callback_data: "s:back:refresh" };
|
|
10048
|
+
}
|
|
10049
|
+
}
|
|
10050
|
+
var SETTINGS_PREFIX;
|
|
10051
|
+
var init_callback_navigation = __esm({
|
|
10052
|
+
"src/plugins/telegram/callback-navigation.ts"() {
|
|
10053
|
+
"use strict";
|
|
10054
|
+
SETTINGS_PREFIX = "c/@settings:";
|
|
9485
10055
|
}
|
|
9486
10056
|
});
|
|
9487
10057
|
|
|
@@ -9597,6 +10167,7 @@ async function buildSettingsKeyboard(core) {
|
|
|
9597
10167
|
kb.text(`${label}`, `s:input:${field.path}`).row();
|
|
9598
10168
|
}
|
|
9599
10169
|
}
|
|
10170
|
+
kb.text("\u{1F310} Proxy Management", settingsCommandCallback("/proxy")).row();
|
|
9600
10171
|
kb.text("\u25C0\uFE0F Back to Menu", "s:back");
|
|
9601
10172
|
return kb;
|
|
9602
10173
|
}
|
|
@@ -9644,7 +10215,7 @@ function setupSettingsCallbacks(bot, core, getAssistantSession) {
|
|
|
9644
10215
|
} catch {
|
|
9645
10216
|
}
|
|
9646
10217
|
} catch (err) {
|
|
9647
|
-
|
|
10218
|
+
log29.error({ err, fieldPath }, "Failed to toggle config");
|
|
9648
10219
|
try {
|
|
9649
10220
|
await ctx.answerCallbackQuery({ text: "\u274C Failed to update" });
|
|
9650
10221
|
} catch {
|
|
@@ -9725,7 +10296,7 @@ Tap to change:`, {
|
|
|
9725
10296
|
} catch {
|
|
9726
10297
|
}
|
|
9727
10298
|
} catch (err) {
|
|
9728
|
-
|
|
10299
|
+
log29.error({ err, fieldPath }, "Failed to set config");
|
|
9729
10300
|
try {
|
|
9730
10301
|
await ctx.answerCallbackQuery({ text: "\u274C Failed to update" });
|
|
9731
10302
|
} catch {
|
|
@@ -9783,13 +10354,14 @@ Tap to change:`, {
|
|
|
9783
10354
|
}
|
|
9784
10355
|
});
|
|
9785
10356
|
}
|
|
9786
|
-
var
|
|
10357
|
+
var log29;
|
|
9787
10358
|
var init_settings = __esm({
|
|
9788
10359
|
"src/plugins/telegram/commands/settings.ts"() {
|
|
9789
10360
|
"use strict";
|
|
9790
10361
|
init_config_registry();
|
|
9791
10362
|
init_log();
|
|
9792
|
-
|
|
10363
|
+
init_callback_navigation();
|
|
10364
|
+
log29 = createChildLogger({ module: "telegram-settings" });
|
|
9793
10365
|
}
|
|
9794
10366
|
});
|
|
9795
10367
|
|
|
@@ -9836,7 +10408,7 @@ async function handleDoctor(ctx, core) {
|
|
|
9836
10408
|
reply_markup: keyboard
|
|
9837
10409
|
});
|
|
9838
10410
|
} catch (err) {
|
|
9839
|
-
|
|
10411
|
+
log30.error({ err }, "Doctor command failed");
|
|
9840
10412
|
await ctx.api.editMessageText(
|
|
9841
10413
|
ctx.chat.id,
|
|
9842
10414
|
statusMsg.message_id,
|
|
@@ -9885,7 +10457,7 @@ function setupDoctorCallbacks(bot, core) {
|
|
|
9885
10457
|
}
|
|
9886
10458
|
}
|
|
9887
10459
|
} catch (err) {
|
|
9888
|
-
|
|
10460
|
+
log30.error({ err, index }, "Doctor fix callback failed");
|
|
9889
10461
|
}
|
|
9890
10462
|
});
|
|
9891
10463
|
bot.callbackQuery("m:doctor", async (ctx) => {
|
|
@@ -9896,13 +10468,13 @@ function setupDoctorCallbacks(bot, core) {
|
|
|
9896
10468
|
await handleDoctor(ctx, core);
|
|
9897
10469
|
});
|
|
9898
10470
|
}
|
|
9899
|
-
var
|
|
10471
|
+
var log30, pendingFixesStore;
|
|
9900
10472
|
var init_doctor2 = __esm({
|
|
9901
10473
|
"src/plugins/telegram/commands/doctor.ts"() {
|
|
9902
10474
|
"use strict";
|
|
9903
10475
|
init_doctor();
|
|
9904
10476
|
init_log();
|
|
9905
|
-
|
|
10477
|
+
log30 = createChildLogger({ module: "telegram-cmd-doctor" });
|
|
9906
10478
|
pendingFixesStore = /* @__PURE__ */ new Map();
|
|
9907
10479
|
}
|
|
9908
10480
|
});
|
|
@@ -9961,13 +10533,13 @@ function setupTunnelCallbacks(bot, core) {
|
|
|
9961
10533
|
}
|
|
9962
10534
|
});
|
|
9963
10535
|
}
|
|
9964
|
-
var
|
|
10536
|
+
var log31;
|
|
9965
10537
|
var init_tunnel2 = __esm({
|
|
9966
10538
|
"src/plugins/telegram/commands/tunnel.ts"() {
|
|
9967
10539
|
"use strict";
|
|
9968
10540
|
init_formatting();
|
|
9969
10541
|
init_log();
|
|
9970
|
-
|
|
10542
|
+
log31 = createChildLogger({ module: "telegram-cmd-tunnel" });
|
|
9971
10543
|
}
|
|
9972
10544
|
});
|
|
9973
10545
|
|
|
@@ -9981,10 +10553,10 @@ async function executeSwitchAgent(ctx, core, sessionId, agentName) {
|
|
|
9981
10553
|
`Switched to <b>${escapeHtml(agentName)}</b> (${status})`,
|
|
9982
10554
|
{ parse_mode: "HTML" }
|
|
9983
10555
|
);
|
|
9984
|
-
|
|
10556
|
+
log32.info({ sessionId, agentName, resumed }, "Agent switched via /switch");
|
|
9985
10557
|
} catch (err) {
|
|
9986
10558
|
await ctx.reply(`Failed to switch agent: ${escapeHtml(String(err.message || err))}`);
|
|
9987
|
-
|
|
10559
|
+
log32.warn({ sessionId, agentName, err: err.message }, "Agent switch failed");
|
|
9988
10560
|
}
|
|
9989
10561
|
}
|
|
9990
10562
|
function setupSwitchCallbacks(bot, core) {
|
|
@@ -10029,13 +10601,13 @@ Switch to <b>${escapeHtml(agentName)}</b> anyway?`,
|
|
|
10029
10601
|
await executeSwitchAgent(ctx, core, session.id, data);
|
|
10030
10602
|
});
|
|
10031
10603
|
}
|
|
10032
|
-
var
|
|
10604
|
+
var log32;
|
|
10033
10605
|
var init_switch = __esm({
|
|
10034
10606
|
"src/plugins/telegram/commands/switch.ts"() {
|
|
10035
10607
|
"use strict";
|
|
10036
10608
|
init_formatting();
|
|
10037
10609
|
init_log();
|
|
10038
|
-
|
|
10610
|
+
log32 = createChildLogger({ module: "telegram-cmd-switch" });
|
|
10039
10611
|
}
|
|
10040
10612
|
});
|
|
10041
10613
|
|
|
@@ -10258,7 +10830,7 @@ function setupAllCallbacks(bot, core, chatId, systemTopicIds, getAssistantSessio
|
|
|
10258
10830
|
if (!menuRegistry) return;
|
|
10259
10831
|
const item = menuRegistry.getItem(itemId);
|
|
10260
10832
|
if (!item) {
|
|
10261
|
-
|
|
10833
|
+
log33.warn({ itemId }, "Menu item not found in registry");
|
|
10262
10834
|
return;
|
|
10263
10835
|
}
|
|
10264
10836
|
const topicId = ctx.callbackQuery.message?.message_thread_id;
|
|
@@ -10344,7 +10916,7 @@ ${lines}`, { parse_mode: "HTML" }).catch(() => {
|
|
|
10344
10916
|
}
|
|
10345
10917
|
});
|
|
10346
10918
|
}
|
|
10347
|
-
var
|
|
10919
|
+
var log33, STATIC_COMMANDS;
|
|
10348
10920
|
var init_commands = __esm({
|
|
10349
10921
|
"src/plugins/telegram/commands/index.ts"() {
|
|
10350
10922
|
"use strict";
|
|
@@ -10370,7 +10942,7 @@ var init_commands = __esm({
|
|
|
10370
10942
|
init_settings();
|
|
10371
10943
|
init_doctor2();
|
|
10372
10944
|
init_resume();
|
|
10373
|
-
|
|
10945
|
+
log33 = createChildLogger({ module: "telegram-menu-callbacks" });
|
|
10374
10946
|
STATIC_COMMANDS = [
|
|
10375
10947
|
{ command: "new", description: "Create new session" },
|
|
10376
10948
|
{ command: "newchat", description: "New chat, same agent & workspace" },
|
|
@@ -10386,6 +10958,7 @@ var init_commands = __esm({
|
|
|
10386
10958
|
{ command: "restart", description: "Restart OpenACP" },
|
|
10387
10959
|
{ command: "update", description: "Update to latest version and restart" },
|
|
10388
10960
|
{ command: "doctor", description: "Run system diagnostics" },
|
|
10961
|
+
{ command: "proxy", description: "Manage scoped proxy routing" },
|
|
10389
10962
|
{ command: "retry", description: "Re-check setup prerequisites (use if bot is stuck on startup)" },
|
|
10390
10963
|
{ command: "tunnel", description: "Create/stop tunnel for a local port" },
|
|
10391
10964
|
{ command: "tunnels", description: "List active tunnels" },
|
|
@@ -10406,14 +10979,14 @@ var init_commands = __esm({
|
|
|
10406
10979
|
// src/plugins/telegram/permissions.ts
|
|
10407
10980
|
import { InlineKeyboard as InlineKeyboard12 } from "grammy";
|
|
10408
10981
|
import { nanoid as nanoid4 } from "nanoid";
|
|
10409
|
-
var
|
|
10982
|
+
var log34, PermissionHandler;
|
|
10410
10983
|
var init_permissions = __esm({
|
|
10411
10984
|
"src/plugins/telegram/permissions.ts"() {
|
|
10412
10985
|
"use strict";
|
|
10413
10986
|
init_formatting();
|
|
10414
10987
|
init_topics();
|
|
10415
10988
|
init_log();
|
|
10416
|
-
|
|
10989
|
+
log34 = createChildLogger({ module: "telegram-permissions" });
|
|
10417
10990
|
PermissionHandler = class {
|
|
10418
10991
|
constructor(bot, chatId, getSession, sendNotification) {
|
|
10419
10992
|
this.bot = bot;
|
|
@@ -10491,7 +11064,7 @@ ${escapeHtml(request.description)}`,
|
|
|
10491
11064
|
}
|
|
10492
11065
|
const session = this.getSession(pending.sessionId);
|
|
10493
11066
|
const isAllow = pending.options.find((o) => o.id === optionId)?.isAllow ?? false;
|
|
10494
|
-
|
|
11067
|
+
log34.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
|
|
10495
11068
|
if (session?.permissionGate.requestId === pending.requestId) {
|
|
10496
11069
|
session.permissionGate.resolve(optionId);
|
|
10497
11070
|
}
|
|
@@ -10511,7 +11084,7 @@ ${escapeHtml(request.description)}`,
|
|
|
10511
11084
|
});
|
|
10512
11085
|
|
|
10513
11086
|
// src/plugins/telegram/activity.ts
|
|
10514
|
-
var
|
|
11087
|
+
var log35, THINKING_REFRESH_MS, THINKING_MAX_MS, ThinkingIndicator, ToolCard, ActivityTracker2;
|
|
10515
11088
|
var init_activity = __esm({
|
|
10516
11089
|
"src/plugins/telegram/activity.ts"() {
|
|
10517
11090
|
"use strict";
|
|
@@ -10521,7 +11094,7 @@ var init_activity = __esm({
|
|
|
10521
11094
|
init_stream_accumulator();
|
|
10522
11095
|
init_stream_accumulator();
|
|
10523
11096
|
init_display_spec_builder();
|
|
10524
|
-
|
|
11097
|
+
log35 = createChildLogger({ module: "telegram:activity" });
|
|
10525
11098
|
THINKING_REFRESH_MS = 15e3;
|
|
10526
11099
|
THINKING_MAX_MS = 3 * 60 * 1e3;
|
|
10527
11100
|
ThinkingIndicator = class {
|
|
@@ -10567,7 +11140,7 @@ var init_activity = __esm({
|
|
|
10567
11140
|
}
|
|
10568
11141
|
}
|
|
10569
11142
|
} catch (err) {
|
|
10570
|
-
|
|
11143
|
+
log35.warn({ err }, "ThinkingIndicator.show() failed");
|
|
10571
11144
|
} finally {
|
|
10572
11145
|
this.sending = false;
|
|
10573
11146
|
}
|
|
@@ -10744,7 +11317,7 @@ var init_activity = __esm({
|
|
|
10744
11317
|
this.tracer?.log("telegram", { action: "telegram:delete:overflow", sessionId: this.sessionId, msgId: staleId });
|
|
10745
11318
|
}
|
|
10746
11319
|
} catch (err) {
|
|
10747
|
-
|
|
11320
|
+
log35.warn({ err }, "[ToolCard] send/edit failed");
|
|
10748
11321
|
}
|
|
10749
11322
|
}
|
|
10750
11323
|
};
|
|
@@ -10852,7 +11425,7 @@ var init_activity = __esm({
|
|
|
10852
11425
|
const entry = this.toolStateMap.merge(id, status, rawInput, content, viewerLinks, diffStats);
|
|
10853
11426
|
if (!existed || !entry) return;
|
|
10854
11427
|
if (viewerLinks || entry.viewerLinks) {
|
|
10855
|
-
|
|
11428
|
+
log35.debug({ toolId: id, status, hasIncomingLinks: !!viewerLinks, hasEntryLinks: !!entry.viewerLinks, entryLinks: entry.viewerLinks }, "toolUpdate: viewer links trace");
|
|
10856
11429
|
}
|
|
10857
11430
|
const spec = this.specBuilder.buildToolSpec(entry, this._outputMode, this.sessionContext);
|
|
10858
11431
|
this.toolCard.updateFromSpec(spec);
|
|
@@ -11210,13 +11783,13 @@ var init_draft_manager = __esm({
|
|
|
11210
11783
|
});
|
|
11211
11784
|
|
|
11212
11785
|
// src/plugins/telegram/skill-command-manager.ts
|
|
11213
|
-
var
|
|
11786
|
+
var log36, SkillCommandManager;
|
|
11214
11787
|
var init_skill_command_manager = __esm({
|
|
11215
11788
|
"src/plugins/telegram/skill-command-manager.ts"() {
|
|
11216
11789
|
"use strict";
|
|
11217
11790
|
init_commands();
|
|
11218
11791
|
init_log();
|
|
11219
|
-
|
|
11792
|
+
log36 = createChildLogger({ module: "skill-commands" });
|
|
11220
11793
|
SkillCommandManager = class {
|
|
11221
11794
|
constructor(bot, chatId, sendQueue, sessionManager) {
|
|
11222
11795
|
this.bot = bot;
|
|
@@ -11291,7 +11864,7 @@ var init_skill_command_manager = __esm({
|
|
|
11291
11864
|
disable_notification: true
|
|
11292
11865
|
});
|
|
11293
11866
|
} catch (err) {
|
|
11294
|
-
|
|
11867
|
+
log36.error({ err, sessionId }, "Failed to send skill commands");
|
|
11295
11868
|
}
|
|
11296
11869
|
}
|
|
11297
11870
|
async cleanup(sessionId) {
|
|
@@ -11397,6 +11970,185 @@ var init_renderer2 = __esm({
|
|
|
11397
11970
|
}
|
|
11398
11971
|
});
|
|
11399
11972
|
|
|
11973
|
+
// src/plugins/identity/types.ts
|
|
11974
|
+
var init_types = __esm({
|
|
11975
|
+
"src/plugins/identity/types.ts"() {
|
|
11976
|
+
"use strict";
|
|
11977
|
+
}
|
|
11978
|
+
});
|
|
11979
|
+
|
|
11980
|
+
// src/core/commands/proxy.ts
|
|
11981
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
11982
|
+
function clearProxyDraftsForChannel(channelId) {
|
|
11983
|
+
const prefix = `${channelId}:`;
|
|
11984
|
+
for (const [id, draft] of drafts) if (draft.owner.startsWith(prefix)) drafts.delete(id);
|
|
11985
|
+
}
|
|
11986
|
+
var DRAFT_TTL_MS, drafts;
|
|
11987
|
+
var init_proxy2 = __esm({
|
|
11988
|
+
"src/core/commands/proxy.ts"() {
|
|
11989
|
+
"use strict";
|
|
11990
|
+
init_proxy_types();
|
|
11991
|
+
init_types();
|
|
11992
|
+
init_proxy_store();
|
|
11993
|
+
init_proxy_service();
|
|
11994
|
+
DRAFT_TTL_MS = 10 * 6e4;
|
|
11995
|
+
drafts = /* @__PURE__ */ new Map();
|
|
11996
|
+
}
|
|
11997
|
+
});
|
|
11998
|
+
|
|
11999
|
+
// src/plugins/telegram/command-sync.ts
|
|
12000
|
+
import { createHash as createHash4 } from "crypto";
|
|
12001
|
+
function validDescription(description) {
|
|
12002
|
+
if (typeof description !== "string") return false;
|
|
12003
|
+
const trimmed = description.trim();
|
|
12004
|
+
return trimmed.length > 0 && trimmed.length <= TELEGRAM_DESCRIPTION_LIMIT;
|
|
12005
|
+
}
|
|
12006
|
+
function prepareTelegramCommandBoundary(coreCommands, registryCommands) {
|
|
12007
|
+
if (coreCommands.length > TELEGRAM_COMMAND_LIMIT) {
|
|
12008
|
+
throw new TelegramCommandBoundaryError("OpenACP core defines more than 100 Telegram commands");
|
|
12009
|
+
}
|
|
12010
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12011
|
+
const commands = [];
|
|
12012
|
+
for (const command of coreCommands) {
|
|
12013
|
+
if (!TELEGRAM_COMMAND_NAME.test(command.command) || !validDescription(command.description)) {
|
|
12014
|
+
throw new TelegramCommandBoundaryError("OpenACP core contains an invalid Telegram command definition");
|
|
12015
|
+
}
|
|
12016
|
+
if (seen.has(command.command)) throw new TelegramCommandBoundaryError("OpenACP core contains a duplicate Telegram command definition");
|
|
12017
|
+
seen.add(command.command);
|
|
12018
|
+
commands.push({ command: command.command, description: command.description.trim() });
|
|
12019
|
+
}
|
|
12020
|
+
const skipped = { invalidName: 0, invalidDescription: 0, duplicate: 0, overflow: 0 };
|
|
12021
|
+
const validPlugins = [];
|
|
12022
|
+
for (const command of registryCommands) {
|
|
12023
|
+
if (command.category !== "plugin") continue;
|
|
12024
|
+
if (!TELEGRAM_COMMAND_NAME.test(command.name)) {
|
|
12025
|
+
skipped.invalidName++;
|
|
12026
|
+
continue;
|
|
12027
|
+
}
|
|
12028
|
+
if (!validDescription(command.description)) {
|
|
12029
|
+
skipped.invalidDescription++;
|
|
12030
|
+
continue;
|
|
12031
|
+
}
|
|
12032
|
+
if (seen.has(command.name) || validPlugins.some((entry) => entry.command === command.name)) {
|
|
12033
|
+
skipped.duplicate++;
|
|
12034
|
+
continue;
|
|
12035
|
+
}
|
|
12036
|
+
validPlugins.push({ command: command.name, description: command.description.trim() });
|
|
12037
|
+
}
|
|
12038
|
+
validPlugins.sort((a, b) => a.command.localeCompare(b.command) || a.description.localeCompare(b.description));
|
|
12039
|
+
const capacity = TELEGRAM_COMMAND_LIMIT - commands.length;
|
|
12040
|
+
skipped.overflow = Math.max(0, validPlugins.length - capacity);
|
|
12041
|
+
commands.push(...validPlugins.slice(0, capacity));
|
|
12042
|
+
return { commands, skipped };
|
|
12043
|
+
}
|
|
12044
|
+
function assertTelegramCommandBoundary(commands) {
|
|
12045
|
+
if (commands.length > TELEGRAM_COMMAND_LIMIT) throw new TelegramCommandBoundaryError("Telegram command list exceeds 100 entries");
|
|
12046
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12047
|
+
for (const command of commands) {
|
|
12048
|
+
if (!TELEGRAM_COMMAND_NAME.test(command.command) || !validDescription(command.description) || seen.has(command.command)) {
|
|
12049
|
+
throw new TelegramCommandBoundaryError("Telegram command list failed validation before synchronization");
|
|
12050
|
+
}
|
|
12051
|
+
seen.add(command.command);
|
|
12052
|
+
}
|
|
12053
|
+
}
|
|
12054
|
+
function commandsEqual(current, desired) {
|
|
12055
|
+
return current.length === desired.length && current.every((command, index) => {
|
|
12056
|
+
const expected = desired[index];
|
|
12057
|
+
return command.command === expected?.command && command.description === expected.description;
|
|
12058
|
+
});
|
|
12059
|
+
}
|
|
12060
|
+
function throwIfAborted(signal) {
|
|
12061
|
+
if (signal?.aborted) throw signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
12062
|
+
}
|
|
12063
|
+
function localeLabel(locale) {
|
|
12064
|
+
return locale || "neutral";
|
|
12065
|
+
}
|
|
12066
|
+
async function synchronizeTelegramCommands(api, chatId, desired, options) {
|
|
12067
|
+
if (!/^\d{1,20}$/.test(options.botId)) throw new Error("Telegram bot identity is invalid");
|
|
12068
|
+
assertTelegramCommandBoundary(desired);
|
|
12069
|
+
const chatKey = createHash4("sha256").update(String(chatId)).digest("hex").slice(0, 16);
|
|
12070
|
+
const scopes = [
|
|
12071
|
+
{ name: "default", scope: { type: "default" }, ledgerName: "default" },
|
|
12072
|
+
{ name: "chat", scope: { type: "chat", chat_id: chatId }, ledgerName: `chat:${chatKey}` },
|
|
12073
|
+
{
|
|
12074
|
+
name: "chat_administrators",
|
|
12075
|
+
scope: { type: "chat_administrators", chat_id: chatId },
|
|
12076
|
+
ledgerName: `chat_administrators:${chatKey}`,
|
|
12077
|
+
onlyWhenPresent: true
|
|
12078
|
+
}
|
|
12079
|
+
];
|
|
12080
|
+
const desiredNames = new Set(desired.map((command) => command.command));
|
|
12081
|
+
const result = { updated: [], unchanged: [] };
|
|
12082
|
+
const initialConservative = await options.ownershipStore.withLock(async ({ ledger, conservative }) => {
|
|
12083
|
+
options.ownershipStore.claimOwner(ledger, options.botId, options.ownerIdentity, options.allowOwnerTakeover);
|
|
12084
|
+
options.ownershipStore.save(ledger);
|
|
12085
|
+
return conservative;
|
|
12086
|
+
});
|
|
12087
|
+
options.onOwnerClaimed?.();
|
|
12088
|
+
for (const locale of TELEGRAM_COMMAND_LOCALES) {
|
|
12089
|
+
for (const entry of scopes) {
|
|
12090
|
+
throwIfAborted(options.signal);
|
|
12091
|
+
const label = `${entry.name}:${localeLabel(locale)}`;
|
|
12092
|
+
const scopeKey = `${entry.ledgerName}|${localeLabel(locale)}`;
|
|
12093
|
+
const apiOptions = {
|
|
12094
|
+
scope: entry.scope,
|
|
12095
|
+
...locale ? { language_code: locale } : {}
|
|
12096
|
+
};
|
|
12097
|
+
const current = await api.getMyCommands(apiOptions, options.signal);
|
|
12098
|
+
throwIfAborted(options.signal);
|
|
12099
|
+
const ownership = await options.ownershipStore.withLock(async ({ ledger, conservative }) => ({
|
|
12100
|
+
priorOwned: options.ownershipStore.getOwned(ledger, options.botId, scopeKey),
|
|
12101
|
+
conservative
|
|
12102
|
+
}));
|
|
12103
|
+
if (entry.onlyWhenPresent && current.length === 0) {
|
|
12104
|
+
await options.ownershipStore.withLock(async ({ ledger }) => {
|
|
12105
|
+
options.ownershipStore.claimOwner(ledger, options.botId, options.ownerIdentity);
|
|
12106
|
+
options.ownershipStore.setOwned(ledger, options.botId, scopeKey, []);
|
|
12107
|
+
options.ownershipStore.save(ledger);
|
|
12108
|
+
});
|
|
12109
|
+
result.unchanged.push(label);
|
|
12110
|
+
continue;
|
|
12111
|
+
}
|
|
12112
|
+
const owned = new Set(ownership.priorOwned ?? (initialConservative || ownership.conservative ? [] : options.historicalOwnedNames));
|
|
12113
|
+
const unmanaged = current.filter((command) => !desiredNames.has(command.command) && !owned.has(command.command));
|
|
12114
|
+
const merged = [...desired, ...unmanaged];
|
|
12115
|
+
if (merged.length > 100) {
|
|
12116
|
+
throw new Error(`Telegram command scope ${label} has no capacity for OpenACP commands without deleting unmanaged commands`);
|
|
12117
|
+
}
|
|
12118
|
+
if (!commandsEqual(current, merged)) {
|
|
12119
|
+
throwIfAborted(options.signal);
|
|
12120
|
+
await api.setMyCommands(merged, apiOptions, options.signal);
|
|
12121
|
+
result.updated.push(label);
|
|
12122
|
+
} else {
|
|
12123
|
+
result.unchanged.push(label);
|
|
12124
|
+
}
|
|
12125
|
+
throwIfAborted(options.signal);
|
|
12126
|
+
await options.ownershipStore.withLock(async ({ ledger }) => {
|
|
12127
|
+
options.ownershipStore.claimOwner(ledger, options.botId, options.ownerIdentity);
|
|
12128
|
+
options.ownershipStore.setOwned(ledger, options.botId, scopeKey, [...desiredNames]);
|
|
12129
|
+
options.ownershipStore.save(ledger);
|
|
12130
|
+
});
|
|
12131
|
+
}
|
|
12132
|
+
}
|
|
12133
|
+
return result;
|
|
12134
|
+
}
|
|
12135
|
+
var TelegramCommandBoundaryError, TELEGRAM_COMMAND_NAME, TELEGRAM_COMMAND_LIMIT, TELEGRAM_DESCRIPTION_LIMIT;
|
|
12136
|
+
var init_command_sync = __esm({
|
|
12137
|
+
"src/plugins/telegram/command-sync.ts"() {
|
|
12138
|
+
"use strict";
|
|
12139
|
+
init_telegram_command_scopes();
|
|
12140
|
+
TelegramCommandBoundaryError = class extends Error {
|
|
12141
|
+
constructor(message) {
|
|
12142
|
+
super(message);
|
|
12143
|
+
this.name = "TelegramCommandBoundaryError";
|
|
12144
|
+
}
|
|
12145
|
+
};
|
|
12146
|
+
TELEGRAM_COMMAND_NAME = /^[a-z0-9_]{1,32}$/;
|
|
12147
|
+
TELEGRAM_COMMAND_LIMIT = 100;
|
|
12148
|
+
TELEGRAM_DESCRIPTION_LIMIT = 256;
|
|
12149
|
+
}
|
|
12150
|
+
});
|
|
12151
|
+
|
|
11400
12152
|
// src/plugins/telegram/validators.ts
|
|
11401
12153
|
var validators_exports = {};
|
|
11402
12154
|
__export(validators_exports, {
|
|
@@ -11471,7 +12223,7 @@ async function validateBotAdmin(token, chatId, telegramFetch) {
|
|
|
11471
12223
|
};
|
|
11472
12224
|
}
|
|
11473
12225
|
const { status } = data.result;
|
|
11474
|
-
|
|
12226
|
+
log37.info(
|
|
11475
12227
|
{ status, can_manage_topics: data.result.can_manage_topics, raw_result: data.result },
|
|
11476
12228
|
"validateBotAdmin: getChatMember raw result"
|
|
11477
12229
|
);
|
|
@@ -11491,7 +12243,7 @@ async function validateBotAdmin(token, chatId, telegramFetch) {
|
|
|
11491
12243
|
}
|
|
11492
12244
|
async function checkTopicsPrerequisites(token, chatId, telegramFetch) {
|
|
11493
12245
|
const issues = [];
|
|
11494
|
-
|
|
12246
|
+
log37.info({ chatId }, "checkTopicsPrerequisites: starting checks");
|
|
11495
12247
|
try {
|
|
11496
12248
|
const res = await telegramFetch(`https://api.telegram.org/bot${token}/getChat`, {
|
|
11497
12249
|
method: "POST",
|
|
@@ -11499,7 +12251,7 @@ async function checkTopicsPrerequisites(token, chatId, telegramFetch) {
|
|
|
11499
12251
|
body: JSON.stringify({ chat_id: chatId })
|
|
11500
12252
|
});
|
|
11501
12253
|
const data = await res.json();
|
|
11502
|
-
|
|
12254
|
+
log37.info(
|
|
11503
12255
|
{ chatId, apiOk: data.ok, is_forum: data.result?.is_forum, type: data.result?.type, title: data.result?.title },
|
|
11504
12256
|
"checkTopicsPrerequisites: getChat result"
|
|
11505
12257
|
);
|
|
@@ -11509,11 +12261,11 @@ async function checkTopicsPrerequisites(token, chatId, telegramFetch) {
|
|
|
11509
12261
|
);
|
|
11510
12262
|
}
|
|
11511
12263
|
} catch (err) {
|
|
11512
|
-
|
|
12264
|
+
log37.warn({ err, chatId }, "checkTopicsPrerequisites: getChat failed (network error)");
|
|
11513
12265
|
issues.push("\u274C Could not check if Topics are enabled (network error).");
|
|
11514
12266
|
}
|
|
11515
12267
|
const adminResult = await validateBotAdmin(token, chatId, telegramFetch);
|
|
11516
|
-
|
|
12268
|
+
log37.info(
|
|
11517
12269
|
{ chatId, adminOk: adminResult.ok, canManageTopics: adminResult.ok ? adminResult.canManageTopics : void 0, error: !adminResult.ok ? adminResult.error : void 0 },
|
|
11518
12270
|
"checkTopicsPrerequisites: validateBotAdmin result"
|
|
11519
12271
|
);
|
|
@@ -11528,17 +12280,17 @@ async function checkTopicsPrerequisites(token, chatId, telegramFetch) {
|
|
|
11528
12280
|
'\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
12281
|
);
|
|
11530
12282
|
}
|
|
11531
|
-
|
|
12283
|
+
log37.info({ chatId, issueCount: issues.length, ok: issues.length === 0 }, "checkTopicsPrerequisites: result");
|
|
11532
12284
|
if (issues.length > 0) return { ok: false, issues };
|
|
11533
12285
|
return { ok: true };
|
|
11534
12286
|
}
|
|
11535
|
-
var
|
|
12287
|
+
var log37;
|
|
11536
12288
|
var init_validators = __esm({
|
|
11537
12289
|
"src/plugins/telegram/validators.ts"() {
|
|
11538
12290
|
"use strict";
|
|
11539
12291
|
init_log();
|
|
11540
12292
|
init_network_redaction();
|
|
11541
|
-
|
|
12293
|
+
log37 = createChildLogger({ module: "telegram-validators" });
|
|
11542
12294
|
}
|
|
11543
12295
|
});
|
|
11544
12296
|
|
|
@@ -11557,7 +12309,7 @@ function patchedFetch(input2, init, delegate) {
|
|
|
11557
12309
|
}
|
|
11558
12310
|
return delegate(input2, init);
|
|
11559
12311
|
}
|
|
11560
|
-
var
|
|
12312
|
+
var log38, HISTORICAL_OPENACP_COMMAND_NAMES, TelegramAdapter;
|
|
11561
12313
|
var init_adapter = __esm({
|
|
11562
12314
|
"src/plugins/telegram/adapter.ts"() {
|
|
11563
12315
|
"use strict";
|
|
@@ -11577,7 +12329,20 @@ var init_adapter = __esm({
|
|
|
11577
12329
|
init_messaging_adapter();
|
|
11578
12330
|
init_renderer2();
|
|
11579
12331
|
init_output_mode_resolver();
|
|
11580
|
-
|
|
12332
|
+
init_proxy2();
|
|
12333
|
+
init_network_redaction();
|
|
12334
|
+
init_command_sync();
|
|
12335
|
+
init_command_ownership_store();
|
|
12336
|
+
init_instance_context();
|
|
12337
|
+
init_callback_navigation();
|
|
12338
|
+
log38 = createChildLogger({ module: "telegram" });
|
|
12339
|
+
HISTORICAL_OPENACP_COMMAND_NAMES = /* @__PURE__ */ new Set([
|
|
12340
|
+
...STATIC_COMMANDS.map((command) => command.command),
|
|
12341
|
+
"clear",
|
|
12342
|
+
"bypass",
|
|
12343
|
+
"usage",
|
|
12344
|
+
"summary"
|
|
12345
|
+
]);
|
|
11581
12346
|
TelegramAdapter = class extends MessagingAdapter {
|
|
11582
12347
|
name = "telegram";
|
|
11583
12348
|
renderer = new TelegramRenderer();
|
|
@@ -11606,6 +12371,7 @@ var init_adapter = __esm({
|
|
|
11606
12371
|
sessionTrackers = /* @__PURE__ */ new Map();
|
|
11607
12372
|
callbackCache = /* @__PURE__ */ new Map();
|
|
11608
12373
|
callbackCounter = 0;
|
|
12374
|
+
pendingCommandInputs = /* @__PURE__ */ new Map();
|
|
11609
12375
|
/** Pending skill commands queued when session.threadId was not yet set */
|
|
11610
12376
|
_pendingSkillCommands = /* @__PURE__ */ new Map();
|
|
11611
12377
|
/** Control message IDs per session (for updating status text/buttons) */
|
|
@@ -11630,6 +12396,23 @@ var init_adapter = __esm({
|
|
|
11630
12396
|
/** Set during normal shutdown so bot.stop() does not trigger a self-restart. */
|
|
11631
12397
|
_stopping = false;
|
|
11632
12398
|
unregisterProxyTester;
|
|
12399
|
+
/** Serializes command-list reconciliations triggered by startup and plugin hot reloads. */
|
|
12400
|
+
_commandSyncChain = Promise.resolve();
|
|
12401
|
+
_pendingCommandSnapshot;
|
|
12402
|
+
_commandSyncWorkerRunning = false;
|
|
12403
|
+
_commandSyncGeneration = 0;
|
|
12404
|
+
_commandSyncAbort;
|
|
12405
|
+
_commandSnapshotVersion = 0;
|
|
12406
|
+
_commandsReadyHandler;
|
|
12407
|
+
_latestCommandSnapshot;
|
|
12408
|
+
_commandApiReady = false;
|
|
12409
|
+
_initialCommandSyncScheduled = false;
|
|
12410
|
+
commandOwnershipStore = new TelegramCommandOwnershipStore(getGlobalRoot());
|
|
12411
|
+
commandOwnerIdentity;
|
|
12412
|
+
commandOwnerBotId;
|
|
12413
|
+
commandOwnerTakeoverRequested;
|
|
12414
|
+
_commandOwnerClaimed = false;
|
|
12415
|
+
_commandOwnerHeartbeat;
|
|
11633
12416
|
telegramFetch() {
|
|
11634
12417
|
return this.core.proxyService.createFetch("channels.telegram");
|
|
11635
12418
|
}
|
|
@@ -11704,6 +12487,14 @@ var init_adapter = __esm({
|
|
|
11704
12487
|
});
|
|
11705
12488
|
this.core = core;
|
|
11706
12489
|
this.telegramConfig = config;
|
|
12490
|
+
this.commandOwnerBotId = this.telegramConfig.botToken.split(":", 1)[0] ?? "";
|
|
12491
|
+
this.commandOwnerIdentity = {
|
|
12492
|
+
instanceId: this.core.instanceContext.id,
|
|
12493
|
+
instanceKey: telegramCommandInstanceKey(this.core.instanceContext.root),
|
|
12494
|
+
hostId: telegramCommandHostId(),
|
|
12495
|
+
pid: process.pid
|
|
12496
|
+
};
|
|
12497
|
+
this.commandOwnerTakeoverRequested = process.env.OPENACP_TELEGRAM_COMMAND_TAKEOVER === "1";
|
|
11707
12498
|
this.saveTopicIds = saveTopicIds;
|
|
11708
12499
|
this.unregisterProxyTester = this.core.proxyService.registerRouteTester(
|
|
11709
12500
|
"channels.telegram",
|
|
@@ -11716,6 +12507,35 @@ var init_adapter = __esm({
|
|
|
11716
12507
|
}
|
|
11717
12508
|
);
|
|
11718
12509
|
}
|
|
12510
|
+
/**
|
|
12511
|
+
* Capture command snapshots once startup begins. The normal boot path emits
|
|
12512
|
+
* SYSTEM_COMMANDS_READY before core.start(), so initial reconciliation reads the
|
|
12513
|
+
* authoritative registry state instead of relying on replay of that event. Once
|
|
12514
|
+
* the API is ready, later snapshots drive hot reloads.
|
|
12515
|
+
*/
|
|
12516
|
+
subscribeToCommandSnapshots() {
|
|
12517
|
+
if (this._commandsReadyHandler) return;
|
|
12518
|
+
this._commandsReadyHandler = ({ commands }) => {
|
|
12519
|
+
this._latestCommandSnapshot = commands;
|
|
12520
|
+
if (this._commandApiReady) this.syncCommandsWithRetry(commands);
|
|
12521
|
+
};
|
|
12522
|
+
this.core.eventBus.on(BusEvent.SYSTEM_COMMANDS_READY, this._commandsReadyHandler);
|
|
12523
|
+
}
|
|
12524
|
+
/** Schedule one authoritative initial reconciliation after grammY is available. */
|
|
12525
|
+
scheduleInitialCommandSync() {
|
|
12526
|
+
if (this._initialCommandSyncScheduled) return;
|
|
12527
|
+
this._initialCommandSyncScheduled = true;
|
|
12528
|
+
const registry = this.core.lifecycleManager?.serviceRegistry?.get("command-registry");
|
|
12529
|
+
const commands = registry?.getAll() ?? this._latestCommandSnapshot;
|
|
12530
|
+
if (!commands) {
|
|
12531
|
+
log38.warn(
|
|
12532
|
+
{ operation: "telegram-command-sync" },
|
|
12533
|
+
"Telegram command registry is not ready; waiting for a commands-ready snapshot"
|
|
12534
|
+
);
|
|
12535
|
+
return;
|
|
12536
|
+
}
|
|
12537
|
+
this.syncCommandsWithRetry(commands);
|
|
12538
|
+
}
|
|
11719
12539
|
/**
|
|
11720
12540
|
* Set up the grammY bot, register all callback and message handlers, then perform
|
|
11721
12541
|
* two-phase startup: Phase 1 starts polling immediately; Phase 2 checks group
|
|
@@ -11724,6 +12544,11 @@ var init_adapter = __esm({
|
|
|
11724
12544
|
*/
|
|
11725
12545
|
async start() {
|
|
11726
12546
|
this._stopping = false;
|
|
12547
|
+
this._commandSyncGeneration++;
|
|
12548
|
+
this._commandSyncAbort = new AbortController();
|
|
12549
|
+
this._pendingCommandSnapshot = void 0;
|
|
12550
|
+
this._commandSyncWorkerRunning = false;
|
|
12551
|
+
this.subscribeToCommandSnapshots();
|
|
11727
12552
|
this.bot = new Bot(this.telegramConfig.botToken, {
|
|
11728
12553
|
client: {
|
|
11729
12554
|
baseFetchConfig: { duplex: "half" },
|
|
@@ -11748,7 +12573,7 @@ var init_adapter = __esm({
|
|
|
11748
12573
|
);
|
|
11749
12574
|
this.bot.catch((err) => {
|
|
11750
12575
|
const rootCause = err.error instanceof Error ? err.error : err;
|
|
11751
|
-
|
|
12576
|
+
log38.error({ err: rootCause }, "Telegram bot error");
|
|
11752
12577
|
});
|
|
11753
12578
|
this.bot.api.config.use(async (prev, method, payload, signal) => {
|
|
11754
12579
|
const maxRetries = 3;
|
|
@@ -11766,7 +12591,7 @@ var init_adapter = __esm({
|
|
|
11766
12591
|
if (rateLimitedMethods.includes(method)) {
|
|
11767
12592
|
this.sendQueue.onRateLimited();
|
|
11768
12593
|
}
|
|
11769
|
-
|
|
12594
|
+
log38.warn(
|
|
11770
12595
|
{ method, retryAfter, attempt: attempt + 1 },
|
|
11771
12596
|
"Rate limited by Telegram, retrying"
|
|
11772
12597
|
);
|
|
@@ -11784,10 +12609,6 @@ var init_adapter = __esm({
|
|
|
11784
12609
|
}
|
|
11785
12610
|
return prev(method, payload, signal);
|
|
11786
12611
|
});
|
|
11787
|
-
const onCommandsReady = ({ commands }) => {
|
|
11788
|
-
this.syncCommandsWithRetry(commands);
|
|
11789
|
-
};
|
|
11790
|
-
this.core.eventBus.on(BusEvent.SYSTEM_COMMANDS_READY, onCommandsReady);
|
|
11791
12612
|
this.bot.use((ctx, next) => {
|
|
11792
12613
|
const chatId = ctx.chat?.id ?? ctx.callbackQuery?.message?.chat?.id;
|
|
11793
12614
|
if (chatId !== this.telegramConfig.chatId) return;
|
|
@@ -11801,7 +12622,58 @@ var init_adapter = __esm({
|
|
|
11801
12622
|
);
|
|
11802
12623
|
this.bot.on("message:text", async (ctx, next) => {
|
|
11803
12624
|
const text3 = ctx.message?.text;
|
|
11804
|
-
if (!text3
|
|
12625
|
+
if (!text3) return next();
|
|
12626
|
+
const topicIdForInput = ctx.message.message_thread_id;
|
|
12627
|
+
const inputKey = `${ctx.chat.id}:${ctx.from?.id ?? 0}:${topicIdForInput ?? 0}`;
|
|
12628
|
+
const pending = this.pendingCommandInputs.get(inputKey);
|
|
12629
|
+
if (!text3.startsWith("/") && pending) {
|
|
12630
|
+
if (pending.promptMessageId !== void 0 && ctx.message.reply_to_message?.message_id !== pending.promptMessageId) {
|
|
12631
|
+
return next();
|
|
12632
|
+
}
|
|
12633
|
+
this.pendingCommandInputs.delete(inputKey);
|
|
12634
|
+
if (pending.expiresAt <= Date.now()) {
|
|
12635
|
+
await ctx.reply("This input request expired. Start the operation again.").catch(() => {
|
|
12636
|
+
});
|
|
12637
|
+
return;
|
|
12638
|
+
}
|
|
12639
|
+
if (pending.sensitive) {
|
|
12640
|
+
try {
|
|
12641
|
+
await ctx.deleteMessage();
|
|
12642
|
+
} catch {
|
|
12643
|
+
await ctx.reply(`\u26A0\uFE0F I could not securely remove that message, so its value was not used. ${pending.fallback}`).catch(() => {
|
|
12644
|
+
});
|
|
12645
|
+
return;
|
|
12646
|
+
}
|
|
12647
|
+
}
|
|
12648
|
+
const registry2 = this.core.lifecycleManager?.serviceRegistry?.get("command-registry");
|
|
12649
|
+
if (!registry2) return;
|
|
12650
|
+
const sessionId = topicIdForInput != null ? (await this.core.getOrResumeSession("telegram", String(topicIdForInput)))?.id ?? null : null;
|
|
12651
|
+
const response = await registry2.execute(pending.command, {
|
|
12652
|
+
raw: "",
|
|
12653
|
+
sessionId,
|
|
12654
|
+
channelId: "telegram",
|
|
12655
|
+
userId: String(ctx.from?.id),
|
|
12656
|
+
conversationId: `${ctx.chat.id}:${topicIdForInput ?? 0}`,
|
|
12657
|
+
interaction: {
|
|
12658
|
+
textInput: true,
|
|
12659
|
+
secureInput: "delete-after-capture",
|
|
12660
|
+
capturedInput: { value: text3, sensitive: pending.sensitive }
|
|
12661
|
+
},
|
|
12662
|
+
reply: async () => {
|
|
12663
|
+
}
|
|
12664
|
+
});
|
|
12665
|
+
if (response.type !== "silent" && response.type !== "delegated") {
|
|
12666
|
+
await this.renderCommandResponse(
|
|
12667
|
+
response,
|
|
12668
|
+
ctx.chat.id,
|
|
12669
|
+
topicIdForInput,
|
|
12670
|
+
String(ctx.from?.id),
|
|
12671
|
+
pending.returnTarget
|
|
12672
|
+
);
|
|
12673
|
+
}
|
|
12674
|
+
return;
|
|
12675
|
+
}
|
|
12676
|
+
if (!text3.startsWith("/")) return next();
|
|
11805
12677
|
const rawCmd = text3.split(" ")[0].slice(1).split("@")[0].toLowerCase();
|
|
11806
12678
|
if (rawCmd === "retry") {
|
|
11807
12679
|
await this.handleRetryCommand(ctx);
|
|
@@ -11847,6 +12719,8 @@ var init_adapter = __esm({
|
|
|
11847
12719
|
sessionId,
|
|
11848
12720
|
channelId: "telegram",
|
|
11849
12721
|
userId: String(ctx.from?.id),
|
|
12722
|
+
conversationId: `${chatId}:${topicId ?? 0}`,
|
|
12723
|
+
interaction: { textInput: true, secureInput: "delete-after-capture" },
|
|
11850
12724
|
reply: async (content) => {
|
|
11851
12725
|
if (typeof content === "string") {
|
|
11852
12726
|
await ctx.reply(content);
|
|
@@ -11854,7 +12728,8 @@ var init_adapter = __esm({
|
|
|
11854
12728
|
await this.renderCommandResponse(
|
|
11855
12729
|
content,
|
|
11856
12730
|
chatId,
|
|
11857
|
-
topicId
|
|
12731
|
+
topicId,
|
|
12732
|
+
String(ctx.from?.id)
|
|
11858
12733
|
);
|
|
11859
12734
|
}
|
|
11860
12735
|
}
|
|
@@ -11865,7 +12740,7 @@ var init_adapter = __esm({
|
|
|
11865
12740
|
if (response.type === "silent") {
|
|
11866
12741
|
return next();
|
|
11867
12742
|
}
|
|
11868
|
-
await this.renderCommandResponse(response, chatId, topicId);
|
|
12743
|
+
await this.renderCommandResponse(response, chatId, topicId, String(ctx.from?.id));
|
|
11869
12744
|
} catch (err) {
|
|
11870
12745
|
await ctx.reply(`\u26A0\uFE0F Command failed: ${String(err)}`);
|
|
11871
12746
|
}
|
|
@@ -11877,7 +12752,8 @@ var init_adapter = __esm({
|
|
|
11877
12752
|
return;
|
|
11878
12753
|
}
|
|
11879
12754
|
const data = ctx.callbackQuery.data;
|
|
11880
|
-
const
|
|
12755
|
+
const callback = this.fromCallbackData(data);
|
|
12756
|
+
const command = callback.command;
|
|
11881
12757
|
const registry = this.core.lifecycleManager?.serviceRegistry?.get(
|
|
11882
12758
|
"command-registry"
|
|
11883
12759
|
);
|
|
@@ -11894,6 +12770,8 @@ var init_adapter = __esm({
|
|
|
11894
12770
|
sessionId,
|
|
11895
12771
|
channelId: "telegram",
|
|
11896
12772
|
userId: String(ctx.from?.id),
|
|
12773
|
+
conversationId: `${chatId}:${topicId ?? 0}`,
|
|
12774
|
+
interaction: { textInput: true, secureInput: "delete-after-capture" },
|
|
11897
12775
|
reply: async (content) => {
|
|
11898
12776
|
if (typeof content === "string") {
|
|
11899
12777
|
await ctx.editMessageText(content).catch(() => {
|
|
@@ -11902,14 +12780,25 @@ var init_adapter = __esm({
|
|
|
11902
12780
|
}
|
|
11903
12781
|
});
|
|
11904
12782
|
await ctx.answerCallbackQuery();
|
|
11905
|
-
if (response.type !== "silent") {
|
|
12783
|
+
if (response.type !== "silent" && response.type !== "delegated") {
|
|
12784
|
+
if (response.type === "input") {
|
|
12785
|
+
await this.renderCommandResponse(
|
|
12786
|
+
response,
|
|
12787
|
+
chatId,
|
|
12788
|
+
topicId,
|
|
12789
|
+
String(ctx.from?.id),
|
|
12790
|
+
callback.returnTarget
|
|
12791
|
+
);
|
|
12792
|
+
return;
|
|
12793
|
+
}
|
|
11906
12794
|
if (response.type === "menu") {
|
|
11907
12795
|
const keyboard = response.options.map((opt) => [
|
|
11908
12796
|
{
|
|
11909
12797
|
text: `${opt.label}${opt.hint ? ` \u2014 ${opt.hint}` : ""}`,
|
|
11910
|
-
callback_data: this.toCallbackData(opt.command)
|
|
12798
|
+
callback_data: this.toCallbackData(opt.command, callback.returnTarget)
|
|
11911
12799
|
}
|
|
11912
12800
|
]);
|
|
12801
|
+
this.appendReturnButton(keyboard, callback.returnTarget);
|
|
11913
12802
|
try {
|
|
11914
12803
|
await ctx.editMessageText(response.title, {
|
|
11915
12804
|
reply_markup: { inline_keyboard: keyboard }
|
|
@@ -11928,7 +12817,11 @@ var init_adapter = __esm({
|
|
|
11928
12817
|
parseMode = "Markdown";
|
|
11929
12818
|
}
|
|
11930
12819
|
try {
|
|
11931
|
-
|
|
12820
|
+
const returnMarkup = this.returnMarkup(callback.returnTarget);
|
|
12821
|
+
await ctx.editMessageText(text3, {
|
|
12822
|
+
...parseMode && { parse_mode: parseMode },
|
|
12823
|
+
...returnMarkup && { reply_markup: returnMarkup }
|
|
12824
|
+
});
|
|
11932
12825
|
} catch {
|
|
11933
12826
|
}
|
|
11934
12827
|
}
|
|
@@ -12010,32 +12903,34 @@ ${p}` : p;
|
|
|
12010
12903
|
}
|
|
12011
12904
|
);
|
|
12012
12905
|
this.setupRoutes();
|
|
12906
|
+
this._commandApiReady = true;
|
|
12907
|
+
this.scheduleInitialCommandSync();
|
|
12013
12908
|
void this.bot.start({
|
|
12014
12909
|
allowed_updates: ["message", "callback_query"],
|
|
12015
|
-
onStart: () =>
|
|
12910
|
+
onStart: () => log38.info({ chatId: this.telegramConfig.chatId }, "Telegram bot started")
|
|
12016
12911
|
}).catch((err) => {
|
|
12017
12912
|
if (this._stopping) return;
|
|
12018
12913
|
const inner = err?.error;
|
|
12019
12914
|
const rootCause = err instanceof Error ? err : inner instanceof Error ? inner : err;
|
|
12020
|
-
|
|
12915
|
+
log38.error({ err: rootCause }, "Telegram polling stopped unexpectedly");
|
|
12021
12916
|
if (this.core.requestRestart) {
|
|
12022
12917
|
const requestRestart = this.core.requestRestart;
|
|
12023
|
-
|
|
12918
|
+
log38.error("Restarting OpenACP because Telegram polling stopped");
|
|
12024
12919
|
setImmediate(() => {
|
|
12025
12920
|
if (this._stopping) return;
|
|
12026
12921
|
void requestRestart().catch((restartErr) => {
|
|
12027
|
-
|
|
12922
|
+
log38.error({ err: restartErr }, "OpenACP restart request failed after Telegram polling stopped");
|
|
12028
12923
|
});
|
|
12029
12924
|
});
|
|
12030
12925
|
} else {
|
|
12031
|
-
|
|
12926
|
+
log38.error("Exiting because Telegram polling stopped and no restart hook is available");
|
|
12032
12927
|
setImmediate(() => {
|
|
12033
12928
|
if (this._stopping) return;
|
|
12034
12929
|
process.exit(1);
|
|
12035
12930
|
});
|
|
12036
12931
|
}
|
|
12037
12932
|
});
|
|
12038
|
-
|
|
12933
|
+
log38.info(
|
|
12039
12934
|
{
|
|
12040
12935
|
chatId: this.telegramConfig.chatId,
|
|
12041
12936
|
notificationTopicId: this.telegramConfig.notificationTopicId,
|
|
@@ -12050,12 +12945,12 @@ ${p}` : p;
|
|
|
12050
12945
|
this.telegramFetch()
|
|
12051
12946
|
);
|
|
12052
12947
|
if (prereqResult.ok) {
|
|
12053
|
-
|
|
12948
|
+
log38.info("Telegram adapter: prerequisites OK, initializing topic-dependent features");
|
|
12054
12949
|
await this.initTopicDependentFeatures();
|
|
12055
12950
|
} else {
|
|
12056
|
-
|
|
12951
|
+
log38.warn({ issues: prereqResult.issues }, "Telegram adapter: prerequisites NOT met, starting watcher");
|
|
12057
12952
|
for (const issue of prereqResult.issues) {
|
|
12058
|
-
|
|
12953
|
+
log38.warn({ issue }, "Telegram prerequisite not met");
|
|
12059
12954
|
}
|
|
12060
12955
|
this.startPrerequisiteWatcher(prereqResult.issues);
|
|
12061
12956
|
}
|
|
@@ -12071,7 +12966,7 @@ ${p}` : p;
|
|
|
12071
12966
|
} catch (err) {
|
|
12072
12967
|
if (attempt === maxRetries) throw err;
|
|
12073
12968
|
const delay = baseDelayMs * Math.pow(2, attempt - 1);
|
|
12074
|
-
|
|
12969
|
+
log38.warn(
|
|
12075
12970
|
{ err, attempt, maxRetries, delayMs: delay, operation: label },
|
|
12076
12971
|
`${label} failed, retrying in ${delay}ms`
|
|
12077
12972
|
);
|
|
@@ -12086,21 +12981,126 @@ ${p}` : p;
|
|
|
12086
12981
|
* from the registry, deduplicating by command name. Non-critical.
|
|
12087
12982
|
*/
|
|
12088
12983
|
syncCommandsWithRetry(registryCommands) {
|
|
12089
|
-
|
|
12090
|
-
|
|
12091
|
-
|
|
12092
|
-
this.
|
|
12093
|
-
|
|
12094
|
-
|
|
12095
|
-
|
|
12096
|
-
|
|
12097
|
-
|
|
12098
|
-
|
|
12984
|
+
this._pendingCommandSnapshot = registryCommands;
|
|
12985
|
+
this._commandSnapshotVersion++;
|
|
12986
|
+
if (this._commandSyncWorkerRunning) return;
|
|
12987
|
+
this._commandSyncWorkerRunning = true;
|
|
12988
|
+
const generation = this._commandSyncGeneration;
|
|
12989
|
+
const signal = this._commandSyncAbort?.signal;
|
|
12990
|
+
this._commandSyncChain = (async () => {
|
|
12991
|
+
while (this._pendingCommandSnapshot && generation === this._commandSyncGeneration && !signal?.aborted) {
|
|
12992
|
+
const snapshot = this._pendingCommandSnapshot;
|
|
12993
|
+
const snapshotVersion = this._commandSnapshotVersion;
|
|
12994
|
+
this._pendingCommandSnapshot = void 0;
|
|
12995
|
+
let allCommands;
|
|
12996
|
+
try {
|
|
12997
|
+
const boundary = prepareTelegramCommandBoundary(STATIC_COMMANDS, snapshot);
|
|
12998
|
+
allCommands = boundary.commands;
|
|
12999
|
+
const { invalidName, invalidDescription, duplicate, overflow } = boundary.skipped;
|
|
13000
|
+
if (invalidName || invalidDescription || duplicate || overflow) {
|
|
13001
|
+
log38.warn(
|
|
13002
|
+
{ invalidName, invalidDescription, duplicate, overflow, operation: "telegram-command-boundary" },
|
|
13003
|
+
"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."
|
|
13004
|
+
);
|
|
13005
|
+
}
|
|
13006
|
+
if (!allCommands.some((command) => command.command === "proxy")) {
|
|
13007
|
+
throw new TelegramCommandBoundaryError("Required OpenACP command /proxy is missing");
|
|
13008
|
+
}
|
|
13009
|
+
} catch (error) {
|
|
13010
|
+
log38.error(
|
|
13011
|
+
{ error: redactNetworkSecrets(error instanceof Error ? error.message : String(error)), operation: "telegram-command-boundary" },
|
|
13012
|
+
"OpenACP core Telegram command definitions are invalid; command sync was skipped without changing Telegram state"
|
|
13013
|
+
);
|
|
13014
|
+
continue;
|
|
13015
|
+
}
|
|
13016
|
+
const maxAttempts = 5;
|
|
13017
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
13018
|
+
if (generation !== this._commandSyncGeneration || signal?.aborted) return;
|
|
13019
|
+
try {
|
|
13020
|
+
const result = await synchronizeTelegramCommands(
|
|
13021
|
+
this.bot.api,
|
|
13022
|
+
this.telegramConfig.chatId,
|
|
13023
|
+
allCommands,
|
|
13024
|
+
{
|
|
13025
|
+
ownershipStore: this.commandOwnershipStore,
|
|
13026
|
+
botId: this.commandOwnerBotId,
|
|
13027
|
+
ownerIdentity: this.commandOwnerIdentity,
|
|
13028
|
+
allowOwnerTakeover: this.commandOwnerTakeoverRequested,
|
|
13029
|
+
onOwnerClaimed: () => {
|
|
13030
|
+
this._commandOwnerClaimed = true;
|
|
13031
|
+
this.startCommandOwnerHeartbeat();
|
|
13032
|
+
},
|
|
13033
|
+
historicalOwnedNames: HISTORICAL_OPENACP_COMMAND_NAMES,
|
|
13034
|
+
signal
|
|
13035
|
+
}
|
|
13036
|
+
);
|
|
13037
|
+
if (generation !== this._commandSyncGeneration || signal?.aborted) return;
|
|
13038
|
+
log38.info(
|
|
13039
|
+
{ updatedScopes: result.updated, unchangedScopes: result.unchanged },
|
|
13040
|
+
"Telegram command menus synchronized"
|
|
13041
|
+
);
|
|
13042
|
+
break;
|
|
13043
|
+
} catch (err) {
|
|
13044
|
+
if (generation !== this._commandSyncGeneration || signal?.aborted) return;
|
|
13045
|
+
if (err instanceof TelegramCommandOwnerConflictError) {
|
|
13046
|
+
log38.warn(
|
|
13047
|
+
{ code: err.code, ownerInstanceId: err.ownerInstanceId, instanceId: this.commandOwnerIdentity.instanceId, operation: "telegram-command-sync" },
|
|
13048
|
+
"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."
|
|
13049
|
+
);
|
|
13050
|
+
break;
|
|
13051
|
+
}
|
|
13052
|
+
if (snapshotVersion !== this._commandSnapshotVersion && this._pendingCommandSnapshot) break;
|
|
13053
|
+
const safeError2 = redactNetworkSecrets(err instanceof Error ? err.message : String(err));
|
|
13054
|
+
if (attempt === maxAttempts) {
|
|
13055
|
+
log38.warn(
|
|
13056
|
+
{ error: safeError2, attempt, maxAttempts, operation: "telegram-command-sync" },
|
|
13057
|
+
"Telegram command menu sync failed; bot remains online. Run /doctor and inspect Telegram connectivity"
|
|
13058
|
+
);
|
|
13059
|
+
break;
|
|
13060
|
+
}
|
|
13061
|
+
const delayMs = 2e3 * Math.pow(2, attempt - 1);
|
|
13062
|
+
log38.warn(
|
|
13063
|
+
{ error: safeError2, attempt, maxAttempts, delayMs, operation: "telegram-command-sync" },
|
|
13064
|
+
"Telegram command menu sync failed, retrying"
|
|
13065
|
+
);
|
|
13066
|
+
await new Promise((resolve7) => {
|
|
13067
|
+
const timer = setTimeout(resolve7, delayMs);
|
|
13068
|
+
signal?.addEventListener("abort", () => {
|
|
13069
|
+
clearTimeout(timer);
|
|
13070
|
+
resolve7();
|
|
13071
|
+
}, { once: true });
|
|
13072
|
+
});
|
|
13073
|
+
}
|
|
13074
|
+
}
|
|
13075
|
+
}
|
|
13076
|
+
})().finally(() => {
|
|
13077
|
+
this._commandSyncWorkerRunning = false;
|
|
13078
|
+
if (this._pendingCommandSnapshot && generation === this._commandSyncGeneration && !signal?.aborted) {
|
|
13079
|
+
this.syncCommandsWithRetry(this._pendingCommandSnapshot);
|
|
13080
|
+
}
|
|
12099
13081
|
});
|
|
12100
13082
|
}
|
|
13083
|
+
startCommandOwnerHeartbeat() {
|
|
13084
|
+
if (this._commandOwnerHeartbeat) return;
|
|
13085
|
+
this._commandOwnerHeartbeat = setInterval(() => {
|
|
13086
|
+
void this.commandOwnershipStore.heartbeatOwner(this.commandOwnerBotId, this.commandOwnerIdentity).then((owned) => {
|
|
13087
|
+
if (!owned) {
|
|
13088
|
+
this._commandOwnerClaimed = false;
|
|
13089
|
+
log38.warn(
|
|
13090
|
+
{ instanceId: this.commandOwnerIdentity.instanceId, operation: "telegram-command-owner-heartbeat" },
|
|
13091
|
+
"Telegram command-sync ownership was lost; no further command-list writes will be attempted until ownership is resolved."
|
|
13092
|
+
);
|
|
13093
|
+
}
|
|
13094
|
+
}).catch((error) => log38.warn(
|
|
13095
|
+
{ error: redactNetworkSecrets(error instanceof Error ? error.message : String(error)), operation: "telegram-command-owner-heartbeat" },
|
|
13096
|
+
"Could not refresh Telegram command-sync ownership heartbeat"
|
|
13097
|
+
));
|
|
13098
|
+
}, 3e4);
|
|
13099
|
+
this._commandOwnerHeartbeat.unref?.();
|
|
13100
|
+
}
|
|
12101
13101
|
async initTopicDependentFeatures() {
|
|
12102
13102
|
if (this._topicsInitialized) return;
|
|
12103
|
-
|
|
13103
|
+
log38.info(
|
|
12104
13104
|
{ notificationTopicId: this.telegramConfig.notificationTopicId, assistantTopicId: this.telegramConfig.assistantTopicId },
|
|
12105
13105
|
"initTopicDependentFeatures: starting (existing IDs in config)"
|
|
12106
13106
|
);
|
|
@@ -12125,7 +13125,7 @@ ${p}` : p;
|
|
|
12125
13125
|
this.assistantTopicId = topics.assistantTopicId;
|
|
12126
13126
|
this._systemTopicIds.notificationTopicId = topics.notificationTopicId;
|
|
12127
13127
|
this._systemTopicIds.assistantTopicId = topics.assistantTopicId;
|
|
12128
|
-
|
|
13128
|
+
log38.info(
|
|
12129
13129
|
{ notificationTopicId: this.notificationTopicId, assistantTopicId: this.assistantTopicId },
|
|
12130
13130
|
"initTopicDependentFeatures: topics ready"
|
|
12131
13131
|
);
|
|
@@ -12156,7 +13156,7 @@ ${p}` : p;
|
|
|
12156
13156
|
).then((msg) => {
|
|
12157
13157
|
if (msg) this.storeControlMsgId(sessionId, msg.message_id);
|
|
12158
13158
|
}).catch((err) => {
|
|
12159
|
-
|
|
13159
|
+
log38.warn({ err, sessionId }, "Failed to send initial messages for new session");
|
|
12160
13160
|
});
|
|
12161
13161
|
};
|
|
12162
13162
|
this.core.eventBus.on(BusEvent.SESSION_THREAD_READY, this._threadReadyHandler);
|
|
@@ -12198,7 +13198,7 @@ ${p}` : p;
|
|
|
12198
13198
|
this._queueNotifications.delete(data.turnId);
|
|
12199
13199
|
if (result) {
|
|
12200
13200
|
this.bot.api.deleteMessage(this.telegramConfig.chatId, result.message_id).catch((err) => {
|
|
12201
|
-
|
|
13201
|
+
log38.warn({ err }, "Failed to delete queue notification after race");
|
|
12202
13202
|
});
|
|
12203
13203
|
}
|
|
12204
13204
|
} else if (result) {
|
|
@@ -12208,7 +13208,7 @@ ${p}` : p;
|
|
|
12208
13208
|
}
|
|
12209
13209
|
} catch (err) {
|
|
12210
13210
|
this._queueNotifications.delete(data.turnId);
|
|
12211
|
-
|
|
13211
|
+
log38.warn({ err }, "Failed to send queue notification");
|
|
12212
13212
|
}
|
|
12213
13213
|
});
|
|
12214
13214
|
this.core.eventBus.on(BusEvent.MESSAGE_PROCESSING, async (data) => {
|
|
@@ -12217,13 +13217,13 @@ ${p}` : p;
|
|
|
12217
13217
|
if (typeof entry === "number") {
|
|
12218
13218
|
this._queueNotifications.delete(data.turnId);
|
|
12219
13219
|
this.bot.api.deleteMessage(this.telegramConfig.chatId, entry).catch((err) => {
|
|
12220
|
-
|
|
13220
|
+
log38.warn({ err }, "Failed to delete queue notification on processing");
|
|
12221
13221
|
});
|
|
12222
13222
|
} else if (entry === "pending") {
|
|
12223
13223
|
this._queueNotifications.set(data.turnId, "delete");
|
|
12224
13224
|
}
|
|
12225
13225
|
});
|
|
12226
|
-
|
|
13226
|
+
log38.info({ assistantTopicId: this.assistantTopicId }, "initTopicDependentFeatures: sending welcome message");
|
|
12227
13227
|
try {
|
|
12228
13228
|
const config = this.core.configManager.get();
|
|
12229
13229
|
const agents = this.core.agentManager.getAvailableAgents();
|
|
@@ -12245,17 +13245,17 @@ ${p}` : p;
|
|
|
12245
13245
|
this.core.lifecycleManager?.serviceRegistry?.get("menu-registry")
|
|
12246
13246
|
)
|
|
12247
13247
|
});
|
|
12248
|
-
|
|
13248
|
+
log38.info("initTopicDependentFeatures: welcome message sent");
|
|
12249
13249
|
} catch (err) {
|
|
12250
|
-
|
|
13250
|
+
log38.warn({ err }, "Failed to send welcome message");
|
|
12251
13251
|
}
|
|
12252
13252
|
try {
|
|
12253
13253
|
await this.core.assistantManager.getOrSpawn("telegram", String(this.assistantTopicId));
|
|
12254
13254
|
} catch (err) {
|
|
12255
|
-
|
|
13255
|
+
log38.error({ err }, "Failed to spawn assistant");
|
|
12256
13256
|
}
|
|
12257
13257
|
this._topicsInitialized = true;
|
|
12258
|
-
|
|
13258
|
+
log38.info("Telegram adapter fully initialized");
|
|
12259
13259
|
}
|
|
12260
13260
|
/**
|
|
12261
13261
|
* Handle the /retry command — re-runs prerequisite checks immediately.
|
|
@@ -12292,7 +13292,7 @@ ${p}` : p;
|
|
|
12292
13292
|
).catch(() => {
|
|
12293
13293
|
});
|
|
12294
13294
|
} catch (err) {
|
|
12295
|
-
|
|
13295
|
+
log38.error({ err }, "handleRetryCommand: init failed after prerequisites met");
|
|
12296
13296
|
await ctx.reply("\u26A0\uFE0F Prerequisites are met but initialization failed. Check server logs and try /retry again.").catch(() => {
|
|
12297
13297
|
});
|
|
12298
13298
|
}
|
|
@@ -12318,7 +13318,7 @@ OpenACP will automatically retry every 30 seconds. After fixing the issues above
|
|
|
12318
13318
|
this.bot.api.sendMessage(this.telegramConfig.chatId, setupMessage, {
|
|
12319
13319
|
parse_mode: "HTML"
|
|
12320
13320
|
}).catch((err) => {
|
|
12321
|
-
|
|
13321
|
+
log38.warn({ err }, "Failed to send setup guidance to General topic");
|
|
12322
13322
|
});
|
|
12323
13323
|
const schedule = [5e3, 1e4, 3e4];
|
|
12324
13324
|
let attempt = 1;
|
|
@@ -12331,7 +13331,7 @@ OpenACP will automatically retry every 30 seconds. After fixing the issues above
|
|
|
12331
13331
|
this.telegramFetch()
|
|
12332
13332
|
);
|
|
12333
13333
|
if (result.ok) {
|
|
12334
|
-
|
|
13334
|
+
log38.info("Prerequisites met \u2014 completing Telegram adapter initialization");
|
|
12335
13335
|
try {
|
|
12336
13336
|
await this.initTopicDependentFeatures();
|
|
12337
13337
|
this._prerequisiteWatcher = null;
|
|
@@ -12344,14 +13344,14 @@ System topics have been created. Tap${assistantTopicLink} to get started.`,
|
|
|
12344
13344
|
{ parse_mode: "HTML" }
|
|
12345
13345
|
);
|
|
12346
13346
|
} catch (err) {
|
|
12347
|
-
|
|
13347
|
+
log38.error({ err }, "Failed to complete initialization after prerequisites met \u2014 will retry");
|
|
12348
13348
|
const delay2 = schedule[Math.min(attempt, schedule.length - 1)];
|
|
12349
13349
|
attempt++;
|
|
12350
13350
|
this._prerequisiteWatcher = setTimeout(retry, delay2);
|
|
12351
13351
|
}
|
|
12352
13352
|
return;
|
|
12353
13353
|
}
|
|
12354
|
-
|
|
13354
|
+
log38.debug({ issues: result.issues }, "Prerequisites not yet met, retrying");
|
|
12355
13355
|
const delay = schedule[Math.min(attempt, schedule.length - 1)];
|
|
12356
13356
|
attempt++;
|
|
12357
13357
|
this._prerequisiteWatcher = setTimeout(retry, delay);
|
|
@@ -12367,6 +13367,14 @@ System topics have been created. Tap${assistantTopicLink} to get started.`,
|
|
|
12367
13367
|
*/
|
|
12368
13368
|
async stop() {
|
|
12369
13369
|
this._stopping = true;
|
|
13370
|
+
this._commandSyncGeneration++;
|
|
13371
|
+
this._commandSyncAbort?.abort();
|
|
13372
|
+
this._commandSyncAbort = void 0;
|
|
13373
|
+
this._pendingCommandSnapshot = void 0;
|
|
13374
|
+
if (this._commandOwnerHeartbeat) {
|
|
13375
|
+
clearInterval(this._commandOwnerHeartbeat);
|
|
13376
|
+
this._commandOwnerHeartbeat = void 0;
|
|
13377
|
+
}
|
|
12370
13378
|
this.unregisterProxyTester?.();
|
|
12371
13379
|
this.unregisterProxyTester = void 0;
|
|
12372
13380
|
if (this._prerequisiteWatcher !== null) {
|
|
@@ -12385,16 +13393,42 @@ System topics have been created. Tap${assistantTopicLink} to get started.`,
|
|
|
12385
13393
|
this.core.eventBus.off("session:configChanged", this._configChangedHandler);
|
|
12386
13394
|
this._configChangedHandler = void 0;
|
|
12387
13395
|
}
|
|
13396
|
+
if (this._commandsReadyHandler) {
|
|
13397
|
+
this.core.eventBus.off(BusEvent.SYSTEM_COMMANDS_READY, this._commandsReadyHandler);
|
|
13398
|
+
this._commandsReadyHandler = void 0;
|
|
13399
|
+
}
|
|
13400
|
+
this._commandApiReady = false;
|
|
13401
|
+
this._initialCommandSyncScheduled = false;
|
|
13402
|
+
this._latestCommandSnapshot = void 0;
|
|
12388
13403
|
this.sendQueue.clear();
|
|
12389
|
-
|
|
12390
|
-
|
|
13404
|
+
this.pendingCommandInputs.clear();
|
|
13405
|
+
this.callbackCache.clear();
|
|
13406
|
+
clearProxyDraftsForChannel("telegram");
|
|
13407
|
+
try {
|
|
13408
|
+
await this.bot.stop();
|
|
13409
|
+
} finally {
|
|
13410
|
+
if (this._commandOwnerClaimed) {
|
|
13411
|
+
try {
|
|
13412
|
+
await this.commandOwnershipStore.releaseOwner(this.commandOwnerBotId, this.commandOwnerIdentity);
|
|
13413
|
+
} catch (error) {
|
|
13414
|
+
log38.warn(
|
|
13415
|
+
{ error: redactNetworkSecrets(error instanceof Error ? error.message : String(error)), operation: "telegram-command-owner-release" },
|
|
13416
|
+
"Could not mark Telegram command-sync ownership as stopped; a same-host explicit takeover may be required on restart"
|
|
13417
|
+
);
|
|
13418
|
+
}
|
|
13419
|
+
this._commandOwnerClaimed = false;
|
|
13420
|
+
}
|
|
13421
|
+
}
|
|
13422
|
+
log38.info("Telegram bot stopped");
|
|
12391
13423
|
}
|
|
12392
13424
|
// --- CommandRegistry response rendering ---
|
|
12393
|
-
async renderCommandResponse(response, chatId, topicId) {
|
|
13425
|
+
async renderCommandResponse(response, chatId, topicId, userId, returnTarget) {
|
|
13426
|
+
const returnMarkup = this.returnMarkup(returnTarget);
|
|
12394
13427
|
switch (response.type) {
|
|
12395
13428
|
case "text":
|
|
12396
13429
|
await this.bot.api.sendMessage(chatId, response.text, {
|
|
12397
|
-
message_thread_id: topicId
|
|
13430
|
+
message_thread_id: topicId,
|
|
13431
|
+
...returnMarkup && { reply_markup: returnMarkup }
|
|
12398
13432
|
});
|
|
12399
13433
|
break;
|
|
12400
13434
|
case "adaptive": {
|
|
@@ -12402,7 +13436,8 @@ System topics have been created. Tap${assistantTopicLink} to get started.`,
|
|
|
12402
13436
|
const text3 = variant?.text ?? response.fallback;
|
|
12403
13437
|
await this.bot.api.sendMessage(chatId, text3, {
|
|
12404
13438
|
message_thread_id: topicId,
|
|
12405
|
-
...variant?.parse_mode && { parse_mode: variant.parse_mode }
|
|
13439
|
+
...variant?.parse_mode && { parse_mode: variant.parse_mode },
|
|
13440
|
+
...returnMarkup && { reply_markup: returnMarkup }
|
|
12406
13441
|
});
|
|
12407
13442
|
break;
|
|
12408
13443
|
}
|
|
@@ -12410,16 +13445,17 @@ System topics have been created. Tap${assistantTopicLink} to get started.`,
|
|
|
12410
13445
|
await this.bot.api.sendMessage(
|
|
12411
13446
|
chatId,
|
|
12412
13447
|
`\u26A0\uFE0F ${response.message}`,
|
|
12413
|
-
{ message_thread_id: topicId }
|
|
13448
|
+
{ message_thread_id: topicId, ...returnMarkup && { reply_markup: returnMarkup } }
|
|
12414
13449
|
);
|
|
12415
13450
|
break;
|
|
12416
13451
|
case "menu": {
|
|
12417
13452
|
const keyboard = response.options.map((opt) => [
|
|
12418
13453
|
{
|
|
12419
13454
|
text: `${opt.label}${opt.hint ? ` \u2014 ${opt.hint}` : ""}`,
|
|
12420
|
-
callback_data: this.toCallbackData(opt.command)
|
|
13455
|
+
callback_data: this.toCallbackData(opt.command, returnTarget)
|
|
12421
13456
|
}
|
|
12422
13457
|
]);
|
|
13458
|
+
this.appendReturnButton(keyboard, returnTarget);
|
|
12423
13459
|
await this.bot.api.sendMessage(chatId, response.title, {
|
|
12424
13460
|
message_thread_id: topicId,
|
|
12425
13461
|
reply_markup: { inline_keyboard: keyboard }
|
|
@@ -12433,7 +13469,8 @@ System topics have been created. Tap${assistantTopicLink} to get started.`,
|
|
|
12433
13469
|
const text3 = `${response.title}
|
|
12434
13470
|
${lines.join("\n")}`;
|
|
12435
13471
|
await this.bot.api.sendMessage(chatId, text3, {
|
|
12436
|
-
message_thread_id: topicId
|
|
13472
|
+
message_thread_id: topicId,
|
|
13473
|
+
...returnMarkup && { reply_markup: returnMarkup }
|
|
12437
13474
|
});
|
|
12438
13475
|
break;
|
|
12439
13476
|
}
|
|
@@ -12442,31 +13479,68 @@ ${lines.join("\n")}`;
|
|
|
12442
13479
|
[
|
|
12443
13480
|
{
|
|
12444
13481
|
text: "\u2705 Yes",
|
|
12445
|
-
callback_data: this.toCallbackData(response.onYes)
|
|
13482
|
+
callback_data: this.toCallbackData(response.onYes, returnTarget)
|
|
12446
13483
|
}
|
|
12447
13484
|
]
|
|
12448
13485
|
];
|
|
12449
13486
|
if (response.onNo) {
|
|
12450
13487
|
buttons[0].push({
|
|
12451
13488
|
text: "\u274C No",
|
|
12452
|
-
callback_data: this.toCallbackData(response.onNo)
|
|
13489
|
+
callback_data: this.toCallbackData(response.onNo, returnTarget)
|
|
12453
13490
|
});
|
|
12454
13491
|
}
|
|
13492
|
+
this.appendReturnButton(buttons, returnTarget);
|
|
12455
13493
|
await this.bot.api.sendMessage(chatId, response.question, {
|
|
12456
13494
|
message_thread_id: topicId,
|
|
12457
13495
|
reply_markup: { inline_keyboard: buttons }
|
|
12458
13496
|
});
|
|
12459
13497
|
break;
|
|
12460
13498
|
}
|
|
13499
|
+
case "input": {
|
|
13500
|
+
if (!userId) {
|
|
13501
|
+
await this.bot.api.sendMessage(chatId, response.fallback, {
|
|
13502
|
+
message_thread_id: topicId,
|
|
13503
|
+
...returnMarkup && { reply_markup: returnMarkup }
|
|
13504
|
+
});
|
|
13505
|
+
break;
|
|
13506
|
+
}
|
|
13507
|
+
const key = `${chatId}:${userId}:${topicId ?? 0}`;
|
|
13508
|
+
for (const [pendingKey, pending] of this.pendingCommandInputs) {
|
|
13509
|
+
if (pending.expiresAt <= Date.now()) this.pendingCommandInputs.delete(pendingKey);
|
|
13510
|
+
}
|
|
13511
|
+
while (this.pendingCommandInputs.size >= 1e3) {
|
|
13512
|
+
const oldest = this.pendingCommandInputs.keys().next().value;
|
|
13513
|
+
if (!oldest) break;
|
|
13514
|
+
this.pendingCommandInputs.delete(oldest);
|
|
13515
|
+
}
|
|
13516
|
+
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." : "";
|
|
13517
|
+
const promptMessage = await this.bot.api.sendMessage(chatId, `${response.prompt}${warning}`, {
|
|
13518
|
+
message_thread_id: topicId,
|
|
13519
|
+
reply_markup: { force_reply: true, selective: true }
|
|
13520
|
+
});
|
|
13521
|
+
this.pendingCommandInputs.set(key, {
|
|
13522
|
+
command: response.command,
|
|
13523
|
+
sensitive: response.sensitive === true,
|
|
13524
|
+
fallback: response.fallback,
|
|
13525
|
+
expiresAt: Date.now() + (response.expiresInMs ?? 10 * 6e4),
|
|
13526
|
+
promptMessageId: promptMessage.message_id,
|
|
13527
|
+
returnTarget
|
|
13528
|
+
});
|
|
13529
|
+
break;
|
|
13530
|
+
}
|
|
12461
13531
|
case "silent":
|
|
12462
13532
|
break;
|
|
12463
13533
|
}
|
|
12464
13534
|
}
|
|
12465
|
-
toCallbackData(command) {
|
|
13535
|
+
toCallbackData(command, returnTarget) {
|
|
12466
13536
|
const data = `c/${command}`;
|
|
12467
|
-
if (data
|
|
13537
|
+
if (!returnTarget && Buffer.byteLength(data, "utf8") <= 64) return data;
|
|
13538
|
+
if (returnTarget) {
|
|
13539
|
+
const contextual = contextualCommandCallback(command, returnTarget);
|
|
13540
|
+
if (contextual) return contextual;
|
|
13541
|
+
}
|
|
12468
13542
|
const id = String(++this.callbackCounter);
|
|
12469
|
-
this.callbackCache.set(id, command);
|
|
13543
|
+
this.callbackCache.set(id, { command, returnTarget });
|
|
12470
13544
|
if (this.callbackCache.size > 1e3) {
|
|
12471
13545
|
const first = this.callbackCache.keys().next().value;
|
|
12472
13546
|
if (first) this.callbackCache.delete(first);
|
|
@@ -12475,9 +13549,15 @@ ${lines.join("\n")}`;
|
|
|
12475
13549
|
}
|
|
12476
13550
|
fromCallbackData(data) {
|
|
12477
13551
|
if (data.startsWith("c/#")) {
|
|
12478
|
-
return this.callbackCache.get(data.slice(3)) ?? data.slice(2);
|
|
13552
|
+
return this.callbackCache.get(data.slice(3)) ?? { command: data.slice(2) };
|
|
12479
13553
|
}
|
|
12480
|
-
return data
|
|
13554
|
+
return decodeCommandCallback(data);
|
|
13555
|
+
}
|
|
13556
|
+
returnMarkup(returnTarget) {
|
|
13557
|
+
return returnTarget ? { inline_keyboard: [[returnButton(returnTarget)]] } : void 0;
|
|
13558
|
+
}
|
|
13559
|
+
appendReturnButton(keyboard, returnTarget) {
|
|
13560
|
+
if (returnTarget) keyboard.push([returnButton(returnTarget)]);
|
|
12481
13561
|
}
|
|
12482
13562
|
setupRoutes() {
|
|
12483
13563
|
this.bot.on("message:text", async (ctx) => {
|
|
@@ -12532,7 +13612,7 @@ ${lines.join("\n")}`;
|
|
|
12532
13612
|
username: ctx.from.username
|
|
12533
13613
|
}
|
|
12534
13614
|
}
|
|
12535
|
-
).catch((err) =>
|
|
13615
|
+
).catch((err) => log38.error({ err }, "handleMessage error"));
|
|
12536
13616
|
});
|
|
12537
13617
|
this.bot.on("message:photo", async (ctx) => {
|
|
12538
13618
|
const threadId = ctx.message.message_thread_id;
|
|
@@ -12638,7 +13718,7 @@ ${lines.join("\n")}`;
|
|
|
12638
13718
|
if (session.archiving) return;
|
|
12639
13719
|
const threadId = Number(session.threadId);
|
|
12640
13720
|
if (!threadId || isNaN(threadId)) {
|
|
12641
|
-
|
|
13721
|
+
log38.warn(
|
|
12642
13722
|
{ sessionId, threadId: session.threadId },
|
|
12643
13723
|
"Session has no valid threadId, skipping message"
|
|
12644
13724
|
);
|
|
@@ -12654,7 +13734,7 @@ ${lines.join("\n")}`;
|
|
|
12654
13734
|
this._sessionThreadIds.delete(sessionId);
|
|
12655
13735
|
}
|
|
12656
13736
|
}).catch((err) => {
|
|
12657
|
-
|
|
13737
|
+
log38.warn({ err, sessionId }, "Dispatch queue error");
|
|
12658
13738
|
});
|
|
12659
13739
|
this._dispatchQueues.set(sessionId, next);
|
|
12660
13740
|
await next;
|
|
@@ -12784,7 +13864,7 @@ ${lines.join("\n")}`;
|
|
|
12784
13864
|
usageMsgId = result?.message_id;
|
|
12785
13865
|
if (usageMsgId) this.usageMsgIds.set(sessionId, usageMsgId);
|
|
12786
13866
|
} catch (err) {
|
|
12787
|
-
|
|
13867
|
+
log38.warn({ err, sessionId }, "Failed to send usage message");
|
|
12788
13868
|
}
|
|
12789
13869
|
}
|
|
12790
13870
|
async handleAttachment(sessionId, content) {
|
|
@@ -12793,7 +13873,7 @@ ${lines.join("\n")}`;
|
|
|
12793
13873
|
if (!content.attachment) return;
|
|
12794
13874
|
const { attachment } = content;
|
|
12795
13875
|
if (attachment.size > 50 * 1024 * 1024) {
|
|
12796
|
-
|
|
13876
|
+
log38.warn(
|
|
12797
13877
|
{
|
|
12798
13878
|
sessionId,
|
|
12799
13879
|
fileName: attachment.fileName,
|
|
@@ -12837,7 +13917,7 @@ ${lines.join("\n")}`;
|
|
|
12837
13917
|
);
|
|
12838
13918
|
}
|
|
12839
13919
|
} catch (err) {
|
|
12840
|
-
|
|
13920
|
+
log38.error(
|
|
12841
13921
|
{ err, sessionId, fileName: attachment.fileName },
|
|
12842
13922
|
"Failed to send attachment"
|
|
12843
13923
|
);
|
|
@@ -12962,7 +14042,7 @@ Task completed.
|
|
|
12962
14042
|
*/
|
|
12963
14043
|
async sendPermissionRequest(sessionId, request) {
|
|
12964
14044
|
this.getTracer(sessionId)?.log("telegram", { action: "permission:send", sessionId, requestId: request.id, description: request.description });
|
|
12965
|
-
|
|
14045
|
+
log38.info({ sessionId, requestId: request.id }, "Permission request sent");
|
|
12966
14046
|
const session = this.core.sessionManager.getSession(sessionId);
|
|
12967
14047
|
if (!session) return;
|
|
12968
14048
|
await this.sendQueue.enqueue(
|
|
@@ -12977,7 +14057,7 @@ Task completed.
|
|
|
12977
14057
|
async sendNotification(notification) {
|
|
12978
14058
|
this.getTracer(notification.sessionId)?.log("telegram", { action: "notification:send", sessionId: notification.sessionId, type: notification.type });
|
|
12979
14059
|
if (notification.sessionId === this.core.assistantManager?.get("telegram")?.id) return;
|
|
12980
|
-
|
|
14060
|
+
log38.info(
|
|
12981
14061
|
{ sessionId: notification.sessionId, type: notification.type },
|
|
12982
14062
|
"Notification sent"
|
|
12983
14063
|
);
|
|
@@ -13023,14 +14103,14 @@ Task completed.
|
|
|
13023
14103
|
*/
|
|
13024
14104
|
async createSessionThread(sessionId, name) {
|
|
13025
14105
|
this.getTracer(sessionId)?.log("telegram", { action: "thread:create", sessionId, name });
|
|
13026
|
-
|
|
14106
|
+
log38.info({ sessionId, name }, "Session topic created");
|
|
13027
14107
|
const threadId = await createSessionTopic(this.bot, this.telegramConfig.chatId, name);
|
|
13028
14108
|
this.bot.api.sendMessage(
|
|
13029
14109
|
this.telegramConfig.chatId,
|
|
13030
14110
|
"\u23F3 Setting up session, please wait...",
|
|
13031
14111
|
{ message_thread_id: threadId, parse_mode: "HTML" }
|
|
13032
14112
|
).catch((err) => {
|
|
13033
|
-
|
|
14113
|
+
log38.warn({ err, sessionId, threadId }, "Failed to send setup message to new topic");
|
|
13034
14114
|
});
|
|
13035
14115
|
return String(threadId);
|
|
13036
14116
|
}
|
|
@@ -13044,7 +14124,7 @@ Task completed.
|
|
|
13044
14124
|
if (!session) return;
|
|
13045
14125
|
const threadId = Number(session.threadId);
|
|
13046
14126
|
if (!threadId) {
|
|
13047
|
-
|
|
14127
|
+
log38.debug({ sessionId, newName }, "Cannot rename thread \u2014 threadId not set yet");
|
|
13048
14128
|
return;
|
|
13049
14129
|
}
|
|
13050
14130
|
await renameSessionTopic(
|
|
@@ -13064,7 +14144,7 @@ Task completed.
|
|
|
13064
14144
|
try {
|
|
13065
14145
|
await this.bot.api.deleteForumTopic(this.telegramConfig.chatId, topicId);
|
|
13066
14146
|
} catch (err) {
|
|
13067
|
-
|
|
14147
|
+
log38.warn(
|
|
13068
14148
|
{ err, sessionId, topicId },
|
|
13069
14149
|
"Failed to delete forum topic (may already be deleted)"
|
|
13070
14150
|
);
|
|
@@ -13113,7 +14193,7 @@ Task completed.
|
|
|
13113
14193
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
13114
14194
|
return { buffer, filePath: file.file_path };
|
|
13115
14195
|
} catch (err) {
|
|
13116
|
-
|
|
14196
|
+
log38.error({ err }, "Failed to download file from Telegram");
|
|
13117
14197
|
return null;
|
|
13118
14198
|
}
|
|
13119
14199
|
}
|
|
@@ -13134,7 +14214,7 @@ Task completed.
|
|
|
13134
14214
|
try {
|
|
13135
14215
|
buffer = await this.fileService.convertOggToWav(buffer);
|
|
13136
14216
|
} catch (err) {
|
|
13137
|
-
|
|
14217
|
+
log38.warn({ err }, "OGG\u2192WAV conversion failed, saving original OGG");
|
|
13138
14218
|
fileName = "voice.ogg";
|
|
13139
14219
|
mimeType = "audio/ogg";
|
|
13140
14220
|
originalFilePath = void 0;
|
|
@@ -13169,7 +14249,7 @@ Task completed.
|
|
|
13169
14249
|
userId: String(userId),
|
|
13170
14250
|
text: text3,
|
|
13171
14251
|
attachments: [att]
|
|
13172
|
-
}).catch((err) =>
|
|
14252
|
+
}).catch((err) => log38.error({ err }, "handleMessage error"));
|
|
13173
14253
|
}
|
|
13174
14254
|
/**
|
|
13175
14255
|
* Remove skill slash commands from the Telegram bot command list for a session.
|
|
@@ -15275,7 +16355,7 @@ var TTS_BLOCK_REGEX = /\[TTS\]([\s\S]*?)\[\/TTS\]/;
|
|
|
15275
16355
|
var TTS_MAX_LENGTH = 5e3;
|
|
15276
16356
|
var TTS_TIMEOUT_MS = 3e4;
|
|
15277
16357
|
var VALID_TRANSITIONS = {
|
|
15278
|
-
initializing: /* @__PURE__ */ new Set(["active", "error"]),
|
|
16358
|
+
initializing: /* @__PURE__ */ new Set(["active", "error", "cancelled"]),
|
|
15279
16359
|
active: /* @__PURE__ */ new Set(["error", "finished", "cancelled"]),
|
|
15280
16360
|
error: /* @__PURE__ */ new Set(["active", "cancelled"]),
|
|
15281
16361
|
cancelled: /* @__PURE__ */ new Set(["active"]),
|
|
@@ -15337,6 +16417,9 @@ var Session = class extends TypedEmitter {
|
|
|
15337
16417
|
_abortedTurnIds = /* @__PURE__ */ new Set();
|
|
15338
16418
|
/** Last completed turnId — used by abortPrompt() to retroactively mark a just-finished turn as interrupted */
|
|
15339
16419
|
_lastCompletedTurnId = null;
|
|
16420
|
+
/** Idempotent teardown checkpoints allow cleanup retry after a partial failure. */
|
|
16421
|
+
_destroyedAgent = false;
|
|
16422
|
+
_closedLogger = false;
|
|
15340
16423
|
_autoApprovedCommands = [];
|
|
15341
16424
|
get autoApprovedCommands() {
|
|
15342
16425
|
return this._autoApprovedCommands;
|
|
@@ -15415,7 +16498,7 @@ var Session = class extends TypedEmitter {
|
|
|
15415
16498
|
this.transition("finished");
|
|
15416
16499
|
this.emit(SessionEv.SESSION_END, reason ?? "completed");
|
|
15417
16500
|
}
|
|
15418
|
-
/** Transition to cancelled — from active or error
|
|
16501
|
+
/** Transition to cancelled — from initializing, active, or error. */
|
|
15419
16502
|
markCancelled() {
|
|
15420
16503
|
this.transition("cancelled");
|
|
15421
16504
|
}
|
|
@@ -15932,8 +17015,14 @@ ${result.text}` : result.text;
|
|
|
15932
17015
|
this.permissionGate.reject("Session destroyed");
|
|
15933
17016
|
}
|
|
15934
17017
|
this.queue.clear();
|
|
15935
|
-
|
|
15936
|
-
|
|
17018
|
+
if (!this._destroyedAgent) {
|
|
17019
|
+
await this.agentInstance.destroy();
|
|
17020
|
+
this._destroyedAgent = true;
|
|
17021
|
+
}
|
|
17022
|
+
if (!this._closedLogger) {
|
|
17023
|
+
await closeSessionLogger(this.log);
|
|
17024
|
+
this._closedLogger = true;
|
|
17025
|
+
}
|
|
15937
17026
|
}
|
|
15938
17027
|
};
|
|
15939
17028
|
|
|
@@ -16451,6 +17540,9 @@ var MessageTransformer = class {
|
|
|
16451
17540
|
|
|
16452
17541
|
// src/core/sessions/session-manager.ts
|
|
16453
17542
|
init_events();
|
|
17543
|
+
init_log();
|
|
17544
|
+
init_network_redaction();
|
|
17545
|
+
var log7 = createChildLogger({ module: "session-manager" });
|
|
16454
17546
|
var SessionManager = class {
|
|
16455
17547
|
sessions = /* @__PURE__ */ new Map();
|
|
16456
17548
|
store;
|
|
@@ -16460,6 +17552,7 @@ var SessionManager = class {
|
|
|
16460
17552
|
// Prevents SessionBridge STATUS_CHANGE listeners from overwriting the flushed state
|
|
16461
17553
|
// with transient error status from in-flight prompts that fail after shutdown.
|
|
16462
17554
|
_shutdownComplete = false;
|
|
17555
|
+
cancellationOps = /* @__PURE__ */ new Map();
|
|
16463
17556
|
/**
|
|
16464
17557
|
* Inject the EventBus after construction. Deferred because EventBus is created
|
|
16465
17558
|
* after SessionManager during bootstrap, so it cannot be passed to the constructor.
|
|
@@ -16560,30 +17653,76 @@ var SessionManager = class {
|
|
|
16560
17653
|
getSessionRecord(sessionId) {
|
|
16561
17654
|
return this.store?.get(sessionId);
|
|
16562
17655
|
}
|
|
16563
|
-
/**
|
|
17656
|
+
/**
|
|
17657
|
+
* Cancel a session exactly once. Concurrent callers share one operation;
|
|
17658
|
+
* terminal sessions return an idempotent result instead of throwing.
|
|
17659
|
+
*/
|
|
16564
17660
|
async cancelSession(sessionId) {
|
|
17661
|
+
const existing = this.cancellationOps.get(sessionId);
|
|
17662
|
+
if (existing) return existing;
|
|
17663
|
+
const operation = this.performCancelSession(sessionId);
|
|
17664
|
+
this.cancellationOps.set(sessionId, operation);
|
|
17665
|
+
try {
|
|
17666
|
+
return await operation;
|
|
17667
|
+
} finally {
|
|
17668
|
+
if (this.cancellationOps.get(sessionId) === operation) this.cancellationOps.delete(sessionId);
|
|
17669
|
+
}
|
|
17670
|
+
}
|
|
17671
|
+
async performCancelSession(sessionId) {
|
|
16565
17672
|
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);
|
|
17673
|
+
const recordBefore = this.store?.get(sessionId);
|
|
17674
|
+
const previousStatus = session?.status ?? recordBefore?.status;
|
|
17675
|
+
if (!previousStatus) {
|
|
17676
|
+
const error = new Error(`Session "${sessionId}" not found`);
|
|
17677
|
+
error.code = "SESSION_NOT_FOUND";
|
|
17678
|
+
throw error;
|
|
16576
17679
|
}
|
|
16577
|
-
|
|
17680
|
+
const alreadyTerminal = previousStatus === "cancelled" || previousStatus === "finished";
|
|
17681
|
+
const finalStatus = previousStatus === "finished" ? "finished" : "cancelled";
|
|
17682
|
+
if (this.store && finalStatus === "cancelled") {
|
|
16578
17683
|
const record = this.store.get(sessionId);
|
|
16579
|
-
if (record && record.status !== "cancelled"
|
|
17684
|
+
if (record && record.status !== "cancelled") {
|
|
16580
17685
|
await this.store.save({ ...record, status: "cancelled" });
|
|
17686
|
+
this.store.flush();
|
|
16581
17687
|
}
|
|
16582
17688
|
}
|
|
16583
|
-
|
|
17689
|
+
let cleanupPending = false;
|
|
17690
|
+
if (session) {
|
|
17691
|
+
if (!alreadyTerminal) {
|
|
17692
|
+
session.markCancelled();
|
|
17693
|
+
try {
|
|
17694
|
+
await session.abortPrompt();
|
|
17695
|
+
} catch (error) {
|
|
17696
|
+
log7.warn(
|
|
17697
|
+
{ sessionId, error: redactNetworkSecrets(error instanceof Error ? error.message : String(error)) },
|
|
17698
|
+
"Session prompt abort failed after cancellation became durable; continuing cleanup"
|
|
17699
|
+
);
|
|
17700
|
+
}
|
|
17701
|
+
}
|
|
17702
|
+
try {
|
|
17703
|
+
await session.destroy();
|
|
17704
|
+
this.sessions.delete(sessionId);
|
|
17705
|
+
} catch (error) {
|
|
17706
|
+
cleanupPending = true;
|
|
17707
|
+
log7.warn(
|
|
17708
|
+
{ sessionId, error: redactNetworkSecrets(error instanceof Error ? error.message : String(error)) },
|
|
17709
|
+
"Session is durably cancelled but agent/logger cleanup failed; repeat cancellation to retry cleanup"
|
|
17710
|
+
);
|
|
17711
|
+
}
|
|
17712
|
+
}
|
|
17713
|
+
if (!cleanupPending && this.middlewareChain) {
|
|
16584
17714
|
this.middlewareChain.execute(Hook.SESSION_AFTER_DESTROY, { sessionId }, async (p) => p).catch(() => {
|
|
16585
17715
|
});
|
|
16586
17716
|
}
|
|
17717
|
+
return {
|
|
17718
|
+
sessionId,
|
|
17719
|
+
cancelled: !alreadyTerminal,
|
|
17720
|
+
previousStatus,
|
|
17721
|
+
status: finalStatus,
|
|
17722
|
+
alreadyTerminal,
|
|
17723
|
+
cleanupPending,
|
|
17724
|
+
...cleanupPending ? { warning: "Session state is cancelled and persisted; process cleanup is pending. Repeat cancellation to retry cleanup." } : {}
|
|
17725
|
+
};
|
|
16587
17726
|
}
|
|
16588
17727
|
/** List live (in-memory) sessions, optionally filtered by channel. Excludes assistant sessions. */
|
|
16589
17728
|
listSessions(channelId) {
|
|
@@ -16752,7 +17891,7 @@ init_bypass_detection();
|
|
|
16752
17891
|
import micromatch from "micromatch";
|
|
16753
17892
|
init_events();
|
|
16754
17893
|
var { isMatch } = micromatch;
|
|
16755
|
-
var
|
|
17894
|
+
var log8 = createChildLogger({ module: "session-bridge" });
|
|
16756
17895
|
var SessionBridge = class {
|
|
16757
17896
|
constructor(session, adapter, deps, adapterId) {
|
|
16758
17897
|
this.session = session;
|
|
@@ -16784,16 +17923,16 @@ var SessionBridge = class {
|
|
|
16784
17923
|
if (!result) return;
|
|
16785
17924
|
this.tracer?.log("core", { step: "dispatch", sessionId, message: result.message });
|
|
16786
17925
|
this.adapter.sendMessage(sessionId, result.message).catch((err) => {
|
|
16787
|
-
|
|
17926
|
+
log8.error({ err, sessionId }, "Failed to send message to adapter");
|
|
16788
17927
|
});
|
|
16789
17928
|
} else {
|
|
16790
17929
|
this.tracer?.log("core", { step: "dispatch", sessionId, message });
|
|
16791
17930
|
this.adapter.sendMessage(sessionId, message).catch((err) => {
|
|
16792
|
-
|
|
17931
|
+
log8.error({ err, sessionId }, "Failed to send message to adapter");
|
|
16793
17932
|
});
|
|
16794
17933
|
}
|
|
16795
17934
|
} catch (err) {
|
|
16796
|
-
|
|
17935
|
+
log8.error({ err, sessionId }, "Error in sendMessage middleware");
|
|
16797
17936
|
}
|
|
16798
17937
|
}
|
|
16799
17938
|
/**
|
|
@@ -16839,7 +17978,7 @@ var SessionBridge = class {
|
|
|
16839
17978
|
try {
|
|
16840
17979
|
await this.adapter.sendPermissionRequest(this.session.id, request);
|
|
16841
17980
|
} catch (err) {
|
|
16842
|
-
|
|
17981
|
+
log8.error({ err, sessionId: this.session.id, adapterId: this.adapterId }, "Failed to send permission request to adapter");
|
|
16843
17982
|
}
|
|
16844
17983
|
});
|
|
16845
17984
|
this.listen(this.session, SessionEv.STATUS_CHANGE, (from, to) => {
|
|
@@ -16924,14 +18063,14 @@ var SessionBridge = class {
|
|
|
16924
18063
|
try {
|
|
16925
18064
|
this.handleAgentEvent(event);
|
|
16926
18065
|
} catch (err) {
|
|
16927
|
-
|
|
18066
|
+
log8.error({ err, sessionId: this.session.id }, "Error handling agent event (middleware fallback)");
|
|
16928
18067
|
}
|
|
16929
18068
|
}
|
|
16930
18069
|
} else {
|
|
16931
18070
|
try {
|
|
16932
18071
|
this.handleAgentEvent(event);
|
|
16933
18072
|
} catch (err) {
|
|
16934
|
-
|
|
18073
|
+
log8.error({ err, sessionId: this.session.id }, "Error handling agent event");
|
|
16935
18074
|
}
|
|
16936
18075
|
}
|
|
16937
18076
|
}
|
|
@@ -16984,40 +18123,40 @@ var SessionBridge = class {
|
|
|
16984
18123
|
break;
|
|
16985
18124
|
case "image_content": {
|
|
16986
18125
|
if (this.deps.fileService) {
|
|
16987
|
-
const
|
|
18126
|
+
const fs38 = this.deps.fileService;
|
|
16988
18127
|
const sid = this.session.id;
|
|
16989
18128
|
const { data, mimeType } = event;
|
|
16990
18129
|
const buffer = Buffer.from(data, "base64");
|
|
16991
|
-
const ext =
|
|
16992
|
-
|
|
18130
|
+
const ext = fs38.extensionFromMime(mimeType);
|
|
18131
|
+
fs38.saveFile(sid, `agent-image${ext}`, buffer, mimeType).then((att) => {
|
|
16993
18132
|
this.sendMessage(sid, {
|
|
16994
18133
|
type: "attachment",
|
|
16995
18134
|
text: "",
|
|
16996
18135
|
attachment: att
|
|
16997
18136
|
});
|
|
16998
|
-
}).catch((err) =>
|
|
18137
|
+
}).catch((err) => log8.error({ err }, "Failed to save agent image"));
|
|
16999
18138
|
}
|
|
17000
18139
|
break;
|
|
17001
18140
|
}
|
|
17002
18141
|
case "audio_content": {
|
|
17003
18142
|
if (this.deps.fileService) {
|
|
17004
|
-
const
|
|
18143
|
+
const fs38 = this.deps.fileService;
|
|
17005
18144
|
const sid = this.session.id;
|
|
17006
18145
|
const { data, mimeType } = event;
|
|
17007
18146
|
const buffer = Buffer.from(data, "base64");
|
|
17008
|
-
const ext =
|
|
17009
|
-
|
|
18147
|
+
const ext = fs38.extensionFromMime(mimeType);
|
|
18148
|
+
fs38.saveFile(sid, `agent-audio${ext}`, buffer, mimeType).then((att) => {
|
|
17010
18149
|
this.sendMessage(sid, {
|
|
17011
18150
|
type: "attachment",
|
|
17012
18151
|
text: "",
|
|
17013
18152
|
attachment: att
|
|
17014
18153
|
});
|
|
17015
|
-
}).catch((err) =>
|
|
18154
|
+
}).catch((err) => log8.error({ err }, "Failed to save agent audio"));
|
|
17016
18155
|
}
|
|
17017
18156
|
break;
|
|
17018
18157
|
}
|
|
17019
18158
|
case "commands_update":
|
|
17020
|
-
|
|
18159
|
+
log8.debug({ commands: event.commands }, "Commands available");
|
|
17021
18160
|
this.adapter.sendSkillCommands?.(this.session.id, event.commands);
|
|
17022
18161
|
break;
|
|
17023
18162
|
case "system_message":
|
|
@@ -17114,7 +18253,7 @@ var SessionBridge = class {
|
|
|
17114
18253
|
if (isAgentBypass || isClientBypass) {
|
|
17115
18254
|
const allowOption = request.options.find((o) => o.isAllow);
|
|
17116
18255
|
if (allowOption) {
|
|
17117
|
-
|
|
18256
|
+
log8.info(
|
|
17118
18257
|
{ sessionId: this.session.id, requestId: request.id, optionId: allowOption.id, agentBypass: !!isAgentBypass, clientBypass: !!isClientBypass },
|
|
17119
18258
|
"Bypass mode: auto-approving permission"
|
|
17120
18259
|
);
|
|
@@ -17127,7 +18266,7 @@ var SessionBridge = class {
|
|
|
17127
18266
|
if (isMatch(normalized, patterns, { dot: true })) {
|
|
17128
18267
|
const allowOption = request.options.find((o) => o.isAllow);
|
|
17129
18268
|
if (allowOption) {
|
|
17130
|
-
|
|
18269
|
+
log8.info(
|
|
17131
18270
|
{ sessionId: this.session.id, requestId: request.id, command: request.description },
|
|
17132
18271
|
"autoApprovedCommands: auto-approving matching command"
|
|
17133
18272
|
);
|
|
@@ -17155,7 +18294,7 @@ var SessionBridge = class {
|
|
|
17155
18294
|
// src/core/sessions/session-factory.ts
|
|
17156
18295
|
init_log();
|
|
17157
18296
|
init_events();
|
|
17158
|
-
var
|
|
18297
|
+
var log9 = createChildLogger({ module: "session-factory" });
|
|
17159
18298
|
function toSetConfigOptionValue(option) {
|
|
17160
18299
|
return option.type === "boolean" ? { type: "boolean", value: option.currentValue } : { type: "select", value: option.currentValue };
|
|
17161
18300
|
}
|
|
@@ -17229,7 +18368,7 @@ var SessionFactory = class {
|
|
|
17229
18368
|
configAllowedPaths
|
|
17230
18369
|
);
|
|
17231
18370
|
} catch (resumeErr) {
|
|
17232
|
-
|
|
18371
|
+
log9.warn(
|
|
17233
18372
|
{ agentName: createParams.agentName, resumeErr },
|
|
17234
18373
|
"Agent session resume failed, falling back to fresh spawn"
|
|
17235
18374
|
);
|
|
@@ -17382,10 +18521,10 @@ var SessionFactory = class {
|
|
|
17382
18521
|
}
|
|
17383
18522
|
}
|
|
17384
18523
|
}
|
|
17385
|
-
|
|
18524
|
+
log9.info({ sessionId }, "Lazy resume by ID successful");
|
|
17386
18525
|
return session;
|
|
17387
18526
|
} catch (err) {
|
|
17388
|
-
|
|
18527
|
+
log9.error({ err, sessionId }, "Lazy resume by ID failed");
|
|
17389
18528
|
return null;
|
|
17390
18529
|
} finally {
|
|
17391
18530
|
this.resumeLocks.delete(sessionId);
|
|
@@ -17410,19 +18549,19 @@ var SessionFactory = class {
|
|
|
17410
18549
|
(p) => String(p.topicId) === threadId || String(p.threadId ?? "") === threadId
|
|
17411
18550
|
);
|
|
17412
18551
|
if (!record) {
|
|
17413
|
-
|
|
18552
|
+
log9.debug({ threadId, channelId }, "No session record found for thread");
|
|
17414
18553
|
return null;
|
|
17415
18554
|
}
|
|
17416
18555
|
if (record.isAssistant) return null;
|
|
17417
18556
|
if (record.status === "error" || record.status === "cancelled") {
|
|
17418
|
-
|
|
18557
|
+
log9.warn(
|
|
17419
18558
|
{ threadId, sessionId: record.sessionId, status: record.status },
|
|
17420
18559
|
"Session record found but skipped (status: %s) \u2014 use /new to start a fresh session",
|
|
17421
18560
|
record.status
|
|
17422
18561
|
);
|
|
17423
18562
|
return null;
|
|
17424
18563
|
}
|
|
17425
|
-
|
|
18564
|
+
log9.info({ threadId, sessionId: record.sessionId, status: record.status }, "Lazy resume: found record, attempting resume");
|
|
17426
18565
|
const resumePromise = (async () => {
|
|
17427
18566
|
try {
|
|
17428
18567
|
const session = await this.createFullSession({
|
|
@@ -17474,7 +18613,7 @@ var SessionFactory = class {
|
|
|
17474
18613
|
}
|
|
17475
18614
|
const resumeFalledBack = record.agentSessionId && session.agentSessionId !== record.agentSessionId;
|
|
17476
18615
|
if (resumeFalledBack) {
|
|
17477
|
-
|
|
18616
|
+
log9.info({ sessionId: session.id }, "Resume fell back to fresh spawn \u2014 injecting conversation history");
|
|
17478
18617
|
const contextManager = this.getContextManager?.();
|
|
17479
18618
|
if (contextManager) {
|
|
17480
18619
|
try {
|
|
@@ -17491,10 +18630,10 @@ var SessionFactory = class {
|
|
|
17491
18630
|
}
|
|
17492
18631
|
}
|
|
17493
18632
|
}
|
|
17494
|
-
|
|
18633
|
+
log9.info({ sessionId: session.id, threadId }, "Lazy resume successful");
|
|
17495
18634
|
return session;
|
|
17496
18635
|
} catch (err) {
|
|
17497
|
-
|
|
18636
|
+
log9.error({ err, record }, "Lazy resume failed");
|
|
17498
18637
|
const adapter = this.adapters?.get(channelId);
|
|
17499
18638
|
if (adapter) {
|
|
17500
18639
|
try {
|
|
@@ -17520,7 +18659,7 @@ var SessionFactory = class {
|
|
|
17520
18659
|
}
|
|
17521
18660
|
const config = this.configManager.get();
|
|
17522
18661
|
const resolvedAgent = agentName || config.defaultAgent;
|
|
17523
|
-
|
|
18662
|
+
log9.info({ channelId, agentName: resolvedAgent }, "New session request");
|
|
17524
18663
|
const agentDef = this.agentCatalog.resolve(resolvedAgent);
|
|
17525
18664
|
const resolvedWorkspace = this.configManager.resolveWorkspace(
|
|
17526
18665
|
workspacePath || agentDef?.workingDirectory
|
|
@@ -17570,7 +18709,7 @@ var SessionFactory = class {
|
|
|
17570
18709
|
params.contextOptions
|
|
17571
18710
|
);
|
|
17572
18711
|
} catch (err) {
|
|
17573
|
-
|
|
18712
|
+
log9.warn({ err }, "Context building failed, proceeding without context");
|
|
17574
18713
|
}
|
|
17575
18714
|
}
|
|
17576
18715
|
const session = await this.createFullSession({
|
|
@@ -17625,7 +18764,7 @@ import { nanoid as nanoid3 } from "nanoid";
|
|
|
17625
18764
|
init_log();
|
|
17626
18765
|
import fs9 from "fs";
|
|
17627
18766
|
import path8 from "path";
|
|
17628
|
-
var
|
|
18767
|
+
var log10 = createChildLogger({ module: "session-store" });
|
|
17629
18768
|
var DEBOUNCE_MS = 2e3;
|
|
17630
18769
|
var JsonFileSessionStore = class {
|
|
17631
18770
|
records = /* @__PURE__ */ new Map();
|
|
@@ -17739,7 +18878,7 @@ var JsonFileSessionStore = class {
|
|
|
17739
18878
|
fs9.readFileSync(this.filePath, "utf-8")
|
|
17740
18879
|
);
|
|
17741
18880
|
if (raw.version !== 1) {
|
|
17742
|
-
|
|
18881
|
+
log10.warn(
|
|
17743
18882
|
{ version: raw.version },
|
|
17744
18883
|
"Unknown session store version, skipping load"
|
|
17745
18884
|
);
|
|
@@ -17748,9 +18887,9 @@ var JsonFileSessionStore = class {
|
|
|
17748
18887
|
for (const [id, record] of Object.entries(raw.sessions)) {
|
|
17749
18888
|
this.records.set(id, this.migrateRecord(record));
|
|
17750
18889
|
}
|
|
17751
|
-
|
|
18890
|
+
log10.debug({ count: this.records.size }, "Loaded session records");
|
|
17752
18891
|
} catch (err) {
|
|
17753
|
-
|
|
18892
|
+
log10.error({ err }, "Failed to load session store, backing up corrupt file");
|
|
17754
18893
|
try {
|
|
17755
18894
|
fs9.renameSync(this.filePath, `${this.filePath}.bak`);
|
|
17756
18895
|
} catch {
|
|
@@ -17793,7 +18932,7 @@ var JsonFileSessionStore = class {
|
|
|
17793
18932
|
}
|
|
17794
18933
|
}
|
|
17795
18934
|
if (removed > 0) {
|
|
17796
|
-
|
|
18935
|
+
log10.info({ removed }, "Cleaned up expired session records");
|
|
17797
18936
|
this.scheduleDiskWrite();
|
|
17798
18937
|
}
|
|
17799
18938
|
}
|
|
@@ -17812,7 +18951,7 @@ init_agent_registry();
|
|
|
17812
18951
|
init_agent_registry();
|
|
17813
18952
|
init_log();
|
|
17814
18953
|
init_events();
|
|
17815
|
-
var
|
|
18954
|
+
var log11 = createChildLogger({ module: "agent-switch" });
|
|
17816
18955
|
var AgentSwitchHandler = class {
|
|
17817
18956
|
constructor(deps) {
|
|
17818
18957
|
this.deps = deps;
|
|
@@ -17893,7 +19032,7 @@ var AgentSwitchHandler = class {
|
|
|
17893
19032
|
resumed = true;
|
|
17894
19033
|
return instance2;
|
|
17895
19034
|
} catch {
|
|
17896
|
-
|
|
19035
|
+
log11.warn({ sessionId, toAgent }, "Resume failed, falling back to new agent with context injection");
|
|
17897
19036
|
}
|
|
17898
19037
|
}
|
|
17899
19038
|
const instance = await agentManager.spawn(toAgent, session.workingDirectory, configAllowedPaths);
|
|
@@ -17962,10 +19101,10 @@ var AgentSwitchHandler = class {
|
|
|
17962
19101
|
createBridge(session, adapter, adapterId).connect();
|
|
17963
19102
|
}
|
|
17964
19103
|
}
|
|
17965
|
-
|
|
19104
|
+
log11.warn({ sessionId, fromAgent, toAgent, err }, "Agent switch failed, rolled back to previous agent");
|
|
17966
19105
|
} catch (rollbackErr) {
|
|
17967
19106
|
session.fail(`Switch failed and rollback failed: ${rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr)}`);
|
|
17968
|
-
|
|
19107
|
+
log11.error({ sessionId, fromAgent, toAgent, err, rollbackErr }, "Agent switch failed and rollback also failed");
|
|
17969
19108
|
}
|
|
17970
19109
|
throw err;
|
|
17971
19110
|
}
|
|
@@ -17975,7 +19114,7 @@ var AgentSwitchHandler = class {
|
|
|
17975
19114
|
if (adapter) {
|
|
17976
19115
|
createBridge(session, adapter, adapterId).connect();
|
|
17977
19116
|
} else {
|
|
17978
|
-
|
|
19117
|
+
log11.warn({ sessionId, adapterId }, "Adapter not available during switch reconnect, skipping bridge");
|
|
17979
19118
|
}
|
|
17980
19119
|
}
|
|
17981
19120
|
}
|
|
@@ -18003,7 +19142,7 @@ init_agent_dependencies();
|
|
|
18003
19142
|
init_log();
|
|
18004
19143
|
import * as fs12 from "fs";
|
|
18005
19144
|
import * as path11 from "path";
|
|
18006
|
-
var
|
|
19145
|
+
var log13 = createChildLogger({ module: "agent-catalog" });
|
|
18007
19146
|
var REGISTRY_URL = "https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json";
|
|
18008
19147
|
var DEFAULT_TTL_HOURS = 24;
|
|
18009
19148
|
var AgentCatalog = class {
|
|
@@ -18037,7 +19176,7 @@ var AgentCatalog = class {
|
|
|
18037
19176
|
/** Fetch the latest agent registry from the CDN and update the local cache. */
|
|
18038
19177
|
async fetchRegistry() {
|
|
18039
19178
|
try {
|
|
18040
|
-
|
|
19179
|
+
log13.info("Fetching agent registry from CDN...");
|
|
18041
19180
|
const response = await this.scopedFetch(this.registryUrl);
|
|
18042
19181
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
18043
19182
|
const data = await response.json();
|
|
@@ -18050,9 +19189,9 @@ var AgentCatalog = class {
|
|
|
18050
19189
|
fs12.mkdirSync(path11.dirname(this.cachePath), { recursive: true });
|
|
18051
19190
|
fs12.writeFileSync(this.cachePath, JSON.stringify(cache, null, 2), { mode: 384 });
|
|
18052
19191
|
this.enrichInstalledFromRegistry();
|
|
18053
|
-
|
|
19192
|
+
log13.info({ count: this.registryAgents.length }, "Registry updated");
|
|
18054
19193
|
} catch (err) {
|
|
18055
|
-
|
|
19194
|
+
log13.warn({ err }, "Failed to fetch registry, using cached data");
|
|
18056
19195
|
}
|
|
18057
19196
|
}
|
|
18058
19197
|
/** Re-fetch registry only if the local cache has expired (24-hour TTL). */
|
|
@@ -18260,7 +19399,7 @@ var AgentCatalog = class {
|
|
|
18260
19399
|
}
|
|
18261
19400
|
}
|
|
18262
19401
|
if (changed) {
|
|
18263
|
-
|
|
19402
|
+
log13.info("Enriched installed agents with registry data");
|
|
18264
19403
|
}
|
|
18265
19404
|
}
|
|
18266
19405
|
isCacheStale() {
|
|
@@ -18280,11 +19419,11 @@ var AgentCatalog = class {
|
|
|
18280
19419
|
const raw = JSON.parse(fs12.readFileSync(this.cachePath, "utf-8"));
|
|
18281
19420
|
if (raw.data?.agents) {
|
|
18282
19421
|
this.registryAgents = raw.data.agents;
|
|
18283
|
-
|
|
19422
|
+
log13.debug({ count: this.registryAgents.length }, "Loaded registry from cache");
|
|
18284
19423
|
return;
|
|
18285
19424
|
}
|
|
18286
19425
|
} catch {
|
|
18287
|
-
|
|
19426
|
+
log13.warn("Failed to load registry cache");
|
|
18288
19427
|
}
|
|
18289
19428
|
}
|
|
18290
19429
|
try {
|
|
@@ -18297,13 +19436,13 @@ var AgentCatalog = class {
|
|
|
18297
19436
|
if (fs12.existsSync(candidate)) {
|
|
18298
19437
|
const raw = JSON.parse(fs12.readFileSync(candidate, "utf-8"));
|
|
18299
19438
|
this.registryAgents = raw.agents ?? [];
|
|
18300
|
-
|
|
19439
|
+
log13.debug({ count: this.registryAgents.length }, "Loaded registry from bundled snapshot");
|
|
18301
19440
|
return;
|
|
18302
19441
|
}
|
|
18303
19442
|
}
|
|
18304
|
-
|
|
19443
|
+
log13.warn("No registry data available (no cache, no snapshot)");
|
|
18305
19444
|
} catch {
|
|
18306
|
-
|
|
19445
|
+
log13.warn("Failed to load bundled registry snapshot");
|
|
18307
19446
|
}
|
|
18308
19447
|
}
|
|
18309
19448
|
};
|
|
@@ -18332,7 +19471,7 @@ init_log();
|
|
|
18332
19471
|
import * as fs13 from "fs";
|
|
18333
19472
|
import * as path12 from "path";
|
|
18334
19473
|
import { z as z2 } from "zod";
|
|
18335
|
-
var
|
|
19474
|
+
var log14 = createChildLogger({ module: "agent-store" });
|
|
18336
19475
|
var InstalledAgentSchema = z2.object({
|
|
18337
19476
|
registryId: z2.string().nullable(),
|
|
18338
19477
|
name: z2.string(),
|
|
@@ -18368,11 +19507,11 @@ var AgentStore = class {
|
|
|
18368
19507
|
if (result.success) {
|
|
18369
19508
|
this.data = result.data;
|
|
18370
19509
|
} else {
|
|
18371
|
-
|
|
19510
|
+
log14.warn({ errors: result.error.issues }, "Invalid agents.json, starting fresh");
|
|
18372
19511
|
this.data = { version: 1, installed: {} };
|
|
18373
19512
|
}
|
|
18374
19513
|
} catch (err) {
|
|
18375
|
-
|
|
19514
|
+
log14.warn({ err }, "Failed to read agents.json, starting fresh");
|
|
18376
19515
|
this.data = { version: 1, installed: {} };
|
|
18377
19516
|
}
|
|
18378
19517
|
}
|
|
@@ -18827,7 +19966,7 @@ function createPluginContext(opts) {
|
|
|
18827
19966
|
}
|
|
18828
19967
|
};
|
|
18829
19968
|
const baseLog = opts.log ?? noopLog;
|
|
18830
|
-
const
|
|
19969
|
+
const log39 = typeof baseLog.child === "function" ? baseLog.child({ plugin: pluginName }) : baseLog;
|
|
18831
19970
|
const storageImpl = new PluginStorageImpl(storagePath);
|
|
18832
19971
|
const storage = {
|
|
18833
19972
|
async get(key) {
|
|
@@ -18897,7 +20036,7 @@ function createPluginContext(opts) {
|
|
|
18897
20036
|
const ctx = {
|
|
18898
20037
|
pluginName,
|
|
18899
20038
|
pluginConfig,
|
|
18900
|
-
log:
|
|
20039
|
+
log: log39,
|
|
18901
20040
|
storage,
|
|
18902
20041
|
on(event, handler) {
|
|
18903
20042
|
requirePermission(permissions, "events:read", "on()");
|
|
@@ -18932,7 +20071,7 @@ function createPluginContext(opts) {
|
|
|
18932
20071
|
const registry = serviceRegistry.get("command-registry");
|
|
18933
20072
|
if (registry && typeof registry.register === "function") {
|
|
18934
20073
|
registry.register(def, pluginName);
|
|
18935
|
-
|
|
20074
|
+
log39.debug(`Command '/${def.name}' registered`);
|
|
18936
20075
|
}
|
|
18937
20076
|
},
|
|
18938
20077
|
async sendMessage(_sessionId, _content) {
|
|
@@ -18995,7 +20134,7 @@ function createPluginContext(opts) {
|
|
|
18995
20134
|
const registry = serviceRegistry.get("field-registry");
|
|
18996
20135
|
if (registry && typeof registry.register === "function") {
|
|
18997
20136
|
registry.register(pluginName, fields);
|
|
18998
|
-
|
|
20137
|
+
log39.debug(`Registered ${fields.length} editable field(s) for ${pluginName}`);
|
|
18999
20138
|
}
|
|
19000
20139
|
},
|
|
19001
20140
|
get sessions() {
|
|
@@ -19335,7 +20474,7 @@ var LifecycleManager = class {
|
|
|
19335
20474
|
|
|
19336
20475
|
// src/core/menu-registry.ts
|
|
19337
20476
|
init_log();
|
|
19338
|
-
var
|
|
20477
|
+
var log15 = createChildLogger({ module: "menu-registry" });
|
|
19339
20478
|
var MenuRegistry = class {
|
|
19340
20479
|
items = /* @__PURE__ */ new Map();
|
|
19341
20480
|
/** Register or replace a menu item by its unique ID. */
|
|
@@ -19357,7 +20496,7 @@ var MenuRegistry = class {
|
|
|
19357
20496
|
try {
|
|
19358
20497
|
return item.visible();
|
|
19359
20498
|
} catch (err) {
|
|
19360
|
-
|
|
20499
|
+
log15.warn({ err, id: item.id }, "MenuItem visible() threw, hiding item");
|
|
19361
20500
|
return false;
|
|
19362
20501
|
}
|
|
19363
20502
|
}).sort((a, b) => a.priority - b.priority);
|
|
@@ -19425,7 +20564,7 @@ openacp api new claude ~/project
|
|
|
19425
20564
|
}
|
|
19426
20565
|
|
|
19427
20566
|
// src/core/assistant/assistant-registry.ts
|
|
19428
|
-
var
|
|
20567
|
+
var log16 = createChildLogger({ module: "assistant-registry" });
|
|
19429
20568
|
var AssistantRegistry = class {
|
|
19430
20569
|
sections = /* @__PURE__ */ new Map();
|
|
19431
20570
|
_instanceRoot = "";
|
|
@@ -19436,7 +20575,7 @@ var AssistantRegistry = class {
|
|
|
19436
20575
|
/** Register a prompt section. Overwrites any existing section with the same id. */
|
|
19437
20576
|
register(section) {
|
|
19438
20577
|
if (this.sections.has(section.id)) {
|
|
19439
|
-
|
|
20578
|
+
log16.warn({ id: section.id }, "Assistant section overwritten");
|
|
19440
20579
|
}
|
|
19441
20580
|
this.sections.set(section.id, section);
|
|
19442
20581
|
}
|
|
@@ -19472,7 +20611,7 @@ ${context}`);
|
|
|
19472
20611
|
parts.push("```bash\n" + cmds + "\n```");
|
|
19473
20612
|
}
|
|
19474
20613
|
} catch (err) {
|
|
19475
|
-
|
|
20614
|
+
log16.warn({ err, sectionId: section.id }, "Assistant section buildContext() failed, skipping");
|
|
19476
20615
|
}
|
|
19477
20616
|
}
|
|
19478
20617
|
parts.push(buildAssistantGuidelines(this._instanceRoot));
|
|
@@ -19482,7 +20621,7 @@ ${context}`);
|
|
|
19482
20621
|
|
|
19483
20622
|
// src/core/assistant/assistant-manager.ts
|
|
19484
20623
|
init_log();
|
|
19485
|
-
var
|
|
20624
|
+
var log17 = createChildLogger({ module: "assistant-manager" });
|
|
19486
20625
|
var AssistantManager = class {
|
|
19487
20626
|
constructor(core, registry) {
|
|
19488
20627
|
this.core = core;
|
|
@@ -19513,7 +20652,7 @@ var AssistantManager = class {
|
|
|
19513
20652
|
this.sessions.set(channelId, session);
|
|
19514
20653
|
const systemPrompt = this.registry.buildSystemPrompt(channelId);
|
|
19515
20654
|
this.pendingSystemPrompts.set(channelId, systemPrompt);
|
|
19516
|
-
|
|
20655
|
+
log17.info(
|
|
19517
20656
|
{ sessionId: session.id, channelId, reused: !!existing },
|
|
19518
20657
|
existing ? "Assistant session reused (system prompt deferred)" : "Assistant spawned (system prompt deferred)"
|
|
19519
20658
|
);
|
|
@@ -19704,6 +20843,15 @@ function registerCoreMenuItems(registry) {
|
|
|
19704
20843
|
group: "config",
|
|
19705
20844
|
action: { type: "command", command: "/integrate" }
|
|
19706
20845
|
});
|
|
20846
|
+
registry.register({
|
|
20847
|
+
id: "core:proxy",
|
|
20848
|
+
label: "\u{1F310} Proxy Routing",
|
|
20849
|
+
priority: 32,
|
|
20850
|
+
group: "config",
|
|
20851
|
+
action: { type: "command", command: "/proxy" },
|
|
20852
|
+
// Keep already-sent m:core:proxy buttons valid while nesting new entry points under Settings.
|
|
20853
|
+
visible: () => false
|
|
20854
|
+
});
|
|
19707
20855
|
registry.register({
|
|
19708
20856
|
id: "core:restart",
|
|
19709
20857
|
label: "\u{1F504} Restart",
|
|
@@ -19739,7 +20887,7 @@ init_log();
|
|
|
19739
20887
|
init_native_stt();
|
|
19740
20888
|
init_events();
|
|
19741
20889
|
init_proxy_service();
|
|
19742
|
-
var
|
|
20890
|
+
var log18 = createChildLogger({ module: "core" });
|
|
19743
20891
|
var OpenACPCore = class {
|
|
19744
20892
|
configManager;
|
|
19745
20893
|
agentCatalog;
|
|
@@ -19822,7 +20970,7 @@ var OpenACPCore = class {
|
|
|
19822
20970
|
this.proxyService.onRouteChanged(async (scope) => {
|
|
19823
20971
|
if (scope === "global" || scope === "agents.default" || scope.startsWith("agents.")) {
|
|
19824
20972
|
await this.agentManager.destroyWarm();
|
|
19825
|
-
|
|
20973
|
+
log18.info({ scope }, "Proxy route changed; idle agent warm pool invalidated");
|
|
19826
20974
|
}
|
|
19827
20975
|
});
|
|
19828
20976
|
const storePath = ctx.paths.sessions;
|
|
@@ -19884,7 +21032,7 @@ var OpenACPCore = class {
|
|
|
19884
21032
|
if (configPath === "logging.level" && typeof value === "string") {
|
|
19885
21033
|
const { setLogLevel: setLogLevel2 } = await Promise.resolve().then(() => (init_log(), log_exports));
|
|
19886
21034
|
setLogLevel2(value);
|
|
19887
|
-
|
|
21035
|
+
log18.info({ level: value }, "Log level changed at runtime");
|
|
19888
21036
|
}
|
|
19889
21037
|
if (configPath.startsWith("speech.")) {
|
|
19890
21038
|
const speechSvc = this.lifecycleManager.serviceRegistry.get("speech");
|
|
@@ -19894,7 +21042,7 @@ var OpenACPCore = class {
|
|
|
19894
21042
|
const pluginCfg = await settingsMgr.loadSettings("@openacp/speech");
|
|
19895
21043
|
const newSpeechConfig = buildSpeechServiceConfig(pluginCfg);
|
|
19896
21044
|
speechSvc.refreshProviders(newSpeechConfig);
|
|
19897
|
-
|
|
21045
|
+
log18.info("Speech service config updated at runtime (from plugin settings)");
|
|
19898
21046
|
}
|
|
19899
21047
|
}
|
|
19900
21048
|
}
|
|
@@ -19935,14 +21083,14 @@ var OpenACPCore = class {
|
|
|
19935
21083
|
*/
|
|
19936
21084
|
async start() {
|
|
19937
21085
|
this.agentCatalog.refreshRegistryIfStale().catch((err) => {
|
|
19938
|
-
|
|
21086
|
+
log18.warn({ err }, "Background registry refresh failed");
|
|
19939
21087
|
});
|
|
19940
21088
|
const failures = [];
|
|
19941
21089
|
for (const [name, adapter] of this.adapters.entries()) {
|
|
19942
21090
|
try {
|
|
19943
21091
|
await adapter.start();
|
|
19944
21092
|
} catch (err) {
|
|
19945
|
-
|
|
21093
|
+
log18.error({ err, adapter: name }, `Adapter "${name}" failed to start`);
|
|
19946
21094
|
failures.push({ name, error: err });
|
|
19947
21095
|
}
|
|
19948
21096
|
}
|
|
@@ -20023,7 +21171,7 @@ var OpenACPCore = class {
|
|
|
20023
21171
|
* If no session is found, the user is told to start one with /new.
|
|
20024
21172
|
*/
|
|
20025
21173
|
async handleMessage(message, initialMeta) {
|
|
20026
|
-
|
|
21174
|
+
log18.debug(
|
|
20027
21175
|
{
|
|
20028
21176
|
channelId: message.channelId,
|
|
20029
21177
|
threadId: message.threadId,
|
|
@@ -20044,7 +21192,7 @@ var OpenACPCore = class {
|
|
|
20044
21192
|
}
|
|
20045
21193
|
const access2 = await this.securityGuard.checkAccess(message);
|
|
20046
21194
|
if (!access2.allowed) {
|
|
20047
|
-
|
|
21195
|
+
log18.warn({ userId: message.userId, reason: access2.reason }, "Access denied");
|
|
20048
21196
|
if (access2.reason.includes("Session limit")) {
|
|
20049
21197
|
const adapter = this.adapters.get(message.channelId);
|
|
20050
21198
|
if (adapter) {
|
|
@@ -20058,7 +21206,7 @@ var OpenACPCore = class {
|
|
|
20058
21206
|
}
|
|
20059
21207
|
let session = await this.sessionFactory.getOrResume(message.channelId, message.threadId);
|
|
20060
21208
|
if (!session) {
|
|
20061
|
-
|
|
21209
|
+
log18.warn(
|
|
20062
21210
|
{ channelId: message.channelId, threadId: message.threadId },
|
|
20063
21211
|
"No session found for thread (in-memory miss + lazy resume returned null)"
|
|
20064
21212
|
);
|
|
@@ -20110,7 +21258,7 @@ ${text3}`;
|
|
|
20110
21258
|
});
|
|
20111
21259
|
session.enqueuePrompt(text3, attachments, routing, turnId, meta).catch((err) => {
|
|
20112
21260
|
const reason = err instanceof Error ? err.message : String(err);
|
|
20113
|
-
|
|
21261
|
+
log18.warn({ err, sessionId: session.id, turnId, reason }, "enqueuePrompt failed \u2014 emitting message:failed");
|
|
20114
21262
|
this.eventBus.emit(BusEvent.MESSAGE_FAILED, {
|
|
20115
21263
|
sessionId: session.id,
|
|
20116
21264
|
turnId,
|
|
@@ -20227,7 +21375,7 @@ ${text3}`;
|
|
|
20227
21375
|
const bridge = this.createBridge(session, adapter, session.channelId);
|
|
20228
21376
|
bridge.connect();
|
|
20229
21377
|
adapter.flushPendingSkillCommands?.(session.id).catch((err) => {
|
|
20230
|
-
|
|
21378
|
+
log18.warn({ err, sessionId: session.id }, "Failed to flush pending skill commands");
|
|
20231
21379
|
});
|
|
20232
21380
|
if (params.createThread && session.threadId) {
|
|
20233
21381
|
this.eventBus.emit(BusEvent.SESSION_THREAD_READY, {
|
|
@@ -20241,14 +21389,14 @@ ${text3}`;
|
|
|
20241
21389
|
session.agentInstance.onPermissionRequest = async (permRequest) => {
|
|
20242
21390
|
const allowOption = permRequest.options.find((o) => o.isAllow);
|
|
20243
21391
|
if (!allowOption) {
|
|
20244
|
-
|
|
21392
|
+
log18.warn(
|
|
20245
21393
|
{ sessionId: session.id, permissionId: permRequest.id, description: permRequest.description },
|
|
20246
21394
|
"Headless session has no allow option for permission request \u2014 skipping auto-approve, will time out"
|
|
20247
21395
|
);
|
|
20248
21396
|
return new Promise(() => {
|
|
20249
21397
|
});
|
|
20250
21398
|
}
|
|
20251
|
-
|
|
21399
|
+
log18.warn(
|
|
20252
21400
|
{ sessionId: session.id, permissionId: permRequest.id, option: allowOption.id },
|
|
20253
21401
|
`Auto-approving permission "${permRequest.description}" for headless session \u2014 no adapter connected`
|
|
20254
21402
|
);
|
|
@@ -20302,7 +21450,7 @@ ${text3}`;
|
|
|
20302
21450
|
notificationManager: this.notificationManager,
|
|
20303
21451
|
tunnelService: this._tunnelService
|
|
20304
21452
|
});
|
|
20305
|
-
|
|
21453
|
+
log18.info(
|
|
20306
21454
|
{ sessionId: session.id, agentName: params.agentName },
|
|
20307
21455
|
"Session created via pipeline"
|
|
20308
21456
|
);
|
|
@@ -20721,33 +21869,33 @@ init_doctor();
|
|
|
20721
21869
|
init_config_registry();
|
|
20722
21870
|
|
|
20723
21871
|
// src/core/config/config-editor.ts
|
|
20724
|
-
import * as
|
|
21872
|
+
import * as path33 from "path";
|
|
20725
21873
|
import * as clack2 from "@clack/prompts";
|
|
20726
21874
|
|
|
20727
21875
|
// src/cli/autostart.ts
|
|
20728
21876
|
init_log();
|
|
20729
21877
|
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 =
|
|
21878
|
+
import * as fs29 from "fs";
|
|
21879
|
+
import * as path27 from "path";
|
|
21880
|
+
import * as os9 from "os";
|
|
21881
|
+
var log20 = createChildLogger({ module: "autostart" });
|
|
21882
|
+
var LEGACY_LAUNCHD_PLIST_PATH = path27.join(os9.homedir(), "Library", "LaunchAgents", "com.openacp.daemon.plist");
|
|
21883
|
+
var LEGACY_SYSTEMD_SERVICE_PATH = path27.join(os9.homedir(), ".config", "systemd", "user", "openacp.service");
|
|
20736
21884
|
function getLaunchdLabel(instanceId) {
|
|
20737
21885
|
return `com.openacp.daemon.${instanceId}`;
|
|
20738
21886
|
}
|
|
20739
21887
|
function getLaunchdPlistPath(instanceId) {
|
|
20740
|
-
return
|
|
21888
|
+
return path27.join(os9.homedir(), "Library", "LaunchAgents", `${getLaunchdLabel(instanceId)}.plist`);
|
|
20741
21889
|
}
|
|
20742
21890
|
function getSystemdServiceName(instanceId) {
|
|
20743
21891
|
return `openacp-${instanceId}`;
|
|
20744
21892
|
}
|
|
20745
21893
|
function getSystemdServicePath(instanceId) {
|
|
20746
|
-
return
|
|
21894
|
+
return path27.join(os9.homedir(), ".config", "systemd", "user", `${getSystemdServiceName(instanceId)}.service`);
|
|
20747
21895
|
}
|
|
20748
21896
|
function getAutoStartState(instanceId) {
|
|
20749
21897
|
if (process.platform === "linux") {
|
|
20750
|
-
const installed =
|
|
21898
|
+
const installed = fs29.existsSync(getSystemdServicePath(instanceId));
|
|
20751
21899
|
if (!installed) return { installed: false, manager: null, active: false };
|
|
20752
21900
|
try {
|
|
20753
21901
|
const output = execFileSync6("systemctl", [
|
|
@@ -20769,7 +21917,7 @@ function getAutoStartState(instanceId) {
|
|
|
20769
21917
|
}
|
|
20770
21918
|
}
|
|
20771
21919
|
if (process.platform === "darwin") {
|
|
20772
|
-
const installed =
|
|
21920
|
+
const installed = fs29.existsSync(getLaunchdPlistPath(instanceId));
|
|
20773
21921
|
if (!installed) return { installed: false, manager: null, active: false };
|
|
20774
21922
|
try {
|
|
20775
21923
|
const uid = process.getuid();
|
|
@@ -20799,7 +21947,7 @@ function escapeSystemdValue(str) {
|
|
|
20799
21947
|
}
|
|
20800
21948
|
function generateLaunchdPlist(nodePath, cliPath, logDir2, instanceRoot, instanceId) {
|
|
20801
21949
|
const label = getLaunchdLabel(instanceId);
|
|
20802
|
-
const logFile =
|
|
21950
|
+
const logFile = path27.join(logDir2, "openacp.log");
|
|
20803
21951
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
20804
21952
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
20805
21953
|
<plist version="1.0">
|
|
@@ -20851,32 +21999,32 @@ WantedBy=default.target
|
|
|
20851
21999
|
`;
|
|
20852
22000
|
}
|
|
20853
22001
|
function migrateLegacy() {
|
|
20854
|
-
if (process.platform === "darwin" &&
|
|
22002
|
+
if (process.platform === "darwin" && fs29.existsSync(LEGACY_LAUNCHD_PLIST_PATH)) {
|
|
20855
22003
|
try {
|
|
20856
22004
|
const uid = process.getuid();
|
|
20857
22005
|
execFileSync6("launchctl", ["bootout", `gui/${uid}`, "com.openacp.daemon"], { stdio: "pipe" });
|
|
20858
22006
|
} catch {
|
|
20859
22007
|
}
|
|
20860
22008
|
try {
|
|
20861
|
-
|
|
22009
|
+
fs29.unlinkSync(LEGACY_LAUNCHD_PLIST_PATH);
|
|
20862
22010
|
} catch {
|
|
20863
22011
|
}
|
|
20864
|
-
|
|
22012
|
+
log20.info("Removed legacy single-instance LaunchAgent");
|
|
20865
22013
|
}
|
|
20866
|
-
if (process.platform === "linux" &&
|
|
22014
|
+
if (process.platform === "linux" && fs29.existsSync(LEGACY_SYSTEMD_SERVICE_PATH)) {
|
|
20867
22015
|
try {
|
|
20868
22016
|
execFileSync6("systemctl", ["--user", "disable", "openacp"], { stdio: "pipe" });
|
|
20869
22017
|
} catch {
|
|
20870
22018
|
}
|
|
20871
22019
|
try {
|
|
20872
|
-
|
|
22020
|
+
fs29.unlinkSync(LEGACY_SYSTEMD_SERVICE_PATH);
|
|
20873
22021
|
} catch {
|
|
20874
22022
|
}
|
|
20875
22023
|
try {
|
|
20876
22024
|
execFileSync6("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
|
|
20877
22025
|
} catch {
|
|
20878
22026
|
}
|
|
20879
|
-
|
|
22027
|
+
log20.info("Removed legacy single-instance systemd service");
|
|
20880
22028
|
}
|
|
20881
22029
|
}
|
|
20882
22030
|
function installAutoStart(logDir2, instanceRoot, instanceId) {
|
|
@@ -20884,26 +22032,26 @@ function installAutoStart(logDir2, instanceRoot, instanceId) {
|
|
|
20884
22032
|
return { success: false, error: "Auto-start not supported on this platform" };
|
|
20885
22033
|
}
|
|
20886
22034
|
const nodePath = process.execPath;
|
|
20887
|
-
const cliPath =
|
|
20888
|
-
const resolvedLogDir = logDir2.startsWith("~") ?
|
|
22035
|
+
const cliPath = path27.resolve(process.argv[1]);
|
|
22036
|
+
const resolvedLogDir = logDir2.startsWith("~") ? path27.join(os9.homedir(), logDir2.slice(1)) : logDir2;
|
|
20889
22037
|
try {
|
|
20890
22038
|
migrateLegacy();
|
|
20891
22039
|
if (process.platform === "darwin") {
|
|
20892
22040
|
const plistPath = getLaunchdPlistPath(instanceId);
|
|
20893
22041
|
const plist = generateLaunchdPlist(nodePath, cliPath, resolvedLogDir, instanceRoot, instanceId);
|
|
20894
|
-
const dir =
|
|
20895
|
-
|
|
22042
|
+
const dir = path27.dirname(plistPath);
|
|
22043
|
+
fs29.mkdirSync(dir, { recursive: true });
|
|
20896
22044
|
const uid = process.getuid();
|
|
20897
22045
|
const domain = `gui/${uid}`;
|
|
20898
|
-
const previous =
|
|
22046
|
+
const previous = fs29.existsSync(plistPath) ? fs29.readFileSync(plistPath, "utf8") : void 0;
|
|
20899
22047
|
const previousState = getAutoStartState(instanceId);
|
|
20900
22048
|
const tmp = `${plistPath}.${process.pid}.tmp`;
|
|
20901
22049
|
let replaced = false;
|
|
20902
22050
|
let bootedOut = false;
|
|
20903
22051
|
try {
|
|
20904
|
-
|
|
22052
|
+
fs29.writeFileSync(tmp, plist, { mode: 384 });
|
|
20905
22053
|
execFileSync6("plutil", ["-lint", tmp], { stdio: "pipe" });
|
|
20906
|
-
|
|
22054
|
+
fs29.renameSync(tmp, plistPath);
|
|
20907
22055
|
replaced = true;
|
|
20908
22056
|
if (previousState.active) {
|
|
20909
22057
|
execFileSync6("launchctl", ["bootout", domain, plistPath], { stdio: "pipe" });
|
|
@@ -20912,7 +22060,7 @@ function installAutoStart(logDir2, instanceRoot, instanceId) {
|
|
|
20912
22060
|
execFileSync6("launchctl", ["bootstrap", domain, plistPath], { stdio: "pipe" });
|
|
20913
22061
|
} catch (error) {
|
|
20914
22062
|
try {
|
|
20915
|
-
|
|
22063
|
+
fs29.rmSync(tmp, { force: true });
|
|
20916
22064
|
} catch {
|
|
20917
22065
|
}
|
|
20918
22066
|
if (replaced) {
|
|
@@ -20922,48 +22070,48 @@ function installAutoStart(logDir2, instanceRoot, instanceId) {
|
|
|
20922
22070
|
rollbackBootedOut = true;
|
|
20923
22071
|
} catch {
|
|
20924
22072
|
}
|
|
20925
|
-
if (previous === void 0)
|
|
22073
|
+
if (previous === void 0) fs29.rmSync(plistPath, { force: true });
|
|
20926
22074
|
else {
|
|
20927
22075
|
const rollbackTmp = `${plistPath}.${process.pid}.rollback.tmp`;
|
|
20928
|
-
|
|
20929
|
-
|
|
22076
|
+
fs29.writeFileSync(rollbackTmp, previous, { mode: 384 });
|
|
22077
|
+
fs29.renameSync(rollbackTmp, plistPath);
|
|
20930
22078
|
if (previousState.active || bootedOut || rollbackBootedOut) execFileSync6("launchctl", ["bootstrap", domain, plistPath], { stdio: "pipe" });
|
|
20931
22079
|
}
|
|
20932
22080
|
}
|
|
20933
22081
|
throw error;
|
|
20934
22082
|
}
|
|
20935
|
-
|
|
22083
|
+
log20.info({ instanceId }, "LaunchAgent installed");
|
|
20936
22084
|
return { success: true };
|
|
20937
22085
|
}
|
|
20938
22086
|
if (process.platform === "linux") {
|
|
20939
22087
|
const servicePath = getSystemdServicePath(instanceId);
|
|
20940
22088
|
const serviceName = getSystemdServiceName(instanceId);
|
|
20941
22089
|
const unit = generateSystemdUnit(nodePath, cliPath, instanceRoot, instanceId);
|
|
20942
|
-
const dir =
|
|
20943
|
-
|
|
20944
|
-
const previous =
|
|
22090
|
+
const dir = path27.dirname(servicePath);
|
|
22091
|
+
fs29.mkdirSync(dir, { recursive: true });
|
|
22092
|
+
const previous = fs29.existsSync(servicePath) ? fs29.readFileSync(servicePath, "utf8") : void 0;
|
|
20945
22093
|
const tmp = `${servicePath}.${process.pid}.tmp`;
|
|
20946
|
-
|
|
20947
|
-
|
|
22094
|
+
fs29.writeFileSync(tmp, unit, { mode: 384 });
|
|
22095
|
+
fs29.renameSync(tmp, servicePath);
|
|
20948
22096
|
try {
|
|
20949
22097
|
execFileSync6("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
|
|
20950
22098
|
execFileSync6("systemctl", ["--user", "enable", serviceName], { stdio: "pipe" });
|
|
20951
22099
|
} catch (error) {
|
|
20952
|
-
if (previous === void 0)
|
|
20953
|
-
else
|
|
22100
|
+
if (previous === void 0) fs29.unlinkSync(servicePath);
|
|
22101
|
+
else fs29.writeFileSync(servicePath, previous);
|
|
20954
22102
|
try {
|
|
20955
22103
|
execFileSync6("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
|
|
20956
22104
|
} catch {
|
|
20957
22105
|
}
|
|
20958
22106
|
throw error;
|
|
20959
22107
|
}
|
|
20960
|
-
|
|
22108
|
+
log20.info({ instanceId }, "systemd user service installed");
|
|
20961
22109
|
return { success: true };
|
|
20962
22110
|
}
|
|
20963
22111
|
return { success: false, error: "Unsupported platform" };
|
|
20964
22112
|
} catch (e) {
|
|
20965
22113
|
const msg = e.message;
|
|
20966
|
-
|
|
22114
|
+
log20.error({ err: msg }, "Failed to install auto-start");
|
|
20967
22115
|
return { success: false, error: msg };
|
|
20968
22116
|
}
|
|
20969
22117
|
}
|
|
@@ -20974,44 +22122,44 @@ function uninstallAutoStart(instanceId) {
|
|
|
20974
22122
|
try {
|
|
20975
22123
|
if (process.platform === "darwin") {
|
|
20976
22124
|
const plistPath = getLaunchdPlistPath(instanceId);
|
|
20977
|
-
if (
|
|
22125
|
+
if (fs29.existsSync(plistPath)) {
|
|
20978
22126
|
const uid = process.getuid();
|
|
20979
22127
|
try {
|
|
20980
22128
|
execFileSync6("launchctl", ["bootout", `gui/${uid}`, plistPath], { stdio: "pipe" });
|
|
20981
22129
|
} catch {
|
|
20982
22130
|
}
|
|
20983
|
-
|
|
20984
|
-
|
|
22131
|
+
fs29.unlinkSync(plistPath);
|
|
22132
|
+
log20.info({ instanceId }, "LaunchAgent removed");
|
|
20985
22133
|
}
|
|
20986
22134
|
return { success: true };
|
|
20987
22135
|
}
|
|
20988
22136
|
if (process.platform === "linux") {
|
|
20989
22137
|
const servicePath = getSystemdServicePath(instanceId);
|
|
20990
22138
|
const serviceName = getSystemdServiceName(instanceId);
|
|
20991
|
-
if (
|
|
22139
|
+
if (fs29.existsSync(servicePath)) {
|
|
20992
22140
|
try {
|
|
20993
22141
|
execFileSync6("systemctl", ["--user", "disable", serviceName], { stdio: "pipe" });
|
|
20994
22142
|
} catch {
|
|
20995
22143
|
}
|
|
20996
|
-
|
|
22144
|
+
fs29.unlinkSync(servicePath);
|
|
20997
22145
|
execFileSync6("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
|
|
20998
|
-
|
|
22146
|
+
log20.info({ instanceId }, "systemd user service removed");
|
|
20999
22147
|
}
|
|
21000
22148
|
return { success: true };
|
|
21001
22149
|
}
|
|
21002
22150
|
return { success: false, error: "Unsupported platform" };
|
|
21003
22151
|
} catch (e) {
|
|
21004
22152
|
const msg = e.message;
|
|
21005
|
-
|
|
22153
|
+
log20.error({ err: msg }, "Failed to uninstall auto-start");
|
|
21006
22154
|
return { success: false, error: msg };
|
|
21007
22155
|
}
|
|
21008
22156
|
}
|
|
21009
22157
|
function isAutoStartInstalled(instanceId) {
|
|
21010
22158
|
if (process.platform === "darwin") {
|
|
21011
|
-
return
|
|
22159
|
+
return fs29.existsSync(getLaunchdPlistPath(instanceId));
|
|
21012
22160
|
}
|
|
21013
22161
|
if (process.platform === "linux") {
|
|
21014
|
-
return
|
|
22162
|
+
return fs29.existsSync(getSystemdServicePath(instanceId));
|
|
21015
22163
|
}
|
|
21016
22164
|
return false;
|
|
21017
22165
|
}
|
|
@@ -21020,25 +22168,25 @@ function isAutoStartInstalled(instanceId) {
|
|
|
21020
22168
|
init_instance_context();
|
|
21021
22169
|
init_instance_registry();
|
|
21022
22170
|
init_log();
|
|
21023
|
-
import
|
|
21024
|
-
import
|
|
21025
|
-
var
|
|
22171
|
+
import fs31 from "fs";
|
|
22172
|
+
import path29 from "path";
|
|
22173
|
+
var log21 = createChildLogger({ module: "resolve-instance-id" });
|
|
21026
22174
|
function resolveInstanceId(instanceRoot) {
|
|
21027
22175
|
try {
|
|
21028
|
-
const configPath =
|
|
21029
|
-
const raw = JSON.parse(
|
|
22176
|
+
const configPath = path29.join(instanceRoot, "config.json");
|
|
22177
|
+
const raw = JSON.parse(fs31.readFileSync(configPath, "utf-8"));
|
|
21030
22178
|
if (raw.id && typeof raw.id === "string") return raw.id;
|
|
21031
22179
|
} catch {
|
|
21032
22180
|
}
|
|
21033
22181
|
try {
|
|
21034
|
-
const reg = new InstanceRegistry(
|
|
22182
|
+
const reg = new InstanceRegistry(path29.join(getGlobalRoot(), "instances.json"));
|
|
21035
22183
|
reg.load();
|
|
21036
22184
|
const entry = reg.getByRoot(instanceRoot);
|
|
21037
22185
|
if (entry?.id) return entry.id;
|
|
21038
22186
|
} catch (err) {
|
|
21039
|
-
|
|
22187
|
+
log21.debug({ err: err.message, instanceRoot }, "Could not read instance registry, using fallback id");
|
|
21040
22188
|
}
|
|
21041
|
-
return
|
|
22189
|
+
return path29.basename(path29.dirname(instanceRoot)).replace(/[^a-zA-Z0-9-]/g, "-") || "default";
|
|
21042
22190
|
}
|
|
21043
22191
|
|
|
21044
22192
|
// src/core/config/config-editor.ts
|
|
@@ -21610,7 +22758,7 @@ async function runConfigEditor(configManager, mode = "file", apiPort, settingsMa
|
|
|
21610
22758
|
await configManager.load();
|
|
21611
22759
|
const config = configManager.get();
|
|
21612
22760
|
const updates = {};
|
|
21613
|
-
const instanceRoot =
|
|
22761
|
+
const instanceRoot = path33.dirname(configManager.getConfigPath());
|
|
21614
22762
|
console.log(`
|
|
21615
22763
|
${c.cyan}${c.bold}OpenACP Config Editor${c.reset}`);
|
|
21616
22764
|
console.log(dim(`Config: ${configManager.getConfigPath()}`));
|
|
@@ -21667,17 +22815,17 @@ ${c.cyan}${c.bold}OpenACP Config Editor${c.reset}`);
|
|
|
21667
22815
|
async function sendConfigViaApi(port, updates) {
|
|
21668
22816
|
const { apiCall: call } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
21669
22817
|
const paths = flattenToPaths(updates);
|
|
21670
|
-
for (const { path:
|
|
22818
|
+
for (const { path: path38, value } of paths) {
|
|
21671
22819
|
const res = await call(port, "/api/config", {
|
|
21672
22820
|
method: "PATCH",
|
|
21673
22821
|
headers: { "Content-Type": "application/json" },
|
|
21674
|
-
body: JSON.stringify({ path:
|
|
22822
|
+
body: JSON.stringify({ path: path38, value })
|
|
21675
22823
|
});
|
|
21676
22824
|
const data = await res.json();
|
|
21677
22825
|
if (!res.ok) {
|
|
21678
|
-
console.log(warn(`Failed to update ${
|
|
22826
|
+
console.log(warn(`Failed to update ${path38}: ${data.error}`));
|
|
21679
22827
|
} else if (data.needsRestart) {
|
|
21680
|
-
console.log(warn(`${
|
|
22828
|
+
console.log(warn(`${path38} updated \u2014 restart required`));
|
|
21681
22829
|
}
|
|
21682
22830
|
}
|
|
21683
22831
|
}
|
|
@@ -21697,25 +22845,25 @@ function flattenToPaths(obj, prefix = "") {
|
|
|
21697
22845
|
// src/cli/daemon.ts
|
|
21698
22846
|
init_config();
|
|
21699
22847
|
import { spawn as spawn3 } from "child_process";
|
|
21700
|
-
import * as
|
|
21701
|
-
import * as
|
|
22848
|
+
import * as fs34 from "fs";
|
|
22849
|
+
import * as path34 from "path";
|
|
21702
22850
|
function getPidPath(root) {
|
|
21703
|
-
return
|
|
22851
|
+
return path34.join(root, "openacp.pid");
|
|
21704
22852
|
}
|
|
21705
22853
|
function getLogDir(root) {
|
|
21706
|
-
return
|
|
22854
|
+
return path34.join(root, "logs");
|
|
21707
22855
|
}
|
|
21708
22856
|
function getRunningMarker(root) {
|
|
21709
|
-
return
|
|
22857
|
+
return path34.join(root, "running");
|
|
21710
22858
|
}
|
|
21711
22859
|
function writePidFile(pidPath, pid) {
|
|
21712
|
-
const dir =
|
|
21713
|
-
|
|
21714
|
-
|
|
22860
|
+
const dir = path34.dirname(pidPath);
|
|
22861
|
+
fs34.mkdirSync(dir, { recursive: true });
|
|
22862
|
+
fs34.writeFileSync(pidPath, String(pid));
|
|
21715
22863
|
}
|
|
21716
22864
|
function readPidFile(pidPath) {
|
|
21717
22865
|
try {
|
|
21718
|
-
const content =
|
|
22866
|
+
const content = fs34.readFileSync(pidPath, "utf-8").trim();
|
|
21719
22867
|
const pid = parseInt(content, 10);
|
|
21720
22868
|
return isNaN(pid) ? null : pid;
|
|
21721
22869
|
} catch {
|
|
@@ -21724,7 +22872,7 @@ function readPidFile(pidPath) {
|
|
|
21724
22872
|
}
|
|
21725
22873
|
function removePidFile(pidPath) {
|
|
21726
22874
|
try {
|
|
21727
|
-
|
|
22875
|
+
fs34.unlinkSync(pidPath);
|
|
21728
22876
|
} catch {
|
|
21729
22877
|
}
|
|
21730
22878
|
}
|
|
@@ -21757,12 +22905,12 @@ function startDaemon(pidPath, logDir2, instanceRoot) {
|
|
|
21757
22905
|
return { error: `Already running (PID ${pid})` };
|
|
21758
22906
|
}
|
|
21759
22907
|
const resolvedLogDir = logDir2 ? expandHome2(logDir2) : getLogDir(instanceRoot);
|
|
21760
|
-
|
|
21761
|
-
const logFile =
|
|
21762
|
-
const cliPath =
|
|
22908
|
+
fs34.mkdirSync(resolvedLogDir, { recursive: true });
|
|
22909
|
+
const logFile = path34.join(resolvedLogDir, "openacp.log");
|
|
22910
|
+
const cliPath = path34.resolve(process.argv[1]);
|
|
21763
22911
|
const nodePath = process.execPath;
|
|
21764
|
-
const out =
|
|
21765
|
-
const err =
|
|
22912
|
+
const out = fs34.openSync(logFile, "a");
|
|
22913
|
+
const err = fs34.openSync(logFile, "a");
|
|
21766
22914
|
const child = spawn3(nodePath, [cliPath, "--daemon-child"], {
|
|
21767
22915
|
detached: true,
|
|
21768
22916
|
stdio: ["ignore", out, err],
|
|
@@ -21771,8 +22919,8 @@ function startDaemon(pidPath, logDir2, instanceRoot) {
|
|
|
21771
22919
|
...instanceRoot ? { OPENACP_INSTANCE_ROOT: instanceRoot } : {}
|
|
21772
22920
|
}
|
|
21773
22921
|
});
|
|
21774
|
-
|
|
21775
|
-
|
|
22922
|
+
fs34.closeSync(out);
|
|
22923
|
+
fs34.closeSync(err);
|
|
21776
22924
|
if (!child.pid) {
|
|
21777
22925
|
return { error: "Failed to spawn daemon process" };
|
|
21778
22926
|
}
|
|
@@ -21843,12 +22991,12 @@ async function stopDaemon(pidPath, instanceRoot) {
|
|
|
21843
22991
|
}
|
|
21844
22992
|
function markRunning(root) {
|
|
21845
22993
|
const marker = getRunningMarker(root);
|
|
21846
|
-
|
|
21847
|
-
|
|
22994
|
+
fs34.mkdirSync(path34.dirname(marker), { recursive: true });
|
|
22995
|
+
fs34.writeFileSync(marker, "");
|
|
21848
22996
|
}
|
|
21849
22997
|
function clearRunning(root) {
|
|
21850
22998
|
try {
|
|
21851
|
-
|
|
22999
|
+
fs34.unlinkSync(getRunningMarker(root));
|
|
21852
23000
|
} catch {
|
|
21853
23001
|
}
|
|
21854
23002
|
}
|
|
@@ -21864,7 +23012,7 @@ init_static_server();
|
|
|
21864
23012
|
|
|
21865
23013
|
// src/plugins/telegram/topic-manager.ts
|
|
21866
23014
|
init_log();
|
|
21867
|
-
var
|
|
23015
|
+
var log24 = createChildLogger({ module: "topic-manager" });
|
|
21868
23016
|
var TopicManager = class {
|
|
21869
23017
|
constructor(sessionManager, adapter, systemTopicIds) {
|
|
21870
23018
|
this.sessionManager = sessionManager;
|
|
@@ -21916,7 +23064,7 @@ var TopicManager = class {
|
|
|
21916
23064
|
try {
|
|
21917
23065
|
await this.adapter.deleteSessionThread?.(sessionId);
|
|
21918
23066
|
} catch (err) {
|
|
21919
|
-
|
|
23067
|
+
log24.warn({ err, sessionId, topicId }, "Failed to delete platform thread, removing record anyway");
|
|
21920
23068
|
}
|
|
21921
23069
|
}
|
|
21922
23070
|
await this.sessionManager.removeRecord(sessionId);
|
|
@@ -21943,7 +23091,7 @@ var TopicManager = class {
|
|
|
21943
23091
|
try {
|
|
21944
23092
|
await this.adapter.deleteSessionThread?.(record.sessionId);
|
|
21945
23093
|
} catch (err) {
|
|
21946
|
-
|
|
23094
|
+
log24.warn({ err, sessionId: record.sessionId }, "Failed to delete platform thread during cleanup");
|
|
21947
23095
|
}
|
|
21948
23096
|
}
|
|
21949
23097
|
await this.sessionManager.removeRecord(record.sessionId);
|