@openacp/cli 2026.408.4 → 2026.410.1
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/dist/cli.d.ts +0 -1
- package/dist/cli.js +2063 -1672
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +16 -20
- package/dist/index.js +989 -876
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -245,6 +245,9 @@ var init_log = __esm({
|
|
|
245
245
|
});
|
|
246
246
|
|
|
247
247
|
// src/core/config/config-migrations.ts
|
|
248
|
+
import fs2 from "fs";
|
|
249
|
+
import path2 from "path";
|
|
250
|
+
import os2 from "os";
|
|
248
251
|
function applyMigrations(raw, migrationList = migrations, ctx) {
|
|
249
252
|
let changed = false;
|
|
250
253
|
for (const migration of migrationList) {
|
|
@@ -278,108 +281,34 @@ var init_config_migrations = __esm({
|
|
|
278
281
|
log2.info("Removed legacy displayVerbosity key from config");
|
|
279
282
|
return true;
|
|
280
283
|
}
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
name: "add-instance-id",
|
|
287
|
+
apply(raw, ctx) {
|
|
288
|
+
if (raw.id) return false;
|
|
289
|
+
if (!ctx?.configDir) return false;
|
|
290
|
+
const instanceRoot = ctx.configDir;
|
|
291
|
+
try {
|
|
292
|
+
const registryPath = path2.join(os2.homedir(), ".openacp", "instances.json");
|
|
293
|
+
const data = JSON.parse(fs2.readFileSync(registryPath, "utf-8"));
|
|
294
|
+
const instances = data?.instances ?? {};
|
|
295
|
+
const entry = Object.values(instances).find(
|
|
296
|
+
(e) => e.root === instanceRoot
|
|
297
|
+
);
|
|
298
|
+
if (entry?.id) {
|
|
299
|
+
raw.id = entry.id;
|
|
300
|
+
log2.info({ instanceRoot }, "Migrated: added id to config from registry");
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
} catch {
|
|
304
|
+
}
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
281
307
|
}
|
|
282
308
|
];
|
|
283
309
|
}
|
|
284
310
|
});
|
|
285
311
|
|
|
286
|
-
// src/core/instance/instance-context.ts
|
|
287
|
-
var instance_context_exports = {};
|
|
288
|
-
__export(instance_context_exports, {
|
|
289
|
-
createInstanceContext: () => createInstanceContext,
|
|
290
|
-
generateSlug: () => generateSlug,
|
|
291
|
-
getGlobalRoot: () => getGlobalRoot,
|
|
292
|
-
resolveInstanceRoot: () => resolveInstanceRoot,
|
|
293
|
-
resolveRunningInstance: () => resolveRunningInstance
|
|
294
|
-
});
|
|
295
|
-
import path2 from "path";
|
|
296
|
-
import fs2 from "fs";
|
|
297
|
-
import os2 from "os";
|
|
298
|
-
function createInstanceContext(opts) {
|
|
299
|
-
const { id, root, isGlobal } = opts;
|
|
300
|
-
return {
|
|
301
|
-
id,
|
|
302
|
-
root,
|
|
303
|
-
isGlobal,
|
|
304
|
-
paths: {
|
|
305
|
-
config: path2.join(root, "config.json"),
|
|
306
|
-
sessions: path2.join(root, "sessions.json"),
|
|
307
|
-
agents: path2.join(root, "agents.json"),
|
|
308
|
-
registryCache: path2.join(root, "registry-cache.json"),
|
|
309
|
-
plugins: path2.join(root, "plugins"),
|
|
310
|
-
pluginsData: path2.join(root, "plugins", "data"),
|
|
311
|
-
pluginRegistry: path2.join(root, "plugins.json"),
|
|
312
|
-
logs: path2.join(root, "logs"),
|
|
313
|
-
pid: path2.join(root, "openacp.pid"),
|
|
314
|
-
running: path2.join(root, "running"),
|
|
315
|
-
apiPort: path2.join(root, "api.port"),
|
|
316
|
-
apiSecret: path2.join(root, "api-secret"),
|
|
317
|
-
bin: path2.join(root, "bin"),
|
|
318
|
-
cache: path2.join(root, "cache"),
|
|
319
|
-
tunnels: path2.join(root, "tunnels.json"),
|
|
320
|
-
agentsDir: path2.join(root, "agents")
|
|
321
|
-
}
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
function generateSlug(name) {
|
|
325
|
-
const slug = name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
326
|
-
return slug || "openacp";
|
|
327
|
-
}
|
|
328
|
-
function expandHome2(p) {
|
|
329
|
-
if (p.startsWith("~")) return path2.join(os2.homedir(), p.slice(1));
|
|
330
|
-
return p;
|
|
331
|
-
}
|
|
332
|
-
function resolveInstanceRoot(opts) {
|
|
333
|
-
const cwd = opts.cwd ?? process.cwd();
|
|
334
|
-
if (opts.dir) return path2.join(expandHome2(opts.dir), ".openacp");
|
|
335
|
-
if (opts.local) return path2.join(cwd, ".openacp");
|
|
336
|
-
if (opts.global) return path2.join(os2.homedir(), ".openacp");
|
|
337
|
-
const localRoot = path2.join(cwd, ".openacp");
|
|
338
|
-
if (fs2.existsSync(localRoot)) return localRoot;
|
|
339
|
-
if (process.env.OPENACP_INSTANCE_ROOT) return process.env.OPENACP_INSTANCE_ROOT;
|
|
340
|
-
return null;
|
|
341
|
-
}
|
|
342
|
-
function getGlobalRoot() {
|
|
343
|
-
return path2.join(os2.homedir(), ".openacp");
|
|
344
|
-
}
|
|
345
|
-
async function resolveRunningInstance(cwd) {
|
|
346
|
-
const globalRoot = getGlobalRoot();
|
|
347
|
-
let dir = path2.resolve(cwd);
|
|
348
|
-
while (true) {
|
|
349
|
-
const candidate = path2.join(dir, ".openacp");
|
|
350
|
-
if (candidate !== globalRoot && fs2.existsSync(candidate)) {
|
|
351
|
-
if (await isInstanceRunning(candidate)) return candidate;
|
|
352
|
-
}
|
|
353
|
-
const parent = path2.dirname(dir);
|
|
354
|
-
if (parent === dir) break;
|
|
355
|
-
dir = parent;
|
|
356
|
-
}
|
|
357
|
-
if (fs2.existsSync(globalRoot) && await isInstanceRunning(globalRoot)) return globalRoot;
|
|
358
|
-
return null;
|
|
359
|
-
}
|
|
360
|
-
async function isInstanceRunning(instanceRoot) {
|
|
361
|
-
const portFile = path2.join(instanceRoot, "api.port");
|
|
362
|
-
try {
|
|
363
|
-
const content = fs2.readFileSync(portFile, "utf-8").trim();
|
|
364
|
-
const port = parseInt(content, 10);
|
|
365
|
-
if (isNaN(port)) return false;
|
|
366
|
-
const controller = new AbortController();
|
|
367
|
-
const timeout = setTimeout(() => controller.abort(), 2e3);
|
|
368
|
-
const res = await fetch(`http://127.0.0.1:${port}/api/v1/system/health`, {
|
|
369
|
-
signal: controller.signal
|
|
370
|
-
});
|
|
371
|
-
clearTimeout(timeout);
|
|
372
|
-
return res.ok;
|
|
373
|
-
} catch {
|
|
374
|
-
return false;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
var init_instance_context = __esm({
|
|
378
|
-
"src/core/instance/instance-context.ts"() {
|
|
379
|
-
"use strict";
|
|
380
|
-
}
|
|
381
|
-
});
|
|
382
|
-
|
|
383
312
|
// src/core/config/config-registry.ts
|
|
384
313
|
var config_registry_exports = {};
|
|
385
314
|
__export(config_registry_exports, {
|
|
@@ -392,24 +321,22 @@ __export(config_registry_exports, {
|
|
|
392
321
|
resolveOptions: () => resolveOptions,
|
|
393
322
|
setFieldValueAsync: () => setFieldValueAsync
|
|
394
323
|
});
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
function getFieldDef(path34) {
|
|
398
|
-
return CONFIG_REGISTRY.find((f) => f.path === path34);
|
|
324
|
+
function getFieldDef(path35) {
|
|
325
|
+
return CONFIG_REGISTRY.find((f) => f.path === path35);
|
|
399
326
|
}
|
|
400
327
|
function getSafeFields() {
|
|
401
328
|
return CONFIG_REGISTRY.filter((f) => f.scope === "safe");
|
|
402
329
|
}
|
|
403
|
-
function isHotReloadable(
|
|
404
|
-
const def = getFieldDef(
|
|
330
|
+
function isHotReloadable(path35) {
|
|
331
|
+
const def = getFieldDef(path35);
|
|
405
332
|
return def?.hotReload ?? false;
|
|
406
333
|
}
|
|
407
334
|
function resolveOptions(def, config) {
|
|
408
335
|
if (!def.options) return void 0;
|
|
409
336
|
return typeof def.options === "function" ? def.options(config) : def.options;
|
|
410
337
|
}
|
|
411
|
-
function getConfigValue(config,
|
|
412
|
-
const parts =
|
|
338
|
+
function getConfigValue(config, path35) {
|
|
339
|
+
const parts = path35.split(".");
|
|
413
340
|
let current = config;
|
|
414
341
|
for (const part of parts) {
|
|
415
342
|
if (current && typeof current === "object" && part in current) {
|
|
@@ -454,7 +381,6 @@ var CONFIG_REGISTRY, ConfigValidationError;
|
|
|
454
381
|
var init_config_registry = __esm({
|
|
455
382
|
"src/core/config/config-registry.ts"() {
|
|
456
383
|
"use strict";
|
|
457
|
-
init_instance_context();
|
|
458
384
|
CONFIG_REGISTRY = [
|
|
459
385
|
{
|
|
460
386
|
path: "defaultAgent",
|
|
@@ -462,17 +388,8 @@ var init_config_registry = __esm({
|
|
|
462
388
|
group: "agent",
|
|
463
389
|
type: "select",
|
|
464
390
|
options: (config) => {
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
try {
|
|
468
|
-
const agentsPath = path3.join(getGlobalRoot(), "agents.json");
|
|
469
|
-
if (fs3.existsSync(agentsPath)) {
|
|
470
|
-
const data = JSON.parse(fs3.readFileSync(agentsPath, "utf-8"));
|
|
471
|
-
return Object.keys(data.installed ?? {});
|
|
472
|
-
}
|
|
473
|
-
} catch {
|
|
474
|
-
}
|
|
475
|
-
return [];
|
|
391
|
+
const current = config.defaultAgent;
|
|
392
|
+
return current ? [current] : [];
|
|
476
393
|
},
|
|
477
394
|
scope: "safe",
|
|
478
395
|
hotReload: true
|
|
@@ -486,14 +403,6 @@ var init_config_registry = __esm({
|
|
|
486
403
|
scope: "safe",
|
|
487
404
|
hotReload: true
|
|
488
405
|
},
|
|
489
|
-
{
|
|
490
|
-
path: "workspace.baseDir",
|
|
491
|
-
displayName: "Workspace Directory",
|
|
492
|
-
group: "workspace",
|
|
493
|
-
type: "string",
|
|
494
|
-
scope: "safe",
|
|
495
|
-
hotReload: true
|
|
496
|
-
},
|
|
497
406
|
{
|
|
498
407
|
path: "sessionStore.ttlDays",
|
|
499
408
|
displayName: "Session Store TTL (days)",
|
|
@@ -522,14 +431,14 @@ var init_config_registry = __esm({
|
|
|
522
431
|
|
|
523
432
|
// src/core/config/config.ts
|
|
524
433
|
import { z } from "zod";
|
|
525
|
-
import * as
|
|
526
|
-
import * as
|
|
434
|
+
import * as fs3 from "fs";
|
|
435
|
+
import * as path3 from "path";
|
|
527
436
|
import * as os3 from "os";
|
|
528
437
|
import { randomBytes } from "crypto";
|
|
529
438
|
import { EventEmitter } from "events";
|
|
530
|
-
function
|
|
439
|
+
function expandHome2(p) {
|
|
531
440
|
if (p.startsWith("~")) {
|
|
532
|
-
return
|
|
441
|
+
return path3.join(os3.homedir(), p.slice(1));
|
|
533
442
|
}
|
|
534
443
|
return p;
|
|
535
444
|
}
|
|
@@ -548,10 +457,11 @@ var init_config = __esm({
|
|
|
548
457
|
sessionLogRetentionDays: z.number().default(30)
|
|
549
458
|
}).default({});
|
|
550
459
|
ConfigSchema = z.object({
|
|
460
|
+
id: z.string().optional(),
|
|
461
|
+
// instance UUID, written once at creation time
|
|
551
462
|
instanceName: z.string().optional(),
|
|
552
463
|
defaultAgent: z.string(),
|
|
553
464
|
workspace: z.object({
|
|
554
|
-
baseDir: z.string().default("~/openacp-workspace"),
|
|
555
465
|
allowExternalWorkspaces: z.boolean().default(true),
|
|
556
466
|
security: z.object({
|
|
557
467
|
allowedPaths: z.array(z.string()).default([]),
|
|
@@ -578,7 +488,6 @@ var init_config = __esm({
|
|
|
578
488
|
});
|
|
579
489
|
DEFAULT_CONFIG = {
|
|
580
490
|
defaultAgent: "claude",
|
|
581
|
-
workspace: { baseDir: "~/openacp-workspace" },
|
|
582
491
|
sessionStore: { ttlDays: 30 }
|
|
583
492
|
};
|
|
584
493
|
ConfigManager = class extends EventEmitter {
|
|
@@ -586,13 +495,13 @@ var init_config = __esm({
|
|
|
586
495
|
configPath;
|
|
587
496
|
constructor(configPath) {
|
|
588
497
|
super();
|
|
589
|
-
this.configPath = process.env.OPENACP_CONFIG_PATH || configPath ||
|
|
498
|
+
this.configPath = process.env.OPENACP_CONFIG_PATH || configPath || expandHome2("~/.openacp/config.json");
|
|
590
499
|
}
|
|
591
500
|
async load() {
|
|
592
|
-
const dir =
|
|
593
|
-
|
|
594
|
-
if (!
|
|
595
|
-
|
|
501
|
+
const dir = path3.dirname(this.configPath);
|
|
502
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
503
|
+
if (!fs3.existsSync(this.configPath)) {
|
|
504
|
+
fs3.writeFileSync(
|
|
596
505
|
this.configPath,
|
|
597
506
|
JSON.stringify(DEFAULT_CONFIG, null, 2)
|
|
598
507
|
);
|
|
@@ -602,10 +511,10 @@ var init_config = __esm({
|
|
|
602
511
|
);
|
|
603
512
|
process.exit(1);
|
|
604
513
|
}
|
|
605
|
-
const raw = JSON.parse(
|
|
606
|
-
const { changed: configUpdated } = applyMigrations(raw, void 0, { configDir:
|
|
514
|
+
const raw = JSON.parse(fs3.readFileSync(this.configPath, "utf-8"));
|
|
515
|
+
const { changed: configUpdated } = applyMigrations(raw, void 0, { configDir: path3.dirname(this.configPath) });
|
|
607
516
|
if (configUpdated) {
|
|
608
|
-
|
|
517
|
+
fs3.writeFileSync(this.configPath, JSON.stringify(raw, null, 2));
|
|
609
518
|
}
|
|
610
519
|
this.applyEnvOverrides(raw);
|
|
611
520
|
const result = ConfigSchema.safeParse(raw);
|
|
@@ -626,7 +535,7 @@ var init_config = __esm({
|
|
|
626
535
|
}
|
|
627
536
|
async save(updates, changePath) {
|
|
628
537
|
const oldConfig = this.config ? structuredClone(this.config) : void 0;
|
|
629
|
-
const raw = JSON.parse(
|
|
538
|
+
const raw = JSON.parse(fs3.readFileSync(this.configPath, "utf-8"));
|
|
630
539
|
this.deepMerge(raw, updates);
|
|
631
540
|
const result = ConfigSchema.safeParse(raw);
|
|
632
541
|
if (!result.success) {
|
|
@@ -634,8 +543,8 @@ var init_config = __esm({
|
|
|
634
543
|
return;
|
|
635
544
|
}
|
|
636
545
|
const tmpPath = this.configPath + `.tmp.${randomBytes(4).toString("hex")}`;
|
|
637
|
-
|
|
638
|
-
|
|
546
|
+
fs3.writeFileSync(tmpPath, JSON.stringify(raw, null, 2), "utf-8");
|
|
547
|
+
fs3.renameSync(tmpPath, this.configPath);
|
|
639
548
|
this.config = result.data;
|
|
640
549
|
if (changePath) {
|
|
641
550
|
const { getConfigValue: getConfigValue2 } = await Promise.resolve().then(() => (init_config_registry(), config_registry_exports));
|
|
@@ -665,54 +574,49 @@ var init_config = __esm({
|
|
|
665
574
|
await this.save(updates, dotPath);
|
|
666
575
|
}
|
|
667
576
|
resolveWorkspace(input2) {
|
|
577
|
+
const workspaceBase = path3.dirname(path3.dirname(this.configPath));
|
|
668
578
|
if (!input2) {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
if (
|
|
674
|
-
const
|
|
675
|
-
const base =
|
|
676
|
-
const isInternal =
|
|
579
|
+
fs3.mkdirSync(workspaceBase, { recursive: true });
|
|
580
|
+
return workspaceBase;
|
|
581
|
+
}
|
|
582
|
+
const expanded = input2.startsWith("~") ? expandHome2(input2) : input2;
|
|
583
|
+
if (path3.isAbsolute(expanded)) {
|
|
584
|
+
const resolved = path3.resolve(expanded);
|
|
585
|
+
const base = path3.resolve(workspaceBase);
|
|
586
|
+
const isInternal = resolved === base || resolved.startsWith(base + path3.sep);
|
|
677
587
|
if (!isInternal) {
|
|
678
588
|
if (!this.config.workspace.allowExternalWorkspaces) {
|
|
679
589
|
throw new Error(
|
|
680
|
-
`Workspace path "${input2}" is outside base directory "${
|
|
590
|
+
`Workspace path "${input2}" is outside base directory "${workspaceBase}". Set allowExternalWorkspaces: true to allow this.`
|
|
681
591
|
);
|
|
682
592
|
}
|
|
683
|
-
if (!
|
|
684
|
-
throw new Error(
|
|
685
|
-
`Workspace path "${input2}" does not exist.`
|
|
686
|
-
);
|
|
593
|
+
if (!fs3.existsSync(resolved)) {
|
|
594
|
+
throw new Error(`Workspace path "${resolved}" does not exist.`);
|
|
687
595
|
}
|
|
688
|
-
return
|
|
596
|
+
return resolved;
|
|
689
597
|
}
|
|
690
|
-
|
|
691
|
-
return
|
|
598
|
+
fs3.mkdirSync(resolved, { recursive: true });
|
|
599
|
+
return resolved;
|
|
692
600
|
}
|
|
693
|
-
|
|
694
|
-
if (name !== input2) {
|
|
601
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(input2)) {
|
|
695
602
|
throw new Error(
|
|
696
603
|
`Invalid workspace name: "${input2}". Only alphanumeric characters, hyphens, and underscores are allowed.`
|
|
697
604
|
);
|
|
698
605
|
}
|
|
699
|
-
const
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
);
|
|
703
|
-
fs4.mkdirSync(resolved, { recursive: true });
|
|
704
|
-
return resolved;
|
|
606
|
+
const namedPath = path3.join(workspaceBase, input2.toLowerCase());
|
|
607
|
+
fs3.mkdirSync(namedPath, { recursive: true });
|
|
608
|
+
return namedPath;
|
|
705
609
|
}
|
|
706
610
|
async exists() {
|
|
707
|
-
return
|
|
611
|
+
return fs3.existsSync(this.configPath);
|
|
708
612
|
}
|
|
709
613
|
getConfigPath() {
|
|
710
614
|
return this.configPath;
|
|
711
615
|
}
|
|
712
616
|
async writeNew(config) {
|
|
713
|
-
const dir =
|
|
714
|
-
|
|
715
|
-
|
|
617
|
+
const dir = path3.dirname(this.configPath);
|
|
618
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
619
|
+
fs3.writeFileSync(this.configPath, JSON.stringify(config, null, 2));
|
|
716
620
|
}
|
|
717
621
|
async applyEnvToPluginSettings(settingsManager) {
|
|
718
622
|
const pluginOverrides = [
|
|
@@ -795,9 +699,9 @@ var read_text_file_exports = {};
|
|
|
795
699
|
__export(read_text_file_exports, {
|
|
796
700
|
readTextFileWithRange: () => readTextFileWithRange
|
|
797
701
|
});
|
|
798
|
-
import
|
|
702
|
+
import fs5 from "fs";
|
|
799
703
|
async function readTextFileWithRange(filePath, options) {
|
|
800
|
-
const content = await
|
|
704
|
+
const content = await fs5.promises.readFile(filePath, "utf-8");
|
|
801
705
|
if (!options?.line && !options?.limit) return content;
|
|
802
706
|
const lines = content.split("\n");
|
|
803
707
|
const start = Math.max(0, (options.line ?? 1) - 1);
|
|
@@ -954,8 +858,8 @@ __export(agent_dependencies_exports, {
|
|
|
954
858
|
listAgentsWithIntegration: () => listAgentsWithIntegration
|
|
955
859
|
});
|
|
956
860
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
957
|
-
import * as
|
|
958
|
-
import * as
|
|
861
|
+
import * as fs10 from "fs";
|
|
862
|
+
import * as path9 from "path";
|
|
959
863
|
function getAgentSetup(registryId) {
|
|
960
864
|
return AGENT_SETUP[registryId];
|
|
961
865
|
}
|
|
@@ -979,9 +883,9 @@ function commandExists(cmd) {
|
|
|
979
883
|
}
|
|
980
884
|
let dir = process.cwd();
|
|
981
885
|
while (true) {
|
|
982
|
-
const binPath =
|
|
983
|
-
if (
|
|
984
|
-
const parent =
|
|
886
|
+
const binPath = path9.join(dir, "node_modules", ".bin", cmd);
|
|
887
|
+
if (fs10.existsSync(binPath)) return true;
|
|
888
|
+
const parent = path9.dirname(dir);
|
|
985
889
|
if (parent === dir) break;
|
|
986
890
|
dir = parent;
|
|
987
891
|
}
|
|
@@ -1254,9 +1158,9 @@ var init_agent_registry = __esm({
|
|
|
1254
1158
|
});
|
|
1255
1159
|
|
|
1256
1160
|
// src/core/agents/agent-installer.ts
|
|
1257
|
-
import * as
|
|
1258
|
-
import * as
|
|
1259
|
-
import * as
|
|
1161
|
+
import * as fs11 from "fs";
|
|
1162
|
+
import * as path10 from "path";
|
|
1163
|
+
import * as os4 from "os";
|
|
1260
1164
|
import crypto from "crypto";
|
|
1261
1165
|
function validateArchiveContents(entries, destDir) {
|
|
1262
1166
|
for (const entry of entries) {
|
|
@@ -1270,9 +1174,9 @@ function validateArchiveContents(entries, destDir) {
|
|
|
1270
1174
|
}
|
|
1271
1175
|
}
|
|
1272
1176
|
function validateUninstallPath(binaryPath, agentsDir) {
|
|
1273
|
-
const realPath =
|
|
1274
|
-
const realAgentsDir =
|
|
1275
|
-
if (!realPath.startsWith(realAgentsDir +
|
|
1177
|
+
const realPath = path10.resolve(binaryPath);
|
|
1178
|
+
const realAgentsDir = path10.resolve(agentsDir);
|
|
1179
|
+
if (!realPath.startsWith(realAgentsDir + path10.sep) && realPath !== realAgentsDir) {
|
|
1276
1180
|
throw new Error(`Refusing to delete path outside agents directory: ${realPath}`);
|
|
1277
1181
|
}
|
|
1278
1182
|
}
|
|
@@ -1326,7 +1230,7 @@ function buildInstalledAgent(registryId, name, version, dist, binaryPath) {
|
|
|
1326
1230
|
binaryPath: null
|
|
1327
1231
|
};
|
|
1328
1232
|
}
|
|
1329
|
-
const absCmd =
|
|
1233
|
+
const absCmd = path10.resolve(binaryPath, dist.cmd);
|
|
1330
1234
|
return {
|
|
1331
1235
|
registryId,
|
|
1332
1236
|
name,
|
|
@@ -1392,10 +1296,10 @@ Install it with: pip install uv`;
|
|
|
1392
1296
|
return { ok: true, agentKey, setupSteps: setupSteps.length > 0 ? setupSteps : void 0 };
|
|
1393
1297
|
}
|
|
1394
1298
|
async function downloadAndExtract(agentId, archiveUrl, progress, agentsDir) {
|
|
1395
|
-
const destDir =
|
|
1396
|
-
|
|
1299
|
+
const destDir = path10.join(agentsDir ?? DEFAULT_AGENTS_DIR, agentId);
|
|
1300
|
+
fs11.mkdirSync(destDir, { recursive: true });
|
|
1397
1301
|
await progress?.onStep("Downloading...");
|
|
1398
|
-
|
|
1302
|
+
log10.info({ agentId, url: archiveUrl }, "Downloading agent binary");
|
|
1399
1303
|
const response = await fetch(archiveUrl);
|
|
1400
1304
|
if (!response.ok) {
|
|
1401
1305
|
throw new Error(`Download failed: ${response.status} ${response.statusText}`);
|
|
@@ -1434,70 +1338,70 @@ async function readResponseWithProgress(response, contentLength, progress) {
|
|
|
1434
1338
|
return Buffer.concat(chunks);
|
|
1435
1339
|
}
|
|
1436
1340
|
function validateExtractedPaths(destDir) {
|
|
1437
|
-
const realDest =
|
|
1438
|
-
const entries =
|
|
1341
|
+
const realDest = fs11.realpathSync(destDir);
|
|
1342
|
+
const entries = fs11.readdirSync(destDir, { recursive: true, withFileTypes: true });
|
|
1439
1343
|
for (const entry of entries) {
|
|
1440
1344
|
const dirent = entry;
|
|
1441
1345
|
const parentPath = dirent.parentPath ?? dirent.path ?? destDir;
|
|
1442
|
-
const fullPath =
|
|
1346
|
+
const fullPath = path10.join(parentPath, entry.name);
|
|
1443
1347
|
let realPath;
|
|
1444
1348
|
try {
|
|
1445
|
-
realPath =
|
|
1349
|
+
realPath = fs11.realpathSync(fullPath);
|
|
1446
1350
|
} catch {
|
|
1447
|
-
const linkTarget =
|
|
1448
|
-
realPath =
|
|
1351
|
+
const linkTarget = fs11.readlinkSync(fullPath);
|
|
1352
|
+
realPath = path10.resolve(path10.dirname(fullPath), linkTarget);
|
|
1449
1353
|
}
|
|
1450
|
-
if (!realPath.startsWith(realDest +
|
|
1451
|
-
|
|
1354
|
+
if (!realPath.startsWith(realDest + path10.sep) && realPath !== realDest) {
|
|
1355
|
+
fs11.rmSync(destDir, { recursive: true, force: true });
|
|
1452
1356
|
throw new Error(`Archive contains unsafe path: ${entry.name}`);
|
|
1453
1357
|
}
|
|
1454
1358
|
}
|
|
1455
1359
|
}
|
|
1456
1360
|
async function extractTarGz(buffer, destDir) {
|
|
1457
1361
|
const { execFileSync: execFileSync8 } = await import("child_process");
|
|
1458
|
-
const tmpFile =
|
|
1459
|
-
|
|
1362
|
+
const tmpFile = path10.join(destDir, "_archive.tar.gz");
|
|
1363
|
+
fs11.writeFileSync(tmpFile, buffer);
|
|
1460
1364
|
try {
|
|
1461
1365
|
const listing = execFileSync8("tar", ["tf", tmpFile], { stdio: "pipe" }).toString().trim().split("\n").filter(Boolean);
|
|
1462
1366
|
validateArchiveContents(listing, destDir);
|
|
1463
1367
|
execFileSync8("tar", ["xzf", tmpFile, "-C", destDir], { stdio: "pipe" });
|
|
1464
1368
|
} finally {
|
|
1465
|
-
|
|
1369
|
+
fs11.unlinkSync(tmpFile);
|
|
1466
1370
|
}
|
|
1467
1371
|
validateExtractedPaths(destDir);
|
|
1468
1372
|
}
|
|
1469
1373
|
async function extractZip(buffer, destDir) {
|
|
1470
1374
|
const { execFileSync: execFileSync8 } = await import("child_process");
|
|
1471
|
-
const tmpFile =
|
|
1472
|
-
|
|
1375
|
+
const tmpFile = path10.join(destDir, "_archive.zip");
|
|
1376
|
+
fs11.writeFileSync(tmpFile, buffer);
|
|
1473
1377
|
try {
|
|
1474
1378
|
const listing = execFileSync8("unzip", ["-l", tmpFile], { stdio: "pipe" }).toString().trim().split("\n").filter(Boolean);
|
|
1475
1379
|
const entries = listing.slice(3, -2).map((line) => line.trim().split(/\s+/).slice(3).join(" ")).filter(Boolean);
|
|
1476
1380
|
validateArchiveContents(entries, destDir);
|
|
1477
1381
|
execFileSync8("unzip", ["-o", tmpFile, "-d", destDir], { stdio: "pipe" });
|
|
1478
1382
|
} finally {
|
|
1479
|
-
|
|
1383
|
+
fs11.unlinkSync(tmpFile);
|
|
1480
1384
|
}
|
|
1481
1385
|
validateExtractedPaths(destDir);
|
|
1482
1386
|
}
|
|
1483
1387
|
async function uninstallAgent(agentKey, store, agentsDir) {
|
|
1484
1388
|
const agent = store.getAgent(agentKey);
|
|
1485
1389
|
if (!agent) return;
|
|
1486
|
-
if (agent.binaryPath &&
|
|
1390
|
+
if (agent.binaryPath && fs11.existsSync(agent.binaryPath)) {
|
|
1487
1391
|
validateUninstallPath(agent.binaryPath, agentsDir ?? DEFAULT_AGENTS_DIR);
|
|
1488
|
-
|
|
1489
|
-
|
|
1392
|
+
fs11.rmSync(agent.binaryPath, { recursive: true, force: true });
|
|
1393
|
+
log10.info({ agentKey, binaryPath: agent.binaryPath }, "Deleted agent binary");
|
|
1490
1394
|
}
|
|
1491
1395
|
store.removeAgent(agentKey);
|
|
1492
1396
|
}
|
|
1493
|
-
var
|
|
1397
|
+
var log10, DEFAULT_AGENTS_DIR, MAX_DOWNLOAD_SIZE, validateTarContents, ARCH_MAP, PLATFORM_MAP;
|
|
1494
1398
|
var init_agent_installer = __esm({
|
|
1495
1399
|
"src/core/agents/agent-installer.ts"() {
|
|
1496
1400
|
"use strict";
|
|
1497
1401
|
init_log();
|
|
1498
1402
|
init_agent_dependencies();
|
|
1499
|
-
|
|
1500
|
-
DEFAULT_AGENTS_DIR =
|
|
1403
|
+
log10 = createChildLogger({ module: "agent-installer" });
|
|
1404
|
+
DEFAULT_AGENTS_DIR = path10.join(os4.homedir(), ".openacp", "agents");
|
|
1501
1405
|
MAX_DOWNLOAD_SIZE = 500 * 1024 * 1024;
|
|
1502
1406
|
validateTarContents = validateArchiveContents;
|
|
1503
1407
|
ARCH_MAP = {
|
|
@@ -1513,7 +1417,7 @@ var init_agent_installer = __esm({
|
|
|
1513
1417
|
});
|
|
1514
1418
|
|
|
1515
1419
|
// src/core/doctor/checks/config.ts
|
|
1516
|
-
import * as
|
|
1420
|
+
import * as fs15 from "fs";
|
|
1517
1421
|
var configCheck;
|
|
1518
1422
|
var init_config2 = __esm({
|
|
1519
1423
|
"src/core/doctor/checks/config.ts"() {
|
|
@@ -1525,14 +1429,14 @@ var init_config2 = __esm({
|
|
|
1525
1429
|
order: 1,
|
|
1526
1430
|
async run(ctx) {
|
|
1527
1431
|
const results = [];
|
|
1528
|
-
if (!
|
|
1432
|
+
if (!fs15.existsSync(ctx.configPath)) {
|
|
1529
1433
|
results.push({ status: "fail", message: "Config file not found" });
|
|
1530
1434
|
return results;
|
|
1531
1435
|
}
|
|
1532
1436
|
results.push({ status: "pass", message: "Config file exists" });
|
|
1533
1437
|
let raw;
|
|
1534
1438
|
try {
|
|
1535
|
-
raw = JSON.parse(
|
|
1439
|
+
raw = JSON.parse(fs15.readFileSync(ctx.configPath, "utf-8"));
|
|
1536
1440
|
} catch (err) {
|
|
1537
1441
|
results.push({
|
|
1538
1442
|
status: "fail",
|
|
@@ -1551,7 +1455,7 @@ var init_config2 = __esm({
|
|
|
1551
1455
|
fixRisk: "safe",
|
|
1552
1456
|
fix: async () => {
|
|
1553
1457
|
applyMigrations(raw);
|
|
1554
|
-
|
|
1458
|
+
fs15.writeFileSync(ctx.configPath, JSON.stringify(raw, null, 2));
|
|
1555
1459
|
return { success: true, message: "applied migrations" };
|
|
1556
1460
|
}
|
|
1557
1461
|
});
|
|
@@ -1575,8 +1479,8 @@ var init_config2 = __esm({
|
|
|
1575
1479
|
|
|
1576
1480
|
// src/core/doctor/checks/agents.ts
|
|
1577
1481
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
1578
|
-
import * as
|
|
1579
|
-
import * as
|
|
1482
|
+
import * as fs16 from "fs";
|
|
1483
|
+
import * as path15 from "path";
|
|
1580
1484
|
function commandExists2(cmd) {
|
|
1581
1485
|
try {
|
|
1582
1486
|
execFileSync3("which", [cmd], { stdio: "pipe" });
|
|
@@ -1585,9 +1489,9 @@ function commandExists2(cmd) {
|
|
|
1585
1489
|
}
|
|
1586
1490
|
let dir = process.cwd();
|
|
1587
1491
|
while (true) {
|
|
1588
|
-
const binPath =
|
|
1589
|
-
if (
|
|
1590
|
-
const parent =
|
|
1492
|
+
const binPath = path15.join(dir, "node_modules", ".bin", cmd);
|
|
1493
|
+
if (fs16.existsSync(binPath)) return true;
|
|
1494
|
+
const parent = path15.dirname(dir);
|
|
1591
1495
|
if (parent === dir) break;
|
|
1592
1496
|
dir = parent;
|
|
1593
1497
|
}
|
|
@@ -1609,9 +1513,9 @@ var init_agents = __esm({
|
|
|
1609
1513
|
const defaultAgent = ctx.config.defaultAgent;
|
|
1610
1514
|
let agents = {};
|
|
1611
1515
|
try {
|
|
1612
|
-
const agentsPath =
|
|
1613
|
-
if (
|
|
1614
|
-
const data = JSON.parse(
|
|
1516
|
+
const agentsPath = path15.join(ctx.dataDir, "agents.json");
|
|
1517
|
+
if (fs16.existsSync(agentsPath)) {
|
|
1518
|
+
const data = JSON.parse(fs16.readFileSync(agentsPath, "utf-8"));
|
|
1615
1519
|
agents = data.installed ?? {};
|
|
1616
1520
|
}
|
|
1617
1521
|
} catch {
|
|
@@ -1649,8 +1553,8 @@ var settings_manager_exports = {};
|
|
|
1649
1553
|
__export(settings_manager_exports, {
|
|
1650
1554
|
SettingsManager: () => SettingsManager
|
|
1651
1555
|
});
|
|
1652
|
-
import
|
|
1653
|
-
import
|
|
1556
|
+
import fs17 from "fs";
|
|
1557
|
+
import path16 from "path";
|
|
1654
1558
|
var SettingsManager, SettingsAPIImpl;
|
|
1655
1559
|
var init_settings_manager = __esm({
|
|
1656
1560
|
"src/core/plugin/settings-manager.ts"() {
|
|
@@ -1669,7 +1573,7 @@ var init_settings_manager = __esm({
|
|
|
1669
1573
|
async loadSettings(pluginName) {
|
|
1670
1574
|
const settingsPath = this.getSettingsPath(pluginName);
|
|
1671
1575
|
try {
|
|
1672
|
-
const content =
|
|
1576
|
+
const content = fs17.readFileSync(settingsPath, "utf-8");
|
|
1673
1577
|
return JSON.parse(content);
|
|
1674
1578
|
} catch {
|
|
1675
1579
|
return {};
|
|
@@ -1687,7 +1591,7 @@ var init_settings_manager = __esm({
|
|
|
1687
1591
|
};
|
|
1688
1592
|
}
|
|
1689
1593
|
getSettingsPath(pluginName) {
|
|
1690
|
-
return
|
|
1594
|
+
return path16.join(this.basePath, pluginName, "settings.json");
|
|
1691
1595
|
}
|
|
1692
1596
|
async getPluginSettings(pluginName) {
|
|
1693
1597
|
return this.loadSettings(pluginName);
|
|
@@ -1706,7 +1610,7 @@ var init_settings_manager = __esm({
|
|
|
1706
1610
|
readFile() {
|
|
1707
1611
|
if (this.cache !== null) return this.cache;
|
|
1708
1612
|
try {
|
|
1709
|
-
const content =
|
|
1613
|
+
const content = fs17.readFileSync(this.settingsPath, "utf-8");
|
|
1710
1614
|
this.cache = JSON.parse(content);
|
|
1711
1615
|
return this.cache;
|
|
1712
1616
|
} catch {
|
|
@@ -1715,9 +1619,9 @@ var init_settings_manager = __esm({
|
|
|
1715
1619
|
}
|
|
1716
1620
|
}
|
|
1717
1621
|
writeFile(data) {
|
|
1718
|
-
const dir =
|
|
1719
|
-
|
|
1720
|
-
|
|
1622
|
+
const dir = path16.dirname(this.settingsPath);
|
|
1623
|
+
fs17.mkdirSync(dir, { recursive: true });
|
|
1624
|
+
fs17.writeFileSync(this.settingsPath, JSON.stringify(data, null, 2));
|
|
1721
1625
|
this.cache = data;
|
|
1722
1626
|
}
|
|
1723
1627
|
async get(key) {
|
|
@@ -1752,7 +1656,7 @@ var init_settings_manager = __esm({
|
|
|
1752
1656
|
});
|
|
1753
1657
|
|
|
1754
1658
|
// src/core/doctor/checks/telegram.ts
|
|
1755
|
-
import * as
|
|
1659
|
+
import * as path17 from "path";
|
|
1756
1660
|
var BOT_TOKEN_REGEX, telegramCheck;
|
|
1757
1661
|
var init_telegram = __esm({
|
|
1758
1662
|
"src/core/doctor/checks/telegram.ts"() {
|
|
@@ -1768,7 +1672,7 @@ var init_telegram = __esm({
|
|
|
1768
1672
|
return results;
|
|
1769
1673
|
}
|
|
1770
1674
|
const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
|
|
1771
|
-
const sm = new SettingsManager2(
|
|
1675
|
+
const sm = new SettingsManager2(path17.join(ctx.pluginsDir, "data"));
|
|
1772
1676
|
const ps = await sm.loadSettings("@openacp/telegram");
|
|
1773
1677
|
const botToken = ps.botToken;
|
|
1774
1678
|
const chatId = ps.chatId;
|
|
@@ -1851,7 +1755,7 @@ var init_telegram = __esm({
|
|
|
1851
1755
|
});
|
|
1852
1756
|
|
|
1853
1757
|
// src/core/doctor/checks/storage.ts
|
|
1854
|
-
import * as
|
|
1758
|
+
import * as fs18 from "fs";
|
|
1855
1759
|
var storageCheck;
|
|
1856
1760
|
var init_storage = __esm({
|
|
1857
1761
|
"src/core/doctor/checks/storage.ts"() {
|
|
@@ -1861,28 +1765,28 @@ var init_storage = __esm({
|
|
|
1861
1765
|
order: 4,
|
|
1862
1766
|
async run(ctx) {
|
|
1863
1767
|
const results = [];
|
|
1864
|
-
if (!
|
|
1768
|
+
if (!fs18.existsSync(ctx.dataDir)) {
|
|
1865
1769
|
results.push({
|
|
1866
1770
|
status: "fail",
|
|
1867
1771
|
message: "Data directory ~/.openacp does not exist",
|
|
1868
1772
|
fixable: true,
|
|
1869
1773
|
fixRisk: "safe",
|
|
1870
1774
|
fix: async () => {
|
|
1871
|
-
|
|
1775
|
+
fs18.mkdirSync(ctx.dataDir, { recursive: true });
|
|
1872
1776
|
return { success: true, message: "created directory" };
|
|
1873
1777
|
}
|
|
1874
1778
|
});
|
|
1875
1779
|
} else {
|
|
1876
1780
|
try {
|
|
1877
|
-
|
|
1781
|
+
fs18.accessSync(ctx.dataDir, fs18.constants.W_OK);
|
|
1878
1782
|
results.push({ status: "pass", message: "Data directory exists and writable" });
|
|
1879
1783
|
} catch {
|
|
1880
1784
|
results.push({ status: "fail", message: "Data directory not writable" });
|
|
1881
1785
|
}
|
|
1882
1786
|
}
|
|
1883
|
-
if (
|
|
1787
|
+
if (fs18.existsSync(ctx.sessionsPath)) {
|
|
1884
1788
|
try {
|
|
1885
|
-
const content =
|
|
1789
|
+
const content = fs18.readFileSync(ctx.sessionsPath, "utf-8");
|
|
1886
1790
|
const data = JSON.parse(content);
|
|
1887
1791
|
if (typeof data === "object" && data !== null && "sessions" in data) {
|
|
1888
1792
|
results.push({ status: "pass", message: "Sessions file valid" });
|
|
@@ -1893,7 +1797,7 @@ var init_storage = __esm({
|
|
|
1893
1797
|
fixable: true,
|
|
1894
1798
|
fixRisk: "risky",
|
|
1895
1799
|
fix: async () => {
|
|
1896
|
-
|
|
1800
|
+
fs18.writeFileSync(ctx.sessionsPath, JSON.stringify({ version: 1, sessions: {} }, null, 2));
|
|
1897
1801
|
return { success: true, message: "reset sessions file" };
|
|
1898
1802
|
}
|
|
1899
1803
|
});
|
|
@@ -1905,7 +1809,7 @@ var init_storage = __esm({
|
|
|
1905
1809
|
fixable: true,
|
|
1906
1810
|
fixRisk: "risky",
|
|
1907
1811
|
fix: async () => {
|
|
1908
|
-
|
|
1812
|
+
fs18.writeFileSync(ctx.sessionsPath, JSON.stringify({ version: 1, sessions: {} }, null, 2));
|
|
1909
1813
|
return { success: true, message: "reset sessions file" };
|
|
1910
1814
|
}
|
|
1911
1815
|
});
|
|
@@ -1913,20 +1817,20 @@ var init_storage = __esm({
|
|
|
1913
1817
|
} else {
|
|
1914
1818
|
results.push({ status: "pass", message: "Sessions file not present yet (created on first session)" });
|
|
1915
1819
|
}
|
|
1916
|
-
if (!
|
|
1820
|
+
if (!fs18.existsSync(ctx.logsDir)) {
|
|
1917
1821
|
results.push({
|
|
1918
1822
|
status: "warn",
|
|
1919
1823
|
message: "Log directory does not exist",
|
|
1920
1824
|
fixable: true,
|
|
1921
1825
|
fixRisk: "safe",
|
|
1922
1826
|
fix: async () => {
|
|
1923
|
-
|
|
1827
|
+
fs18.mkdirSync(ctx.logsDir, { recursive: true });
|
|
1924
1828
|
return { success: true, message: "created log directory" };
|
|
1925
1829
|
}
|
|
1926
1830
|
});
|
|
1927
1831
|
} else {
|
|
1928
1832
|
try {
|
|
1929
|
-
|
|
1833
|
+
fs18.accessSync(ctx.logsDir, fs18.constants.W_OK);
|
|
1930
1834
|
results.push({ status: "pass", message: "Log directory exists and writable" });
|
|
1931
1835
|
} catch {
|
|
1932
1836
|
results.push({ status: "fail", message: "Log directory not writable" });
|
|
@@ -1939,39 +1843,35 @@ var init_storage = __esm({
|
|
|
1939
1843
|
});
|
|
1940
1844
|
|
|
1941
1845
|
// src/core/doctor/checks/workspace.ts
|
|
1942
|
-
import * as
|
|
1846
|
+
import * as fs19 from "fs";
|
|
1847
|
+
import * as path18 from "path";
|
|
1943
1848
|
var workspaceCheck;
|
|
1944
1849
|
var init_workspace = __esm({
|
|
1945
1850
|
"src/core/doctor/checks/workspace.ts"() {
|
|
1946
1851
|
"use strict";
|
|
1947
|
-
init_config();
|
|
1948
1852
|
workspaceCheck = {
|
|
1949
1853
|
name: "Workspace",
|
|
1950
1854
|
order: 5,
|
|
1951
1855
|
async run(ctx) {
|
|
1952
1856
|
const results = [];
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
return results;
|
|
1956
|
-
}
|
|
1957
|
-
const baseDir = expandHome3(ctx.config.workspace.baseDir);
|
|
1958
|
-
if (!fs20.existsSync(baseDir)) {
|
|
1857
|
+
const workspace = path18.dirname(ctx.dataDir);
|
|
1858
|
+
if (!fs19.existsSync(workspace)) {
|
|
1959
1859
|
results.push({
|
|
1960
1860
|
status: "warn",
|
|
1961
|
-
message: `Workspace directory does not exist: ${
|
|
1861
|
+
message: `Workspace directory does not exist: ${workspace}`,
|
|
1962
1862
|
fixable: true,
|
|
1963
1863
|
fixRisk: "safe",
|
|
1964
1864
|
fix: async () => {
|
|
1965
|
-
|
|
1865
|
+
fs19.mkdirSync(workspace, { recursive: true });
|
|
1966
1866
|
return { success: true, message: "created directory" };
|
|
1967
1867
|
}
|
|
1968
1868
|
});
|
|
1969
1869
|
} else {
|
|
1970
1870
|
try {
|
|
1971
|
-
|
|
1972
|
-
results.push({ status: "pass", message: `Workspace directory exists: ${
|
|
1871
|
+
fs19.accessSync(workspace, fs19.constants.W_OK);
|
|
1872
|
+
results.push({ status: "pass", message: `Workspace directory exists: ${workspace}` });
|
|
1973
1873
|
} catch {
|
|
1974
|
-
results.push({ status: "fail", message: `Workspace directory not writable: ${
|
|
1874
|
+
results.push({ status: "fail", message: `Workspace directory not writable: ${workspace}` });
|
|
1975
1875
|
}
|
|
1976
1876
|
}
|
|
1977
1877
|
return results;
|
|
@@ -1981,8 +1881,8 @@ var init_workspace = __esm({
|
|
|
1981
1881
|
});
|
|
1982
1882
|
|
|
1983
1883
|
// src/core/doctor/checks/plugins.ts
|
|
1984
|
-
import * as
|
|
1985
|
-
import * as
|
|
1884
|
+
import * as fs20 from "fs";
|
|
1885
|
+
import * as path19 from "path";
|
|
1986
1886
|
var pluginsCheck;
|
|
1987
1887
|
var init_plugins = __esm({
|
|
1988
1888
|
"src/core/doctor/checks/plugins.ts"() {
|
|
@@ -1992,16 +1892,16 @@ var init_plugins = __esm({
|
|
|
1992
1892
|
order: 6,
|
|
1993
1893
|
async run(ctx) {
|
|
1994
1894
|
const results = [];
|
|
1995
|
-
if (!
|
|
1895
|
+
if (!fs20.existsSync(ctx.pluginsDir)) {
|
|
1996
1896
|
results.push({
|
|
1997
1897
|
status: "warn",
|
|
1998
1898
|
message: "Plugins directory does not exist",
|
|
1999
1899
|
fixable: true,
|
|
2000
1900
|
fixRisk: "safe",
|
|
2001
1901
|
fix: async () => {
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
1902
|
+
fs20.mkdirSync(ctx.pluginsDir, { recursive: true });
|
|
1903
|
+
fs20.writeFileSync(
|
|
1904
|
+
path19.join(ctx.pluginsDir, "package.json"),
|
|
2005
1905
|
JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
|
|
2006
1906
|
);
|
|
2007
1907
|
return { success: true, message: "initialized plugins directory" };
|
|
@@ -2010,15 +1910,15 @@ var init_plugins = __esm({
|
|
|
2010
1910
|
return results;
|
|
2011
1911
|
}
|
|
2012
1912
|
results.push({ status: "pass", message: "Plugins directory exists" });
|
|
2013
|
-
const pkgPath =
|
|
2014
|
-
if (!
|
|
1913
|
+
const pkgPath = path19.join(ctx.pluginsDir, "package.json");
|
|
1914
|
+
if (!fs20.existsSync(pkgPath)) {
|
|
2015
1915
|
results.push({
|
|
2016
1916
|
status: "warn",
|
|
2017
1917
|
message: "Plugins package.json missing",
|
|
2018
1918
|
fixable: true,
|
|
2019
1919
|
fixRisk: "safe",
|
|
2020
1920
|
fix: async () => {
|
|
2021
|
-
|
|
1921
|
+
fs20.writeFileSync(
|
|
2022
1922
|
pkgPath,
|
|
2023
1923
|
JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
|
|
2024
1924
|
);
|
|
@@ -2028,7 +1928,7 @@ var init_plugins = __esm({
|
|
|
2028
1928
|
return results;
|
|
2029
1929
|
}
|
|
2030
1930
|
try {
|
|
2031
|
-
const pkg = JSON.parse(
|
|
1931
|
+
const pkg = JSON.parse(fs20.readFileSync(pkgPath, "utf-8"));
|
|
2032
1932
|
const deps = pkg.dependencies || {};
|
|
2033
1933
|
const count = Object.keys(deps).length;
|
|
2034
1934
|
results.push({ status: "pass", message: `Plugins package.json valid (${count} plugins)` });
|
|
@@ -2039,7 +1939,7 @@ var init_plugins = __esm({
|
|
|
2039
1939
|
fixable: true,
|
|
2040
1940
|
fixRisk: "risky",
|
|
2041
1941
|
fix: async () => {
|
|
2042
|
-
|
|
1942
|
+
fs20.writeFileSync(
|
|
2043
1943
|
pkgPath,
|
|
2044
1944
|
JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
|
|
2045
1945
|
);
|
|
@@ -2054,7 +1954,7 @@ var init_plugins = __esm({
|
|
|
2054
1954
|
});
|
|
2055
1955
|
|
|
2056
1956
|
// src/core/doctor/checks/daemon.ts
|
|
2057
|
-
import * as
|
|
1957
|
+
import * as fs21 from "fs";
|
|
2058
1958
|
import * as net from "net";
|
|
2059
1959
|
function isProcessAlive(pid) {
|
|
2060
1960
|
try {
|
|
@@ -2084,8 +1984,8 @@ var init_daemon = __esm({
|
|
|
2084
1984
|
order: 7,
|
|
2085
1985
|
async run(ctx) {
|
|
2086
1986
|
const results = [];
|
|
2087
|
-
if (
|
|
2088
|
-
const content =
|
|
1987
|
+
if (fs21.existsSync(ctx.pidPath)) {
|
|
1988
|
+
const content = fs21.readFileSync(ctx.pidPath, "utf-8").trim();
|
|
2089
1989
|
const pid = parseInt(content, 10);
|
|
2090
1990
|
if (isNaN(pid)) {
|
|
2091
1991
|
results.push({
|
|
@@ -2094,7 +1994,7 @@ var init_daemon = __esm({
|
|
|
2094
1994
|
fixable: true,
|
|
2095
1995
|
fixRisk: "safe",
|
|
2096
1996
|
fix: async () => {
|
|
2097
|
-
|
|
1997
|
+
fs21.unlinkSync(ctx.pidPath);
|
|
2098
1998
|
return { success: true, message: "removed invalid PID file" };
|
|
2099
1999
|
}
|
|
2100
2000
|
});
|
|
@@ -2105,7 +2005,7 @@ var init_daemon = __esm({
|
|
|
2105
2005
|
fixable: true,
|
|
2106
2006
|
fixRisk: "safe",
|
|
2107
2007
|
fix: async () => {
|
|
2108
|
-
|
|
2008
|
+
fs21.unlinkSync(ctx.pidPath);
|
|
2109
2009
|
return { success: true, message: "removed stale PID file" };
|
|
2110
2010
|
}
|
|
2111
2011
|
});
|
|
@@ -2113,8 +2013,8 @@ var init_daemon = __esm({
|
|
|
2113
2013
|
results.push({ status: "pass", message: `Daemon running (PID ${pid})` });
|
|
2114
2014
|
}
|
|
2115
2015
|
}
|
|
2116
|
-
if (
|
|
2117
|
-
const content =
|
|
2016
|
+
if (fs21.existsSync(ctx.portFilePath)) {
|
|
2017
|
+
const content = fs21.readFileSync(ctx.portFilePath, "utf-8").trim();
|
|
2118
2018
|
const port = parseInt(content, 10);
|
|
2119
2019
|
if (isNaN(port)) {
|
|
2120
2020
|
results.push({
|
|
@@ -2123,7 +2023,7 @@ var init_daemon = __esm({
|
|
|
2123
2023
|
fixable: true,
|
|
2124
2024
|
fixRisk: "safe",
|
|
2125
2025
|
fix: async () => {
|
|
2126
|
-
|
|
2026
|
+
fs21.unlinkSync(ctx.portFilePath);
|
|
2127
2027
|
return { success: true, message: "removed invalid port file" };
|
|
2128
2028
|
}
|
|
2129
2029
|
});
|
|
@@ -2135,8 +2035,8 @@ var init_daemon = __esm({
|
|
|
2135
2035
|
const apiPort = 21420;
|
|
2136
2036
|
const inUse = await checkPortInUse(apiPort);
|
|
2137
2037
|
if (inUse) {
|
|
2138
|
-
if (
|
|
2139
|
-
const pid = parseInt(
|
|
2038
|
+
if (fs21.existsSync(ctx.pidPath)) {
|
|
2039
|
+
const pid = parseInt(fs21.readFileSync(ctx.pidPath, "utf-8").trim(), 10);
|
|
2140
2040
|
if (!isNaN(pid) && isProcessAlive(pid)) {
|
|
2141
2041
|
results.push({ status: "pass", message: `API port ${apiPort} in use by OpenACP daemon` });
|
|
2142
2042
|
} else {
|
|
@@ -2156,17 +2056,17 @@ var init_daemon = __esm({
|
|
|
2156
2056
|
});
|
|
2157
2057
|
|
|
2158
2058
|
// src/core/utils/install-binary.ts
|
|
2159
|
-
import
|
|
2160
|
-
import
|
|
2059
|
+
import fs22 from "fs";
|
|
2060
|
+
import path20 from "path";
|
|
2161
2061
|
import https from "https";
|
|
2162
|
-
import
|
|
2062
|
+
import os5 from "os";
|
|
2163
2063
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
2164
2064
|
function downloadFile(url, dest, maxRedirects = 10) {
|
|
2165
2065
|
return new Promise((resolve7, reject) => {
|
|
2166
|
-
const file =
|
|
2066
|
+
const file = fs22.createWriteStream(dest);
|
|
2167
2067
|
const cleanup = () => {
|
|
2168
2068
|
try {
|
|
2169
|
-
if (
|
|
2069
|
+
if (fs22.existsSync(dest)) fs22.unlinkSync(dest);
|
|
2170
2070
|
} catch {
|
|
2171
2071
|
}
|
|
2172
2072
|
};
|
|
@@ -2221,8 +2121,8 @@ function downloadFile(url, dest, maxRedirects = 10) {
|
|
|
2221
2121
|
});
|
|
2222
2122
|
}
|
|
2223
2123
|
function getDownloadUrl(spec) {
|
|
2224
|
-
const platform2 =
|
|
2225
|
-
const arch =
|
|
2124
|
+
const platform2 = os5.platform();
|
|
2125
|
+
const arch = os5.arch();
|
|
2226
2126
|
const mapping = spec.platforms[platform2];
|
|
2227
2127
|
if (!mapping) throw new Error(`${spec.name}: unsupported platform ${platform2}`);
|
|
2228
2128
|
const binary = mapping[arch];
|
|
@@ -2232,36 +2132,36 @@ function getDownloadUrl(spec) {
|
|
|
2232
2132
|
async function ensureBinary(spec, binDir) {
|
|
2233
2133
|
const resolvedBinDir = binDir ?? DEFAULT_BIN_DIR;
|
|
2234
2134
|
const binName = IS_WINDOWS ? `${spec.name}.exe` : spec.name;
|
|
2235
|
-
const binPath =
|
|
2135
|
+
const binPath = path20.join(resolvedBinDir, binName);
|
|
2236
2136
|
if (commandExists(spec.name)) {
|
|
2237
2137
|
log17.debug({ name: spec.name }, "Found in PATH");
|
|
2238
2138
|
return spec.name;
|
|
2239
2139
|
}
|
|
2240
|
-
if (
|
|
2241
|
-
if (!IS_WINDOWS)
|
|
2140
|
+
if (fs22.existsSync(binPath)) {
|
|
2141
|
+
if (!IS_WINDOWS) fs22.chmodSync(binPath, "755");
|
|
2242
2142
|
log17.debug({ name: spec.name, path: binPath }, "Found in ~/.openacp/bin");
|
|
2243
2143
|
return binPath;
|
|
2244
2144
|
}
|
|
2245
2145
|
log17.info({ name: spec.name }, "Not found, downloading from GitHub...");
|
|
2246
|
-
|
|
2146
|
+
fs22.mkdirSync(resolvedBinDir, { recursive: true });
|
|
2247
2147
|
const url = getDownloadUrl(spec);
|
|
2248
2148
|
const isArchive = spec.isArchive?.(url) ?? false;
|
|
2249
|
-
const downloadDest = isArchive ?
|
|
2149
|
+
const downloadDest = isArchive ? path20.join(resolvedBinDir, `${spec.name}.tgz`) : binPath;
|
|
2250
2150
|
await downloadFile(url, downloadDest);
|
|
2251
2151
|
if (isArchive) {
|
|
2252
2152
|
const listing = execFileSync4("tar", ["-tf", downloadDest], { stdio: "pipe" }).toString().trim().split("\n").filter(Boolean);
|
|
2253
2153
|
validateTarContents(listing, resolvedBinDir);
|
|
2254
2154
|
execFileSync4("tar", ["-xzf", downloadDest, "-C", resolvedBinDir], { stdio: "pipe" });
|
|
2255
2155
|
try {
|
|
2256
|
-
|
|
2156
|
+
fs22.unlinkSync(downloadDest);
|
|
2257
2157
|
} catch {
|
|
2258
2158
|
}
|
|
2259
2159
|
}
|
|
2260
|
-
if (!
|
|
2160
|
+
if (!fs22.existsSync(binPath)) {
|
|
2261
2161
|
throw new Error(`${spec.name}: binary not found at ${binPath} after download/extraction. The archive structure may have changed.`);
|
|
2262
2162
|
}
|
|
2263
2163
|
if (!IS_WINDOWS) {
|
|
2264
|
-
|
|
2164
|
+
fs22.chmodSync(binPath, "755");
|
|
2265
2165
|
}
|
|
2266
2166
|
log17.info({ name: spec.name, path: binPath }, "Installed successfully");
|
|
2267
2167
|
return binPath;
|
|
@@ -2274,8 +2174,8 @@ var init_install_binary = __esm({
|
|
|
2274
2174
|
init_agent_dependencies();
|
|
2275
2175
|
init_agent_installer();
|
|
2276
2176
|
log17 = createChildLogger({ module: "binary-installer" });
|
|
2277
|
-
DEFAULT_BIN_DIR =
|
|
2278
|
-
IS_WINDOWS =
|
|
2177
|
+
DEFAULT_BIN_DIR = path20.join(os5.homedir(), ".openacp", "bin");
|
|
2178
|
+
IS_WINDOWS = os5.platform() === "win32";
|
|
2279
2179
|
}
|
|
2280
2180
|
});
|
|
2281
2181
|
|
|
@@ -2315,9 +2215,9 @@ var init_install_cloudflared = __esm({
|
|
|
2315
2215
|
});
|
|
2316
2216
|
|
|
2317
2217
|
// src/core/doctor/checks/tunnel.ts
|
|
2318
|
-
import * as
|
|
2319
|
-
import * as
|
|
2320
|
-
import * as
|
|
2218
|
+
import * as fs23 from "fs";
|
|
2219
|
+
import * as path21 from "path";
|
|
2220
|
+
import * as os6 from "os";
|
|
2321
2221
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
2322
2222
|
var tunnelCheck;
|
|
2323
2223
|
var init_tunnel = __esm({
|
|
@@ -2333,7 +2233,7 @@ var init_tunnel = __esm({
|
|
|
2333
2233
|
return results;
|
|
2334
2234
|
}
|
|
2335
2235
|
const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
|
|
2336
|
-
const sm = new SettingsManager2(
|
|
2236
|
+
const sm = new SettingsManager2(path21.join(ctx.pluginsDir, "data"));
|
|
2337
2237
|
const tunnelSettings = await sm.loadSettings("@openacp/tunnel");
|
|
2338
2238
|
const tunnelEnabled = tunnelSettings.enabled ?? false;
|
|
2339
2239
|
const provider = tunnelSettings.provider ?? "cloudflare";
|
|
@@ -2344,10 +2244,10 @@ var init_tunnel = __esm({
|
|
|
2344
2244
|
}
|
|
2345
2245
|
results.push({ status: "pass", message: `Tunnel provider: ${provider}` });
|
|
2346
2246
|
if (provider === "cloudflare") {
|
|
2347
|
-
const binName =
|
|
2348
|
-
const binPath =
|
|
2247
|
+
const binName = os6.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
|
|
2248
|
+
const binPath = path21.join(ctx.dataDir, "bin", binName);
|
|
2349
2249
|
let found = false;
|
|
2350
|
-
if (
|
|
2250
|
+
if (fs23.existsSync(binPath)) {
|
|
2351
2251
|
found = true;
|
|
2352
2252
|
} else {
|
|
2353
2253
|
try {
|
|
@@ -2388,14 +2288,13 @@ var init_tunnel = __esm({
|
|
|
2388
2288
|
});
|
|
2389
2289
|
|
|
2390
2290
|
// src/core/doctor/index.ts
|
|
2391
|
-
import * as
|
|
2392
|
-
import * as
|
|
2291
|
+
import * as fs24 from "fs";
|
|
2292
|
+
import * as path22 from "path";
|
|
2393
2293
|
var ALL_CHECKS, CHECK_TIMEOUT_MS, DoctorEngine;
|
|
2394
2294
|
var init_doctor = __esm({
|
|
2395
2295
|
"src/core/doctor/index.ts"() {
|
|
2396
2296
|
"use strict";
|
|
2397
2297
|
init_config();
|
|
2398
|
-
init_instance_context();
|
|
2399
2298
|
init_config2();
|
|
2400
2299
|
init_agents();
|
|
2401
2300
|
init_telegram();
|
|
@@ -2420,7 +2319,7 @@ var init_doctor = __esm({
|
|
|
2420
2319
|
dataDir;
|
|
2421
2320
|
constructor(options) {
|
|
2422
2321
|
this.dryRun = options?.dryRun ?? false;
|
|
2423
|
-
this.dataDir = options
|
|
2322
|
+
this.dataDir = options.dataDir;
|
|
2424
2323
|
}
|
|
2425
2324
|
async runAll() {
|
|
2426
2325
|
const ctx = await this.buildContext();
|
|
@@ -2471,27 +2370,27 @@ var init_doctor = __esm({
|
|
|
2471
2370
|
}
|
|
2472
2371
|
async buildContext() {
|
|
2473
2372
|
const dataDir = this.dataDir;
|
|
2474
|
-
const configPath = process.env.OPENACP_CONFIG_PATH ||
|
|
2373
|
+
const configPath = process.env.OPENACP_CONFIG_PATH || path22.join(dataDir, "config.json");
|
|
2475
2374
|
let config = null;
|
|
2476
2375
|
let rawConfig = null;
|
|
2477
2376
|
try {
|
|
2478
|
-
const content =
|
|
2377
|
+
const content = fs24.readFileSync(configPath, "utf-8");
|
|
2479
2378
|
rawConfig = JSON.parse(content);
|
|
2480
2379
|
const cm = new ConfigManager(configPath);
|
|
2481
2380
|
await cm.load();
|
|
2482
2381
|
config = cm.get();
|
|
2483
2382
|
} catch {
|
|
2484
2383
|
}
|
|
2485
|
-
const logsDir = config ?
|
|
2384
|
+
const logsDir = config ? expandHome2(config.logging.logDir) : path22.join(dataDir, "logs");
|
|
2486
2385
|
return {
|
|
2487
2386
|
config,
|
|
2488
2387
|
rawConfig,
|
|
2489
2388
|
configPath,
|
|
2490
2389
|
dataDir,
|
|
2491
|
-
sessionsPath:
|
|
2492
|
-
pidPath:
|
|
2493
|
-
portFilePath:
|
|
2494
|
-
pluginsDir:
|
|
2390
|
+
sessionsPath: path22.join(dataDir, "sessions.json"),
|
|
2391
|
+
pidPath: path22.join(dataDir, "openacp.pid"),
|
|
2392
|
+
portFilePath: path22.join(dataDir, "api.port"),
|
|
2393
|
+
pluginsDir: path22.join(dataDir, "plugins"),
|
|
2495
2394
|
logsDir
|
|
2496
2395
|
};
|
|
2497
2396
|
}
|
|
@@ -2499,6 +2398,123 @@ var init_doctor = __esm({
|
|
|
2499
2398
|
}
|
|
2500
2399
|
});
|
|
2501
2400
|
|
|
2401
|
+
// src/core/instance/instance-context.ts
|
|
2402
|
+
import path24 from "path";
|
|
2403
|
+
import fs26 from "fs";
|
|
2404
|
+
import os8 from "os";
|
|
2405
|
+
function getGlobalRoot() {
|
|
2406
|
+
return path24.join(os8.homedir(), ".openacp");
|
|
2407
|
+
}
|
|
2408
|
+
var init_instance_context = __esm({
|
|
2409
|
+
"src/core/instance/instance-context.ts"() {
|
|
2410
|
+
"use strict";
|
|
2411
|
+
}
|
|
2412
|
+
});
|
|
2413
|
+
|
|
2414
|
+
// src/core/instance/instance-registry.ts
|
|
2415
|
+
import fs27 from "fs";
|
|
2416
|
+
import path25 from "path";
|
|
2417
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2418
|
+
function readIdFromConfig(instanceRoot) {
|
|
2419
|
+
try {
|
|
2420
|
+
const raw = JSON.parse(fs27.readFileSync(path25.join(instanceRoot, "config.json"), "utf-8"));
|
|
2421
|
+
return typeof raw.id === "string" && raw.id ? raw.id : null;
|
|
2422
|
+
} catch {
|
|
2423
|
+
return null;
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
var InstanceRegistry;
|
|
2427
|
+
var init_instance_registry = __esm({
|
|
2428
|
+
"src/core/instance/instance-registry.ts"() {
|
|
2429
|
+
"use strict";
|
|
2430
|
+
InstanceRegistry = class {
|
|
2431
|
+
constructor(registryPath) {
|
|
2432
|
+
this.registryPath = registryPath;
|
|
2433
|
+
}
|
|
2434
|
+
data = { version: 1, instances: {} };
|
|
2435
|
+
load() {
|
|
2436
|
+
try {
|
|
2437
|
+
const raw = fs27.readFileSync(this.registryPath, "utf-8");
|
|
2438
|
+
const parsed = JSON.parse(raw);
|
|
2439
|
+
if (parsed.version === 1 && parsed.instances) {
|
|
2440
|
+
this.data = parsed;
|
|
2441
|
+
this.deduplicate();
|
|
2442
|
+
}
|
|
2443
|
+
} catch {
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
/** Remove duplicate entries that point to the same root, keeping the first one */
|
|
2447
|
+
deduplicate() {
|
|
2448
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2449
|
+
const toRemove = [];
|
|
2450
|
+
for (const [id, entry] of Object.entries(this.data.instances)) {
|
|
2451
|
+
if (seen.has(entry.root)) {
|
|
2452
|
+
toRemove.push(id);
|
|
2453
|
+
} else {
|
|
2454
|
+
seen.add(entry.root);
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
if (toRemove.length > 0) {
|
|
2458
|
+
for (const id of toRemove) delete this.data.instances[id];
|
|
2459
|
+
this.save();
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
save() {
|
|
2463
|
+
const dir = path25.dirname(this.registryPath);
|
|
2464
|
+
fs27.mkdirSync(dir, { recursive: true });
|
|
2465
|
+
fs27.writeFileSync(this.registryPath, JSON.stringify(this.data, null, 2));
|
|
2466
|
+
}
|
|
2467
|
+
register(id, root) {
|
|
2468
|
+
this.data.instances[id] = { id, root };
|
|
2469
|
+
}
|
|
2470
|
+
remove(id) {
|
|
2471
|
+
delete this.data.instances[id];
|
|
2472
|
+
}
|
|
2473
|
+
get(id) {
|
|
2474
|
+
return this.data.instances[id];
|
|
2475
|
+
}
|
|
2476
|
+
getByRoot(root) {
|
|
2477
|
+
return Object.values(this.data.instances).find((e) => e.root === root);
|
|
2478
|
+
}
|
|
2479
|
+
list() {
|
|
2480
|
+
return Object.values(this.data.instances);
|
|
2481
|
+
}
|
|
2482
|
+
uniqueId(baseId) {
|
|
2483
|
+
if (!this.data.instances[baseId]) return baseId;
|
|
2484
|
+
let n = 2;
|
|
2485
|
+
while (this.data.instances[`${baseId}-${n}`]) n++;
|
|
2486
|
+
return `${baseId}-${n}`;
|
|
2487
|
+
}
|
|
2488
|
+
/**
|
|
2489
|
+
* Resolve the authoritative id for an instance root.
|
|
2490
|
+
*
|
|
2491
|
+
* config.json is the source of truth. If the registry entry disagrees with
|
|
2492
|
+
* config.json, the registry is updated to match. If neither exists, a fresh
|
|
2493
|
+
* UUID is generated and registered (but NOT written to config.json — the
|
|
2494
|
+
* caller must call initInstanceFiles to persist it).
|
|
2495
|
+
*
|
|
2496
|
+
* Returns the resolved id and whether the registry was mutated (so callers
|
|
2497
|
+
* can decide whether to persist with save()).
|
|
2498
|
+
*/
|
|
2499
|
+
resolveId(instanceRoot) {
|
|
2500
|
+
const configId = readIdFromConfig(instanceRoot);
|
|
2501
|
+
const entry = this.getByRoot(instanceRoot);
|
|
2502
|
+
if (entry && configId && entry.id !== configId) {
|
|
2503
|
+
this.remove(entry.id);
|
|
2504
|
+
this.register(configId, instanceRoot);
|
|
2505
|
+
return { id: configId, registryUpdated: true };
|
|
2506
|
+
}
|
|
2507
|
+
const id = configId ?? entry?.id ?? randomUUID2();
|
|
2508
|
+
if (!this.getByRoot(instanceRoot)) {
|
|
2509
|
+
this.register(id, instanceRoot);
|
|
2510
|
+
return { id, registryUpdated: true };
|
|
2511
|
+
}
|
|
2512
|
+
return { id, registryUpdated: false };
|
|
2513
|
+
}
|
|
2514
|
+
};
|
|
2515
|
+
}
|
|
2516
|
+
});
|
|
2517
|
+
|
|
2502
2518
|
// src/core/plugin/plugin-installer.ts
|
|
2503
2519
|
var plugin_installer_exports = {};
|
|
2504
2520
|
__export(plugin_installer_exports, {
|
|
@@ -2507,16 +2523,15 @@ __export(plugin_installer_exports, {
|
|
|
2507
2523
|
});
|
|
2508
2524
|
import { execFile } from "child_process";
|
|
2509
2525
|
import { promisify } from "util";
|
|
2510
|
-
import * as
|
|
2511
|
-
import * as
|
|
2512
|
-
import * as path25 from "path";
|
|
2526
|
+
import * as fs29 from "fs/promises";
|
|
2527
|
+
import * as path27 from "path";
|
|
2513
2528
|
import { pathToFileURL } from "url";
|
|
2514
2529
|
async function importFromDir(packageName, dir) {
|
|
2515
|
-
const pkgDir =
|
|
2516
|
-
const pkgJsonPath =
|
|
2530
|
+
const pkgDir = path27.join(dir, "node_modules", ...packageName.split("/"));
|
|
2531
|
+
const pkgJsonPath = path27.join(pkgDir, "package.json");
|
|
2517
2532
|
let pkgJson;
|
|
2518
2533
|
try {
|
|
2519
|
-
pkgJson = JSON.parse(await
|
|
2534
|
+
pkgJson = JSON.parse(await fs29.readFile(pkgJsonPath, "utf-8"));
|
|
2520
2535
|
} catch (err) {
|
|
2521
2536
|
throw new Error(`Cannot read package.json for "${packageName}" at ${pkgJsonPath}: ${err.message}`);
|
|
2522
2537
|
}
|
|
@@ -2529,9 +2544,9 @@ async function importFromDir(packageName, dir) {
|
|
|
2529
2544
|
} else {
|
|
2530
2545
|
entry = pkgJson.main ?? "index.js";
|
|
2531
2546
|
}
|
|
2532
|
-
const entryPath =
|
|
2547
|
+
const entryPath = path27.join(pkgDir, entry);
|
|
2533
2548
|
try {
|
|
2534
|
-
await
|
|
2549
|
+
await fs29.access(entryPath);
|
|
2535
2550
|
} catch {
|
|
2536
2551
|
throw new Error(`Entry point "${entry}" not found for "${packageName}" at ${entryPath}`);
|
|
2537
2552
|
}
|
|
@@ -2541,7 +2556,7 @@ async function installNpmPlugin(packageName, pluginsDir) {
|
|
|
2541
2556
|
if (!VALID_NPM_NAME.test(packageName)) {
|
|
2542
2557
|
throw new Error(`Invalid package name: "${packageName}". Must be a valid npm package name.`);
|
|
2543
2558
|
}
|
|
2544
|
-
const dir = pluginsDir
|
|
2559
|
+
const dir = pluginsDir;
|
|
2545
2560
|
try {
|
|
2546
2561
|
return await importFromDir(packageName, dir);
|
|
2547
2562
|
} catch {
|
|
@@ -2623,10 +2638,10 @@ var install_context_exports = {};
|
|
|
2623
2638
|
__export(install_context_exports, {
|
|
2624
2639
|
createInstallContext: () => createInstallContext
|
|
2625
2640
|
});
|
|
2626
|
-
import
|
|
2641
|
+
import path28 from "path";
|
|
2627
2642
|
function createInstallContext(opts) {
|
|
2628
2643
|
const { pluginName, settingsManager, basePath, instanceRoot } = opts;
|
|
2629
|
-
const dataDir =
|
|
2644
|
+
const dataDir = path28.join(basePath, pluginName, "data");
|
|
2630
2645
|
return {
|
|
2631
2646
|
pluginName,
|
|
2632
2647
|
terminal: createTerminalIO(),
|
|
@@ -2650,21 +2665,31 @@ __export(api_client_exports, {
|
|
|
2650
2665
|
apiCall: () => apiCall,
|
|
2651
2666
|
readApiPort: () => readApiPort,
|
|
2652
2667
|
readApiSecret: () => readApiSecret,
|
|
2653
|
-
removeStalePortFile: () => removeStalePortFile
|
|
2668
|
+
removeStalePortFile: () => removeStalePortFile,
|
|
2669
|
+
waitForPortFile: () => waitForPortFile
|
|
2654
2670
|
});
|
|
2655
|
-
import * as
|
|
2656
|
-
import * as
|
|
2657
|
-
import
|
|
2671
|
+
import * as fs30 from "fs";
|
|
2672
|
+
import * as path29 from "path";
|
|
2673
|
+
import { setTimeout as sleep } from "timers/promises";
|
|
2658
2674
|
function defaultPortFile(root) {
|
|
2659
|
-
return
|
|
2675
|
+
return path29.join(root, "api.port");
|
|
2660
2676
|
}
|
|
2661
2677
|
function defaultSecretFile(root) {
|
|
2662
|
-
return
|
|
2678
|
+
return path29.join(root, "api-secret");
|
|
2679
|
+
}
|
|
2680
|
+
async function waitForPortFile(portFilePath, timeoutMs = 5e3, intervalMs = 100) {
|
|
2681
|
+
const deadline = Date.now() + timeoutMs;
|
|
2682
|
+
while (Date.now() < deadline) {
|
|
2683
|
+
const port = readApiPort(portFilePath);
|
|
2684
|
+
if (port !== null) return port;
|
|
2685
|
+
await sleep(intervalMs);
|
|
2686
|
+
}
|
|
2687
|
+
return readApiPort(portFilePath);
|
|
2663
2688
|
}
|
|
2664
2689
|
function readApiPort(portFilePath, instanceRoot) {
|
|
2665
2690
|
const filePath = portFilePath ?? defaultPortFile(instanceRoot);
|
|
2666
2691
|
try {
|
|
2667
|
-
const content =
|
|
2692
|
+
const content = fs30.readFileSync(filePath, "utf-8").trim();
|
|
2668
2693
|
const port = parseInt(content, 10);
|
|
2669
2694
|
return isNaN(port) ? null : port;
|
|
2670
2695
|
} catch {
|
|
@@ -2674,7 +2699,7 @@ function readApiPort(portFilePath, instanceRoot) {
|
|
|
2674
2699
|
function readApiSecret(secretFilePath, instanceRoot) {
|
|
2675
2700
|
const filePath = secretFilePath ?? defaultSecretFile(instanceRoot);
|
|
2676
2701
|
try {
|
|
2677
|
-
const content =
|
|
2702
|
+
const content = fs30.readFileSync(filePath, "utf-8").trim();
|
|
2678
2703
|
return content || null;
|
|
2679
2704
|
} catch {
|
|
2680
2705
|
return null;
|
|
@@ -2683,7 +2708,7 @@ function readApiSecret(secretFilePath, instanceRoot) {
|
|
|
2683
2708
|
function removeStalePortFile(portFilePath, instanceRoot) {
|
|
2684
2709
|
const filePath = portFilePath ?? defaultPortFile(instanceRoot);
|
|
2685
2710
|
try {
|
|
2686
|
-
|
|
2711
|
+
fs30.unlinkSync(filePath);
|
|
2687
2712
|
} catch {
|
|
2688
2713
|
}
|
|
2689
2714
|
}
|
|
@@ -2695,11 +2720,9 @@ async function apiCall(port, urlPath, options, instanceRoot) {
|
|
|
2695
2720
|
}
|
|
2696
2721
|
return fetch(`http://127.0.0.1:${port}${urlPath}`, { ...options, headers });
|
|
2697
2722
|
}
|
|
2698
|
-
var DEFAULT_ROOT;
|
|
2699
2723
|
var init_api_client = __esm({
|
|
2700
2724
|
"src/cli/api-client.ts"() {
|
|
2701
2725
|
"use strict";
|
|
2702
|
-
DEFAULT_ROOT = path27.join(os13.homedir(), ".openacp");
|
|
2703
2726
|
}
|
|
2704
2727
|
});
|
|
2705
2728
|
|
|
@@ -2733,8 +2756,8 @@ var init_notification = __esm({
|
|
|
2733
2756
|
});
|
|
2734
2757
|
|
|
2735
2758
|
// src/plugins/file-service/file-service.ts
|
|
2736
|
-
import
|
|
2737
|
-
import
|
|
2759
|
+
import fs32 from "fs";
|
|
2760
|
+
import path32 from "path";
|
|
2738
2761
|
import { OggOpusDecoder } from "ogg-opus-decoder";
|
|
2739
2762
|
import wav from "node-wav";
|
|
2740
2763
|
function classifyMime(mimeType) {
|
|
@@ -2790,14 +2813,14 @@ var init_file_service = __esm({
|
|
|
2790
2813
|
const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1e3;
|
|
2791
2814
|
let removed = 0;
|
|
2792
2815
|
try {
|
|
2793
|
-
const entries = await
|
|
2816
|
+
const entries = await fs32.promises.readdir(this.baseDir, { withFileTypes: true });
|
|
2794
2817
|
for (const entry of entries) {
|
|
2795
2818
|
if (!entry.isDirectory()) continue;
|
|
2796
|
-
const dirPath =
|
|
2819
|
+
const dirPath = path32.join(this.baseDir, entry.name);
|
|
2797
2820
|
try {
|
|
2798
|
-
const stat = await
|
|
2821
|
+
const stat = await fs32.promises.stat(dirPath);
|
|
2799
2822
|
if (stat.mtimeMs < cutoff) {
|
|
2800
|
-
await
|
|
2823
|
+
await fs32.promises.rm(dirPath, { recursive: true, force: true });
|
|
2801
2824
|
removed++;
|
|
2802
2825
|
}
|
|
2803
2826
|
} catch {
|
|
@@ -2808,11 +2831,11 @@ var init_file_service = __esm({
|
|
|
2808
2831
|
return removed;
|
|
2809
2832
|
}
|
|
2810
2833
|
async saveFile(sessionId, fileName, data, mimeType) {
|
|
2811
|
-
const sessionDir =
|
|
2812
|
-
await
|
|
2834
|
+
const sessionDir = path32.join(this.baseDir, sessionId);
|
|
2835
|
+
await fs32.promises.mkdir(sessionDir, { recursive: true });
|
|
2813
2836
|
const safeName = `${Date.now()}-${fileName.replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
2814
|
-
const filePath =
|
|
2815
|
-
await
|
|
2837
|
+
const filePath = path32.join(sessionDir, safeName);
|
|
2838
|
+
await fs32.promises.writeFile(filePath, data);
|
|
2816
2839
|
return {
|
|
2817
2840
|
type: classifyMime(mimeType),
|
|
2818
2841
|
filePath,
|
|
@@ -2823,14 +2846,14 @@ var init_file_service = __esm({
|
|
|
2823
2846
|
}
|
|
2824
2847
|
async resolveFile(filePath) {
|
|
2825
2848
|
try {
|
|
2826
|
-
const stat = await
|
|
2849
|
+
const stat = await fs32.promises.stat(filePath);
|
|
2827
2850
|
if (!stat.isFile()) return null;
|
|
2828
|
-
const ext =
|
|
2851
|
+
const ext = path32.extname(filePath).toLowerCase();
|
|
2829
2852
|
const mimeType = EXT_TO_MIME[ext] || "application/octet-stream";
|
|
2830
2853
|
return {
|
|
2831
2854
|
type: classifyMime(mimeType),
|
|
2832
2855
|
filePath,
|
|
2833
|
-
fileName:
|
|
2856
|
+
fileName: path32.basename(filePath),
|
|
2834
2857
|
mimeType,
|
|
2835
2858
|
size: stat.size
|
|
2836
2859
|
};
|
|
@@ -3065,7 +3088,7 @@ function createAuthPreHandler(getSecret, getJwtSecret, tokenStore) {
|
|
|
3065
3088
|
const queryToken = request.query?.token;
|
|
3066
3089
|
const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : queryToken;
|
|
3067
3090
|
if (queryToken && !authHeader) {
|
|
3068
|
-
|
|
3091
|
+
log21.warn(
|
|
3069
3092
|
{ url: request.url.replace(/([?&]token=)[^&]+/, "$1[REDACTED]") },
|
|
3070
3093
|
"Token passed via URL query param \u2014 use Authorization: Bearer header to avoid token leakage in tunnel/proxy logs"
|
|
3071
3094
|
);
|
|
@@ -3119,7 +3142,7 @@ function requireRole(role) {
|
|
|
3119
3142
|
}
|
|
3120
3143
|
};
|
|
3121
3144
|
}
|
|
3122
|
-
var
|
|
3145
|
+
var log21;
|
|
3123
3146
|
var init_auth = __esm({
|
|
3124
3147
|
"src/plugins/api-server/middleware/auth.ts"() {
|
|
3125
3148
|
"use strict";
|
|
@@ -3127,7 +3150,7 @@ var init_auth = __esm({
|
|
|
3127
3150
|
init_jwt();
|
|
3128
3151
|
init_roles();
|
|
3129
3152
|
init_log();
|
|
3130
|
-
|
|
3153
|
+
log21 = createChildLogger({ module: "api-auth" });
|
|
3131
3154
|
}
|
|
3132
3155
|
});
|
|
3133
3156
|
|
|
@@ -3396,8 +3419,8 @@ data: ${JSON.stringify(data)}
|
|
|
3396
3419
|
});
|
|
3397
3420
|
|
|
3398
3421
|
// src/plugins/api-server/static-server.ts
|
|
3399
|
-
import * as
|
|
3400
|
-
import * as
|
|
3422
|
+
import * as fs33 from "fs";
|
|
3423
|
+
import * as path33 from "path";
|
|
3401
3424
|
import { fileURLToPath } from "url";
|
|
3402
3425
|
var MIME_TYPES, StaticServer;
|
|
3403
3426
|
var init_static_server = __esm({
|
|
@@ -3421,16 +3444,16 @@ var init_static_server = __esm({
|
|
|
3421
3444
|
this.uiDir = uiDir;
|
|
3422
3445
|
if (!this.uiDir) {
|
|
3423
3446
|
const __filename = fileURLToPath(import.meta.url);
|
|
3424
|
-
const candidate =
|
|
3425
|
-
if (
|
|
3447
|
+
const candidate = path33.resolve(path33.dirname(__filename), "../../ui/dist");
|
|
3448
|
+
if (fs33.existsSync(path33.join(candidate, "index.html"))) {
|
|
3426
3449
|
this.uiDir = candidate;
|
|
3427
3450
|
}
|
|
3428
3451
|
if (!this.uiDir) {
|
|
3429
|
-
const publishCandidate =
|
|
3430
|
-
|
|
3452
|
+
const publishCandidate = path33.resolve(
|
|
3453
|
+
path33.dirname(__filename),
|
|
3431
3454
|
"../ui"
|
|
3432
3455
|
);
|
|
3433
|
-
if (
|
|
3456
|
+
if (fs33.existsSync(path33.join(publishCandidate, "index.html"))) {
|
|
3434
3457
|
this.uiDir = publishCandidate;
|
|
3435
3458
|
}
|
|
3436
3459
|
}
|
|
@@ -3442,23 +3465,23 @@ var init_static_server = __esm({
|
|
|
3442
3465
|
serve(req, res) {
|
|
3443
3466
|
if (!this.uiDir) return false;
|
|
3444
3467
|
const urlPath = (req.url || "/").split("?")[0];
|
|
3445
|
-
const safePath =
|
|
3446
|
-
const filePath =
|
|
3447
|
-
if (!filePath.startsWith(this.uiDir +
|
|
3468
|
+
const safePath = path33.normalize(urlPath);
|
|
3469
|
+
const filePath = path33.join(this.uiDir, safePath);
|
|
3470
|
+
if (!filePath.startsWith(this.uiDir + path33.sep) && filePath !== this.uiDir)
|
|
3448
3471
|
return false;
|
|
3449
3472
|
let realFilePath;
|
|
3450
3473
|
try {
|
|
3451
|
-
realFilePath =
|
|
3474
|
+
realFilePath = fs33.realpathSync(filePath);
|
|
3452
3475
|
} catch {
|
|
3453
3476
|
realFilePath = null;
|
|
3454
3477
|
}
|
|
3455
3478
|
if (realFilePath !== null) {
|
|
3456
|
-
const realUiDir =
|
|
3457
|
-
if (!realFilePath.startsWith(realUiDir +
|
|
3479
|
+
const realUiDir = fs33.realpathSync(this.uiDir);
|
|
3480
|
+
if (!realFilePath.startsWith(realUiDir + path33.sep) && realFilePath !== realUiDir)
|
|
3458
3481
|
return false;
|
|
3459
3482
|
}
|
|
3460
|
-
if (realFilePath !== null &&
|
|
3461
|
-
const ext =
|
|
3483
|
+
if (realFilePath !== null && fs33.existsSync(realFilePath) && fs33.statSync(realFilePath).isFile()) {
|
|
3484
|
+
const ext = path33.extname(filePath);
|
|
3462
3485
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
3463
3486
|
const isHashed = /\.[a-zA-Z0-9]{8,}\.(js|css)$/.test(filePath);
|
|
3464
3487
|
const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "no-cache";
|
|
@@ -3466,16 +3489,16 @@ var init_static_server = __esm({
|
|
|
3466
3489
|
"Content-Type": contentType,
|
|
3467
3490
|
"Cache-Control": cacheControl
|
|
3468
3491
|
});
|
|
3469
|
-
|
|
3492
|
+
fs33.createReadStream(realFilePath).pipe(res);
|
|
3470
3493
|
return true;
|
|
3471
3494
|
}
|
|
3472
|
-
const indexPath =
|
|
3473
|
-
if (
|
|
3495
|
+
const indexPath = path33.join(this.uiDir, "index.html");
|
|
3496
|
+
if (fs33.existsSync(indexPath)) {
|
|
3474
3497
|
res.writeHead(200, {
|
|
3475
3498
|
"Content-Type": "text/html; charset=utf-8",
|
|
3476
3499
|
"Cache-Control": "no-cache"
|
|
3477
3500
|
});
|
|
3478
|
-
|
|
3501
|
+
fs33.createReadStream(indexPath).pipe(res);
|
|
3479
3502
|
return true;
|
|
3480
3503
|
}
|
|
3481
3504
|
return false;
|
|
@@ -3630,8 +3653,8 @@ var init_exports = __esm({
|
|
|
3630
3653
|
});
|
|
3631
3654
|
|
|
3632
3655
|
// src/plugins/context/context-cache.ts
|
|
3633
|
-
import * as
|
|
3634
|
-
import * as
|
|
3656
|
+
import * as fs34 from "fs";
|
|
3657
|
+
import * as path34 from "path";
|
|
3635
3658
|
import * as crypto2 from "crypto";
|
|
3636
3659
|
var DEFAULT_TTL_MS, ContextCache;
|
|
3637
3660
|
var init_context_cache = __esm({
|
|
@@ -3642,37 +3665,35 @@ var init_context_cache = __esm({
|
|
|
3642
3665
|
constructor(cacheDir, ttlMs = DEFAULT_TTL_MS) {
|
|
3643
3666
|
this.cacheDir = cacheDir;
|
|
3644
3667
|
this.ttlMs = ttlMs;
|
|
3645
|
-
|
|
3668
|
+
fs34.mkdirSync(cacheDir, { recursive: true });
|
|
3646
3669
|
}
|
|
3647
3670
|
keyHash(repoPath, queryKey) {
|
|
3648
3671
|
return crypto2.createHash("sha256").update(`${repoPath}:${queryKey}`).digest("hex").slice(0, 16);
|
|
3649
3672
|
}
|
|
3650
3673
|
filePath(repoPath, queryKey) {
|
|
3651
|
-
return
|
|
3674
|
+
return path34.join(this.cacheDir, `${this.keyHash(repoPath, queryKey)}.json`);
|
|
3652
3675
|
}
|
|
3653
3676
|
get(repoPath, queryKey) {
|
|
3654
3677
|
const fp = this.filePath(repoPath, queryKey);
|
|
3655
3678
|
try {
|
|
3656
|
-
const stat =
|
|
3679
|
+
const stat = fs34.statSync(fp);
|
|
3657
3680
|
if (Date.now() - stat.mtimeMs > this.ttlMs) {
|
|
3658
|
-
|
|
3681
|
+
fs34.unlinkSync(fp);
|
|
3659
3682
|
return null;
|
|
3660
3683
|
}
|
|
3661
|
-
return JSON.parse(
|
|
3684
|
+
return JSON.parse(fs34.readFileSync(fp, "utf-8"));
|
|
3662
3685
|
} catch {
|
|
3663
3686
|
return null;
|
|
3664
3687
|
}
|
|
3665
3688
|
}
|
|
3666
3689
|
set(repoPath, queryKey, result) {
|
|
3667
|
-
|
|
3690
|
+
fs34.writeFileSync(this.filePath(repoPath, queryKey), JSON.stringify(result));
|
|
3668
3691
|
}
|
|
3669
3692
|
};
|
|
3670
3693
|
}
|
|
3671
3694
|
});
|
|
3672
3695
|
|
|
3673
3696
|
// src/plugins/context/context-manager.ts
|
|
3674
|
-
import * as os15 from "os";
|
|
3675
|
-
import * as path33 from "path";
|
|
3676
3697
|
var ContextManager;
|
|
3677
3698
|
var init_context_manager = __esm({
|
|
3678
3699
|
"src/plugins/context/context-manager.ts"() {
|
|
@@ -3684,7 +3705,7 @@ var init_context_manager = __esm({
|
|
|
3684
3705
|
historyStore;
|
|
3685
3706
|
sessionFlusher;
|
|
3686
3707
|
constructor(cachePath) {
|
|
3687
|
-
this.cache = new ContextCache(cachePath
|
|
3708
|
+
this.cache = new ContextCache(cachePath);
|
|
3688
3709
|
}
|
|
3689
3710
|
setHistoryStore(store) {
|
|
3690
3711
|
this.historyStore = store;
|
|
@@ -4601,8 +4622,8 @@ function formatToolSummary(name, rawInput, displaySummary) {
|
|
|
4601
4622
|
}
|
|
4602
4623
|
if (lowerName === "grep") {
|
|
4603
4624
|
const pattern = args.pattern ?? "";
|
|
4604
|
-
const
|
|
4605
|
-
return pattern ? `\u{1F50D} Grep "${pattern}"${
|
|
4625
|
+
const path35 = args.path ?? "";
|
|
4626
|
+
return pattern ? `\u{1F50D} Grep "${pattern}"${path35 ? ` in ${path35}` : ""}` : `\u{1F527} ${name}`;
|
|
4606
4627
|
}
|
|
4607
4628
|
if (lowerName === "glob") {
|
|
4608
4629
|
const pattern = args.pattern ?? "";
|
|
@@ -4638,8 +4659,8 @@ function formatToolTitle(name, rawInput, displayTitle) {
|
|
|
4638
4659
|
}
|
|
4639
4660
|
if (lowerName === "grep") {
|
|
4640
4661
|
const pattern = args.pattern ?? "";
|
|
4641
|
-
const
|
|
4642
|
-
return pattern ? `"${pattern}"${
|
|
4662
|
+
const path35 = args.path ?? "";
|
|
4663
|
+
return pattern ? `"${pattern}"${path35 ? ` in ${path35}` : ""}` : name;
|
|
4643
4664
|
}
|
|
4644
4665
|
if (lowerName === "glob") {
|
|
4645
4666
|
return String(args.pattern ?? name);
|
|
@@ -5842,13 +5863,13 @@ __export(version_exports, {
|
|
|
5842
5863
|
runUpdate: () => runUpdate
|
|
5843
5864
|
});
|
|
5844
5865
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5845
|
-
import { dirname as
|
|
5846
|
-
import { existsSync as
|
|
5866
|
+
import { dirname as dirname12, join as join16, resolve as resolve6 } from "path";
|
|
5867
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
|
|
5847
5868
|
function findPackageJson() {
|
|
5848
|
-
let dir =
|
|
5869
|
+
let dir = dirname12(fileURLToPath2(import.meta.url));
|
|
5849
5870
|
for (let i = 0; i < 5; i++) {
|
|
5850
|
-
const candidate =
|
|
5851
|
-
if (
|
|
5871
|
+
const candidate = join16(dir, "package.json");
|
|
5872
|
+
if (existsSync16(candidate)) return candidate;
|
|
5852
5873
|
const parent = resolve6(dir, "..");
|
|
5853
5874
|
if (parent === dir) break;
|
|
5854
5875
|
dir = parent;
|
|
@@ -5859,7 +5880,7 @@ function getCurrentVersion() {
|
|
|
5859
5880
|
try {
|
|
5860
5881
|
const pkgPath = findPackageJson();
|
|
5861
5882
|
if (!pkgPath) return "0.0.0-dev";
|
|
5862
|
-
const pkg = JSON.parse(
|
|
5883
|
+
const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
|
|
5863
5884
|
return pkg.version;
|
|
5864
5885
|
} catch {
|
|
5865
5886
|
return "0.0.0-dev";
|
|
@@ -5965,7 +5986,7 @@ function setupDangerousModeCallbacks(bot, core) {
|
|
|
5965
5986
|
}).catch(() => {
|
|
5966
5987
|
});
|
|
5967
5988
|
}
|
|
5968
|
-
|
|
5989
|
+
log23.info({ sessionId, wantOn }, "Bypass permissions toggled via button");
|
|
5969
5990
|
try {
|
|
5970
5991
|
await ctx.editMessageText(buildSessionStatusText(session), {
|
|
5971
5992
|
parse_mode: "HTML",
|
|
@@ -5988,7 +6009,7 @@ function setupDangerousModeCallbacks(bot, core) {
|
|
|
5988
6009
|
const newDangerousMode = !(record.clientOverrides?.bypassPermissions ?? record.dangerousMode ?? false);
|
|
5989
6010
|
core.sessionManager.patchRecord(sessionId, { clientOverrides: { bypassPermissions: newDangerousMode } }).catch(() => {
|
|
5990
6011
|
});
|
|
5991
|
-
|
|
6012
|
+
log23.info(
|
|
5992
6013
|
{ sessionId, dangerousMode: newDangerousMode },
|
|
5993
6014
|
"Bypass permissions toggled via button (store-only, session not in memory)"
|
|
5994
6015
|
);
|
|
@@ -6173,14 +6194,14 @@ async function handleRestart(ctx, core) {
|
|
|
6173
6194
|
await new Promise((r) => setTimeout(r, 500));
|
|
6174
6195
|
await core.requestRestart();
|
|
6175
6196
|
}
|
|
6176
|
-
var
|
|
6197
|
+
var log23, OUTPUT_MODE_LABELS;
|
|
6177
6198
|
var init_admin = __esm({
|
|
6178
6199
|
"src/plugins/telegram/commands/admin.ts"() {
|
|
6179
6200
|
"use strict";
|
|
6180
6201
|
init_bypass_detection();
|
|
6181
6202
|
init_formatting();
|
|
6182
6203
|
init_log();
|
|
6183
|
-
|
|
6204
|
+
log23 = createChildLogger({ module: "telegram-cmd-admin" });
|
|
6184
6205
|
OUTPUT_MODE_LABELS = {
|
|
6185
6206
|
low: "\u{1F507} Low",
|
|
6186
6207
|
medium: "\u{1F4CA} Medium",
|
|
@@ -6195,7 +6216,7 @@ function botFromCtx(ctx) {
|
|
|
6195
6216
|
return { api: ctx.api };
|
|
6196
6217
|
}
|
|
6197
6218
|
async function createSessionDirect(ctx, core, chatId, agentName, workspace, onControlMessage) {
|
|
6198
|
-
|
|
6219
|
+
log24.info({ userId: ctx.from?.id, agentName, workspace }, "New session command (direct)");
|
|
6199
6220
|
let threadId;
|
|
6200
6221
|
try {
|
|
6201
6222
|
const topicName = `\u{1F504} New Session`;
|
|
@@ -6222,7 +6243,7 @@ async function createSessionDirect(ctx, core, chatId, agentName, workspace, onCo
|
|
|
6222
6243
|
onControlMessage?.(session.id, controlMsg.message_id);
|
|
6223
6244
|
return threadId ?? null;
|
|
6224
6245
|
} catch (err) {
|
|
6225
|
-
|
|
6246
|
+
log24.error({ err }, "Session creation failed");
|
|
6226
6247
|
if (threadId) {
|
|
6227
6248
|
try {
|
|
6228
6249
|
await ctx.api.deleteForumTopic(chatId, threadId);
|
|
@@ -6329,10 +6350,8 @@ async function showAgentPicker(ctx, core, chatId) {
|
|
|
6329
6350
|
async function showWorkspacePicker(ctx, core, chatId, agentKey, newMessage = false) {
|
|
6330
6351
|
const records = core.sessionManager.listRecords();
|
|
6331
6352
|
const recentWorkspaces = [...new Set(records.map((r) => r.workingDir).filter(Boolean))].slice(0, 5);
|
|
6332
|
-
const
|
|
6333
|
-
const
|
|
6334
|
-
const resolvedBaseDir = core.configManager.resolveWorkspace(baseDir);
|
|
6335
|
-
const hasBaseDir = recentWorkspaces.some((ws) => ws === baseDir || ws === resolvedBaseDir);
|
|
6353
|
+
const resolvedBaseDir = core.configManager.resolveWorkspace();
|
|
6354
|
+
const hasBaseDir = recentWorkspaces.some((ws) => ws === resolvedBaseDir);
|
|
6336
6355
|
const workspaces = hasBaseDir ? recentWorkspaces : [resolvedBaseDir, ...recentWorkspaces].slice(0, 5);
|
|
6337
6356
|
const kb = new InlineKeyboard2();
|
|
6338
6357
|
for (const ws of workspaces) {
|
|
@@ -6457,7 +6476,7 @@ Agent: <code>${escapeHtml(agentKey)}</code>
|
|
|
6457
6476
|
await _sendCustomPathPrompt(ctx, chatId, agentKey);
|
|
6458
6477
|
});
|
|
6459
6478
|
}
|
|
6460
|
-
var
|
|
6479
|
+
var log24, WS_CACHE_MAX, workspaceCache, nextWsId, _forceReplyMap;
|
|
6461
6480
|
var init_new_session = __esm({
|
|
6462
6481
|
"src/plugins/telegram/commands/new-session.ts"() {
|
|
6463
6482
|
"use strict";
|
|
@@ -6465,7 +6484,7 @@ var init_new_session = __esm({
|
|
|
6465
6484
|
init_topics();
|
|
6466
6485
|
init_log();
|
|
6467
6486
|
init_admin();
|
|
6468
|
-
|
|
6487
|
+
log24 = createChildLogger({ module: "telegram-cmd-new-session" });
|
|
6469
6488
|
WS_CACHE_MAX = 50;
|
|
6470
6489
|
workspaceCache = /* @__PURE__ */ new Map();
|
|
6471
6490
|
nextWsId = 0;
|
|
@@ -6530,7 +6549,7 @@ ${lines.join("\n")}${truncated}`,
|
|
|
6530
6549
|
{ parse_mode: "HTML", reply_markup: keyboard }
|
|
6531
6550
|
);
|
|
6532
6551
|
} catch (err) {
|
|
6533
|
-
|
|
6552
|
+
log25.error({ err }, "handleTopics error");
|
|
6534
6553
|
await ctx.reply("\u274C Failed to list sessions.", { parse_mode: "HTML" }).catch(() => {
|
|
6535
6554
|
});
|
|
6536
6555
|
}
|
|
@@ -6551,13 +6570,13 @@ async function handleCleanup(ctx, core, chatId, statuses) {
|
|
|
6551
6570
|
try {
|
|
6552
6571
|
await ctx.api.deleteForumTopic(chatId, topicId);
|
|
6553
6572
|
} catch (err) {
|
|
6554
|
-
|
|
6573
|
+
log25.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
|
|
6555
6574
|
}
|
|
6556
6575
|
}
|
|
6557
6576
|
await core.sessionManager.removeRecord(record.sessionId);
|
|
6558
6577
|
deleted++;
|
|
6559
6578
|
} catch (err) {
|
|
6560
|
-
|
|
6579
|
+
log25.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
|
|
6561
6580
|
failed++;
|
|
6562
6581
|
}
|
|
6563
6582
|
}
|
|
@@ -6628,7 +6647,7 @@ async function handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicId
|
|
|
6628
6647
|
try {
|
|
6629
6648
|
await core.sessionManager.cancelSession(record.sessionId);
|
|
6630
6649
|
} catch (err) {
|
|
6631
|
-
|
|
6650
|
+
log25.warn({ err, sessionId: record.sessionId }, "Failed to cancel session during cleanup");
|
|
6632
6651
|
}
|
|
6633
6652
|
}
|
|
6634
6653
|
const topicId = record.platform?.topicId;
|
|
@@ -6636,13 +6655,13 @@ async function handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicId
|
|
|
6636
6655
|
try {
|
|
6637
6656
|
await ctx.api.deleteForumTopic(chatId, topicId);
|
|
6638
6657
|
} catch (err) {
|
|
6639
|
-
|
|
6658
|
+
log25.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
|
|
6640
6659
|
}
|
|
6641
6660
|
}
|
|
6642
6661
|
await core.sessionManager.removeRecord(record.sessionId);
|
|
6643
6662
|
deleted++;
|
|
6644
6663
|
} catch (err) {
|
|
6645
|
-
|
|
6664
|
+
log25.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
|
|
6646
6665
|
failed++;
|
|
6647
6666
|
}
|
|
6648
6667
|
}
|
|
@@ -6710,13 +6729,13 @@ async function handleArchiveConfirm(ctx, core, chatId) {
|
|
|
6710
6729
|
}
|
|
6711
6730
|
}
|
|
6712
6731
|
}
|
|
6713
|
-
var
|
|
6732
|
+
var log25;
|
|
6714
6733
|
var init_session = __esm({
|
|
6715
6734
|
"src/plugins/telegram/commands/session.ts"() {
|
|
6716
6735
|
"use strict";
|
|
6717
6736
|
init_formatting();
|
|
6718
6737
|
init_log();
|
|
6719
|
-
|
|
6738
|
+
log25 = createChildLogger({ module: "telegram-cmd-session" });
|
|
6720
6739
|
}
|
|
6721
6740
|
});
|
|
6722
6741
|
|
|
@@ -6728,14 +6747,14 @@ __export(integrate_exports, {
|
|
|
6728
6747
|
listIntegrations: () => listIntegrations,
|
|
6729
6748
|
uninstallIntegration: () => uninstallIntegration
|
|
6730
6749
|
});
|
|
6731
|
-
import { existsSync as
|
|
6732
|
-
import { join as
|
|
6733
|
-
import { homedir as
|
|
6750
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync11, readFileSync as readFileSync15, writeFileSync as writeFileSync11, unlinkSync as unlinkSync7, chmodSync, rmdirSync } from "fs";
|
|
6751
|
+
import { join as join17, dirname as dirname13 } from "path";
|
|
6752
|
+
import { homedir as homedir4 } from "os";
|
|
6734
6753
|
function isHooksIntegrationSpec(spec) {
|
|
6735
6754
|
return spec.strategy === "hooks";
|
|
6736
6755
|
}
|
|
6737
6756
|
function expandPath(p) {
|
|
6738
|
-
return p.replace(/^~/,
|
|
6757
|
+
return p.replace(/^~/, homedir4());
|
|
6739
6758
|
}
|
|
6740
6759
|
function generateInjectScript(_agentKey, spec) {
|
|
6741
6760
|
const sidVar = spec.sessionIdVar ?? "SESSION_ID";
|
|
@@ -6890,8 +6909,8 @@ function generateOpencodePlugin(spec) {
|
|
|
6890
6909
|
function mergeSettingsJson(settingsPath, hookEvent, hookScriptPath) {
|
|
6891
6910
|
const fullPath = expandPath(settingsPath);
|
|
6892
6911
|
let settings = {};
|
|
6893
|
-
if (
|
|
6894
|
-
const raw =
|
|
6912
|
+
if (existsSync17(fullPath)) {
|
|
6913
|
+
const raw = readFileSync15(fullPath, "utf-8");
|
|
6895
6914
|
writeFileSync11(`${fullPath}.bak`, raw);
|
|
6896
6915
|
settings = JSON.parse(raw);
|
|
6897
6916
|
}
|
|
@@ -6907,14 +6926,14 @@ function mergeSettingsJson(settingsPath, hookEvent, hookScriptPath) {
|
|
|
6907
6926
|
hooks: [{ type: "command", command: hookScriptPath }]
|
|
6908
6927
|
});
|
|
6909
6928
|
}
|
|
6910
|
-
mkdirSync11(
|
|
6929
|
+
mkdirSync11(dirname13(fullPath), { recursive: true });
|
|
6911
6930
|
writeFileSync11(fullPath, JSON.stringify(settings, null, 2) + "\n");
|
|
6912
6931
|
}
|
|
6913
6932
|
function mergeHooksJson(settingsPath, hookEvent, hookScriptPath) {
|
|
6914
6933
|
const fullPath = expandPath(settingsPath);
|
|
6915
6934
|
let config = { version: 1 };
|
|
6916
|
-
if (
|
|
6917
|
-
const raw =
|
|
6935
|
+
if (existsSync17(fullPath)) {
|
|
6936
|
+
const raw = readFileSync15(fullPath, "utf-8");
|
|
6918
6937
|
writeFileSync11(`${fullPath}.bak`, raw);
|
|
6919
6938
|
config = JSON.parse(raw);
|
|
6920
6939
|
}
|
|
@@ -6926,13 +6945,13 @@ function mergeHooksJson(settingsPath, hookEvent, hookScriptPath) {
|
|
|
6926
6945
|
if (!alreadyInstalled) {
|
|
6927
6946
|
eventHooks.push({ command: hookScriptPath });
|
|
6928
6947
|
}
|
|
6929
|
-
mkdirSync11(
|
|
6948
|
+
mkdirSync11(dirname13(fullPath), { recursive: true });
|
|
6930
6949
|
writeFileSync11(fullPath, JSON.stringify(config, null, 2) + "\n");
|
|
6931
6950
|
}
|
|
6932
6951
|
function removeFromSettingsJson(settingsPath, hookEvent) {
|
|
6933
6952
|
const fullPath = expandPath(settingsPath);
|
|
6934
|
-
if (!
|
|
6935
|
-
const raw =
|
|
6953
|
+
if (!existsSync17(fullPath)) return;
|
|
6954
|
+
const raw = readFileSync15(fullPath, "utf-8");
|
|
6936
6955
|
const settings = JSON.parse(raw);
|
|
6937
6956
|
const hooks = settings.hooks;
|
|
6938
6957
|
if (!hooks?.[hookEvent]) return;
|
|
@@ -6946,8 +6965,8 @@ function removeFromSettingsJson(settingsPath, hookEvent) {
|
|
|
6946
6965
|
}
|
|
6947
6966
|
function removeFromHooksJson(settingsPath, hookEvent) {
|
|
6948
6967
|
const fullPath = expandPath(settingsPath);
|
|
6949
|
-
if (!
|
|
6950
|
-
const raw =
|
|
6968
|
+
if (!existsSync17(fullPath)) return;
|
|
6969
|
+
const raw = readFileSync15(fullPath, "utf-8");
|
|
6951
6970
|
const config = JSON.parse(raw);
|
|
6952
6971
|
const hooks = config.hooks;
|
|
6953
6972
|
if (!hooks?.[hookEvent]) return;
|
|
@@ -6970,30 +6989,30 @@ async function installHooksIntegration(agentKey, spec) {
|
|
|
6970
6989
|
}
|
|
6971
6990
|
const hooksDir = expandPath(spec.hooksDirPath);
|
|
6972
6991
|
mkdirSync11(hooksDir, { recursive: true });
|
|
6973
|
-
const injectPath =
|
|
6992
|
+
const injectPath = join17(hooksDir, "openacp-inject-session.sh");
|
|
6974
6993
|
writeFileSync11(injectPath, generateInjectScript(agentKey, spec));
|
|
6975
6994
|
chmodSync(injectPath, 493);
|
|
6976
6995
|
logs.push(`Created ${injectPath}`);
|
|
6977
|
-
const handoffPath =
|
|
6996
|
+
const handoffPath = join17(hooksDir, "openacp-handoff.sh");
|
|
6978
6997
|
writeFileSync11(handoffPath, generateHandoffScript(agentKey));
|
|
6979
6998
|
chmodSync(handoffPath, 493);
|
|
6980
6999
|
logs.push(`Created ${handoffPath}`);
|
|
6981
7000
|
if (spec.commandsPath && spec.handoffCommandName) {
|
|
6982
7001
|
if (spec.commandFormat === "skill") {
|
|
6983
|
-
const skillDir = expandPath(
|
|
7002
|
+
const skillDir = expandPath(join17(spec.commandsPath, spec.handoffCommandName));
|
|
6984
7003
|
mkdirSync11(skillDir, { recursive: true });
|
|
6985
|
-
const skillPath =
|
|
7004
|
+
const skillPath = join17(skillDir, "SKILL.md");
|
|
6986
7005
|
writeFileSync11(skillPath, generateHandoffCommand(agentKey, spec));
|
|
6987
7006
|
logs.push(`Created ${skillPath}`);
|
|
6988
7007
|
} else {
|
|
6989
7008
|
const cmdsDir = expandPath(spec.commandsPath);
|
|
6990
7009
|
mkdirSync11(cmdsDir, { recursive: true });
|
|
6991
|
-
const cmdPath =
|
|
7010
|
+
const cmdPath = join17(cmdsDir, `${spec.handoffCommandName}.md`);
|
|
6992
7011
|
writeFileSync11(cmdPath, generateHandoffCommand(agentKey, spec));
|
|
6993
7012
|
logs.push(`Created ${cmdPath}`);
|
|
6994
7013
|
}
|
|
6995
7014
|
}
|
|
6996
|
-
const injectFullPath =
|
|
7015
|
+
const injectFullPath = join17(hooksDir, "openacp-inject-session.sh");
|
|
6997
7016
|
if (spec.settingsFormat === "hooks_json") {
|
|
6998
7017
|
mergeHooksJson(spec.settingsPath, spec.hookEvent, injectFullPath);
|
|
6999
7018
|
} else {
|
|
@@ -7011,17 +7030,17 @@ async function uninstallHooksIntegration(agentKey, spec) {
|
|
|
7011
7030
|
try {
|
|
7012
7031
|
const hooksDir = expandPath(spec.hooksDirPath);
|
|
7013
7032
|
for (const filename of ["openacp-inject-session.sh", "openacp-handoff.sh"]) {
|
|
7014
|
-
const filePath =
|
|
7015
|
-
if (
|
|
7033
|
+
const filePath = join17(hooksDir, filename);
|
|
7034
|
+
if (existsSync17(filePath)) {
|
|
7016
7035
|
unlinkSync7(filePath);
|
|
7017
7036
|
logs.push(`Removed ${filePath}`);
|
|
7018
7037
|
}
|
|
7019
7038
|
}
|
|
7020
7039
|
if (spec.commandsPath && spec.handoffCommandName) {
|
|
7021
7040
|
if (spec.commandFormat === "skill") {
|
|
7022
|
-
const skillDir = expandPath(
|
|
7023
|
-
const skillPath =
|
|
7024
|
-
if (
|
|
7041
|
+
const skillDir = expandPath(join17(spec.commandsPath, spec.handoffCommandName));
|
|
7042
|
+
const skillPath = join17(skillDir, "SKILL.md");
|
|
7043
|
+
if (existsSync17(skillPath)) {
|
|
7025
7044
|
unlinkSync7(skillPath);
|
|
7026
7045
|
try {
|
|
7027
7046
|
rmdirSync(skillDir);
|
|
@@ -7030,8 +7049,8 @@ async function uninstallHooksIntegration(agentKey, spec) {
|
|
|
7030
7049
|
logs.push(`Removed ${skillPath}`);
|
|
7031
7050
|
}
|
|
7032
7051
|
} else {
|
|
7033
|
-
const cmdPath = expandPath(
|
|
7034
|
-
if (
|
|
7052
|
+
const cmdPath = expandPath(join17(spec.commandsPath, `${spec.handoffCommandName}.md`));
|
|
7053
|
+
if (existsSync17(cmdPath)) {
|
|
7035
7054
|
unlinkSync7(cmdPath);
|
|
7036
7055
|
logs.push(`Removed ${cmdPath}`);
|
|
7037
7056
|
}
|
|
@@ -7054,15 +7073,15 @@ async function installPluginIntegration(_agentKey, spec) {
|
|
|
7054
7073
|
try {
|
|
7055
7074
|
const commandsDir = expandPath(spec.commandsPath);
|
|
7056
7075
|
mkdirSync11(commandsDir, { recursive: true });
|
|
7057
|
-
const commandPath =
|
|
7076
|
+
const commandPath = join17(commandsDir, spec.handoffCommandFile);
|
|
7058
7077
|
const pluginsDir = expandPath(spec.pluginsPath);
|
|
7059
7078
|
mkdirSync11(pluginsDir, { recursive: true });
|
|
7060
|
-
const pluginPath =
|
|
7061
|
-
if (
|
|
7079
|
+
const pluginPath = join17(pluginsDir, spec.pluginFileName);
|
|
7080
|
+
if (existsSync17(commandPath) && existsSync17(pluginPath)) {
|
|
7062
7081
|
logs.push("Already installed, skipping.");
|
|
7063
7082
|
return { success: true, logs };
|
|
7064
7083
|
}
|
|
7065
|
-
if (
|
|
7084
|
+
if (existsSync17(commandPath) || existsSync17(pluginPath)) {
|
|
7066
7085
|
logs.push("Overwriting existing files.");
|
|
7067
7086
|
}
|
|
7068
7087
|
writeFileSync11(commandPath, generateOpencodeHandoffCommand(spec));
|
|
@@ -7078,15 +7097,15 @@ async function installPluginIntegration(_agentKey, spec) {
|
|
|
7078
7097
|
async function uninstallPluginIntegration(_agentKey, spec) {
|
|
7079
7098
|
const logs = [];
|
|
7080
7099
|
try {
|
|
7081
|
-
const commandPath =
|
|
7100
|
+
const commandPath = join17(expandPath(spec.commandsPath), spec.handoffCommandFile);
|
|
7082
7101
|
let removedCount = 0;
|
|
7083
|
-
if (
|
|
7102
|
+
if (existsSync17(commandPath)) {
|
|
7084
7103
|
unlinkSync7(commandPath);
|
|
7085
7104
|
logs.push(`Removed ${commandPath}`);
|
|
7086
7105
|
removedCount += 1;
|
|
7087
7106
|
}
|
|
7088
|
-
const pluginPath =
|
|
7089
|
-
if (
|
|
7107
|
+
const pluginPath = join17(expandPath(spec.pluginsPath), spec.pluginFileName);
|
|
7108
|
+
if (existsSync17(pluginPath)) {
|
|
7090
7109
|
unlinkSync7(pluginPath);
|
|
7091
7110
|
logs.push(`Removed ${pluginPath}`);
|
|
7092
7111
|
removedCount += 1;
|
|
@@ -7120,11 +7139,11 @@ function buildHandoffItem(agentKey, spec) {
|
|
|
7120
7139
|
isInstalled() {
|
|
7121
7140
|
if (isHooksIntegrationSpec(spec)) {
|
|
7122
7141
|
const hooksDir = expandPath(spec.hooksDirPath);
|
|
7123
|
-
return
|
|
7142
|
+
return existsSync17(join17(hooksDir, "openacp-inject-session.sh")) && existsSync17(join17(hooksDir, "openacp-handoff.sh"));
|
|
7124
7143
|
}
|
|
7125
|
-
const commandPath =
|
|
7126
|
-
const pluginPath =
|
|
7127
|
-
return
|
|
7144
|
+
const commandPath = join17(expandPath(spec.commandsPath), spec.handoffCommandFile);
|
|
7145
|
+
const pluginPath = join17(expandPath(spec.pluginsPath), spec.pluginFileName);
|
|
7146
|
+
return existsSync17(commandPath) && existsSync17(pluginPath);
|
|
7128
7147
|
},
|
|
7129
7148
|
install: () => installIntegration(agentKey, spec),
|
|
7130
7149
|
uninstall: () => uninstallIntegration(agentKey, spec)
|
|
@@ -7139,20 +7158,20 @@ function buildTunnelItem(spec) {
|
|
|
7139
7158
|
if (!isHooksIntegrationSpec(spec) || !spec.commandsPath) return null;
|
|
7140
7159
|
const hooksSpec = spec;
|
|
7141
7160
|
function getTunnelPath() {
|
|
7142
|
-
return
|
|
7161
|
+
return join17(getSkillBasePath(hooksSpec), "openacp-tunnel", "SKILL.md");
|
|
7143
7162
|
}
|
|
7144
7163
|
return {
|
|
7145
7164
|
id: "tunnel",
|
|
7146
7165
|
name: "Tunnel",
|
|
7147
7166
|
description: "Expose local ports to the internet via OpenACP tunnel",
|
|
7148
7167
|
isInstalled() {
|
|
7149
|
-
return
|
|
7168
|
+
return existsSync17(getTunnelPath());
|
|
7150
7169
|
},
|
|
7151
7170
|
async install() {
|
|
7152
7171
|
const logs = [];
|
|
7153
7172
|
try {
|
|
7154
7173
|
const skillPath = getTunnelPath();
|
|
7155
|
-
mkdirSync11(
|
|
7174
|
+
mkdirSync11(dirname13(skillPath), { recursive: true });
|
|
7156
7175
|
writeFileSync11(skillPath, generateTunnelCommand());
|
|
7157
7176
|
logs.push(`Created ${skillPath}`);
|
|
7158
7177
|
return { success: true, logs };
|
|
@@ -7165,10 +7184,10 @@ function buildTunnelItem(spec) {
|
|
|
7165
7184
|
const logs = [];
|
|
7166
7185
|
try {
|
|
7167
7186
|
const skillPath = getTunnelPath();
|
|
7168
|
-
if (
|
|
7187
|
+
if (existsSync17(skillPath)) {
|
|
7169
7188
|
unlinkSync7(skillPath);
|
|
7170
7189
|
try {
|
|
7171
|
-
rmdirSync(
|
|
7190
|
+
rmdirSync(dirname13(skillPath));
|
|
7172
7191
|
} catch {
|
|
7173
7192
|
}
|
|
7174
7193
|
logs.push(`Removed ${skillPath}`);
|
|
@@ -7467,7 +7486,7 @@ var init_agents2 = __esm({
|
|
|
7467
7486
|
// src/plugins/telegram/commands/resume.ts
|
|
7468
7487
|
function setupResumeCallbacks(_bot, _core, _chatId, _onControlMessage) {
|
|
7469
7488
|
}
|
|
7470
|
-
var
|
|
7489
|
+
var log26;
|
|
7471
7490
|
var init_resume = __esm({
|
|
7472
7491
|
"src/plugins/telegram/commands/resume.ts"() {
|
|
7473
7492
|
"use strict";
|
|
@@ -7477,7 +7496,7 @@ var init_resume = __esm({
|
|
|
7477
7496
|
init_topics();
|
|
7478
7497
|
init_admin();
|
|
7479
7498
|
init_log();
|
|
7480
|
-
|
|
7499
|
+
log26 = createChildLogger({ module: "telegram-cmd-resume" });
|
|
7481
7500
|
}
|
|
7482
7501
|
});
|
|
7483
7502
|
|
|
@@ -7640,7 +7659,7 @@ function setupSettingsCallbacks(bot, core, getAssistantSession) {
|
|
|
7640
7659
|
} catch {
|
|
7641
7660
|
}
|
|
7642
7661
|
} catch (err) {
|
|
7643
|
-
|
|
7662
|
+
log27.error({ err, fieldPath }, "Failed to toggle config");
|
|
7644
7663
|
try {
|
|
7645
7664
|
await ctx.answerCallbackQuery({ text: "\u274C Failed to update" });
|
|
7646
7665
|
} catch {
|
|
@@ -7721,7 +7740,7 @@ Tap to change:`, {
|
|
|
7721
7740
|
} catch {
|
|
7722
7741
|
}
|
|
7723
7742
|
} catch (err) {
|
|
7724
|
-
|
|
7743
|
+
log27.error({ err, fieldPath }, "Failed to set config");
|
|
7725
7744
|
try {
|
|
7726
7745
|
await ctx.answerCallbackQuery({ text: "\u274C Failed to update" });
|
|
7727
7746
|
} catch {
|
|
@@ -7779,13 +7798,13 @@ Tap to change:`, {
|
|
|
7779
7798
|
}
|
|
7780
7799
|
});
|
|
7781
7800
|
}
|
|
7782
|
-
var
|
|
7801
|
+
var log27;
|
|
7783
7802
|
var init_settings = __esm({
|
|
7784
7803
|
"src/plugins/telegram/commands/settings.ts"() {
|
|
7785
7804
|
"use strict";
|
|
7786
7805
|
init_config_registry();
|
|
7787
7806
|
init_log();
|
|
7788
|
-
|
|
7807
|
+
log27 = createChildLogger({ module: "telegram-settings" });
|
|
7789
7808
|
}
|
|
7790
7809
|
});
|
|
7791
7810
|
|
|
@@ -7832,7 +7851,7 @@ async function handleDoctor(ctx) {
|
|
|
7832
7851
|
reply_markup: keyboard
|
|
7833
7852
|
});
|
|
7834
7853
|
} catch (err) {
|
|
7835
|
-
|
|
7854
|
+
log28.error({ err }, "Doctor command failed");
|
|
7836
7855
|
await ctx.api.editMessageText(
|
|
7837
7856
|
ctx.chat.id,
|
|
7838
7857
|
statusMsg.message_id,
|
|
@@ -7881,7 +7900,7 @@ function setupDoctorCallbacks(bot) {
|
|
|
7881
7900
|
}
|
|
7882
7901
|
}
|
|
7883
7902
|
} catch (err) {
|
|
7884
|
-
|
|
7903
|
+
log28.error({ err, index }, "Doctor fix callback failed");
|
|
7885
7904
|
}
|
|
7886
7905
|
});
|
|
7887
7906
|
bot.callbackQuery("m:doctor", async (ctx) => {
|
|
@@ -7892,13 +7911,13 @@ function setupDoctorCallbacks(bot) {
|
|
|
7892
7911
|
await handleDoctor(ctx);
|
|
7893
7912
|
});
|
|
7894
7913
|
}
|
|
7895
|
-
var
|
|
7914
|
+
var log28, pendingFixesStore;
|
|
7896
7915
|
var init_doctor2 = __esm({
|
|
7897
7916
|
"src/plugins/telegram/commands/doctor.ts"() {
|
|
7898
7917
|
"use strict";
|
|
7899
7918
|
init_doctor();
|
|
7900
7919
|
init_log();
|
|
7901
|
-
|
|
7920
|
+
log28 = createChildLogger({ module: "telegram-cmd-doctor" });
|
|
7902
7921
|
pendingFixesStore = /* @__PURE__ */ new Map();
|
|
7903
7922
|
}
|
|
7904
7923
|
});
|
|
@@ -7957,13 +7976,13 @@ function setupTunnelCallbacks(bot, core) {
|
|
|
7957
7976
|
}
|
|
7958
7977
|
});
|
|
7959
7978
|
}
|
|
7960
|
-
var
|
|
7979
|
+
var log29;
|
|
7961
7980
|
var init_tunnel2 = __esm({
|
|
7962
7981
|
"src/plugins/telegram/commands/tunnel.ts"() {
|
|
7963
7982
|
"use strict";
|
|
7964
7983
|
init_formatting();
|
|
7965
7984
|
init_log();
|
|
7966
|
-
|
|
7985
|
+
log29 = createChildLogger({ module: "telegram-cmd-tunnel" });
|
|
7967
7986
|
}
|
|
7968
7987
|
});
|
|
7969
7988
|
|
|
@@ -7977,10 +7996,10 @@ async function executeSwitchAgent(ctx, core, sessionId, agentName) {
|
|
|
7977
7996
|
`Switched to <b>${escapeHtml(agentName)}</b> (${status})`,
|
|
7978
7997
|
{ parse_mode: "HTML" }
|
|
7979
7998
|
);
|
|
7980
|
-
|
|
7999
|
+
log30.info({ sessionId, agentName, resumed }, "Agent switched via /switch");
|
|
7981
8000
|
} catch (err) {
|
|
7982
8001
|
await ctx.reply(`Failed to switch agent: ${escapeHtml(String(err.message || err))}`);
|
|
7983
|
-
|
|
8002
|
+
log30.warn({ sessionId, agentName, err: err.message }, "Agent switch failed");
|
|
7984
8003
|
}
|
|
7985
8004
|
}
|
|
7986
8005
|
function setupSwitchCallbacks(bot, core) {
|
|
@@ -8025,13 +8044,13 @@ Switch to <b>${escapeHtml(agentName)}</b> anyway?`,
|
|
|
8025
8044
|
await executeSwitchAgent(ctx, core, session.id, data);
|
|
8026
8045
|
});
|
|
8027
8046
|
}
|
|
8028
|
-
var
|
|
8047
|
+
var log30;
|
|
8029
8048
|
var init_switch = __esm({
|
|
8030
8049
|
"src/plugins/telegram/commands/switch.ts"() {
|
|
8031
8050
|
"use strict";
|
|
8032
8051
|
init_formatting();
|
|
8033
8052
|
init_log();
|
|
8034
|
-
|
|
8053
|
+
log30 = createChildLogger({ module: "telegram-cmd-switch" });
|
|
8035
8054
|
}
|
|
8036
8055
|
});
|
|
8037
8056
|
|
|
@@ -8229,7 +8248,7 @@ function setupAllCallbacks(bot, core, chatId, systemTopicIds, getAssistantSessio
|
|
|
8229
8248
|
core,
|
|
8230
8249
|
chatId,
|
|
8231
8250
|
agentKey,
|
|
8232
|
-
core.configManager.
|
|
8251
|
+
core.configManager.resolveWorkspace()
|
|
8233
8252
|
);
|
|
8234
8253
|
});
|
|
8235
8254
|
setupNewSessionCallbacks(bot, core, chatId);
|
|
@@ -8244,7 +8263,7 @@ function setupAllCallbacks(bot, core, chatId, systemTopicIds, getAssistantSessio
|
|
|
8244
8263
|
if (!menuRegistry) return;
|
|
8245
8264
|
const item = menuRegistry.getItem(itemId);
|
|
8246
8265
|
if (!item) {
|
|
8247
|
-
|
|
8266
|
+
log31.warn({ itemId }, "Menu item not found in registry");
|
|
8248
8267
|
return;
|
|
8249
8268
|
}
|
|
8250
8269
|
const topicId = ctx.callbackQuery.message?.message_thread_id;
|
|
@@ -8330,7 +8349,7 @@ ${lines}`, { parse_mode: "HTML" }).catch(() => {
|
|
|
8330
8349
|
}
|
|
8331
8350
|
});
|
|
8332
8351
|
}
|
|
8333
|
-
var
|
|
8352
|
+
var log31, STATIC_COMMANDS;
|
|
8334
8353
|
var init_commands = __esm({
|
|
8335
8354
|
"src/plugins/telegram/commands/index.ts"() {
|
|
8336
8355
|
"use strict";
|
|
@@ -8355,7 +8374,7 @@ var init_commands = __esm({
|
|
|
8355
8374
|
init_settings();
|
|
8356
8375
|
init_doctor2();
|
|
8357
8376
|
init_resume();
|
|
8358
|
-
|
|
8377
|
+
log31 = createChildLogger({ module: "telegram-menu-callbacks" });
|
|
8359
8378
|
STATIC_COMMANDS = [
|
|
8360
8379
|
{ command: "new", description: "Create new session" },
|
|
8361
8380
|
{ command: "newchat", description: "New chat, same agent & workspace" },
|
|
@@ -8390,14 +8409,14 @@ var init_commands = __esm({
|
|
|
8390
8409
|
// src/plugins/telegram/permissions.ts
|
|
8391
8410
|
import { InlineKeyboard as InlineKeyboard11 } from "grammy";
|
|
8392
8411
|
import { nanoid as nanoid4 } from "nanoid";
|
|
8393
|
-
var
|
|
8412
|
+
var log32, PermissionHandler;
|
|
8394
8413
|
var init_permissions = __esm({
|
|
8395
8414
|
"src/plugins/telegram/permissions.ts"() {
|
|
8396
8415
|
"use strict";
|
|
8397
8416
|
init_formatting();
|
|
8398
8417
|
init_topics();
|
|
8399
8418
|
init_log();
|
|
8400
|
-
|
|
8419
|
+
log32 = createChildLogger({ module: "telegram-permissions" });
|
|
8401
8420
|
PermissionHandler = class {
|
|
8402
8421
|
constructor(bot, chatId, getSession, sendNotification) {
|
|
8403
8422
|
this.bot = bot;
|
|
@@ -8457,7 +8476,7 @@ ${escapeHtml(request.description)}`,
|
|
|
8457
8476
|
}
|
|
8458
8477
|
const session = this.getSession(pending.sessionId);
|
|
8459
8478
|
const isAllow = pending.options.find((o) => o.id === optionId)?.isAllow ?? false;
|
|
8460
|
-
|
|
8479
|
+
log32.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
|
|
8461
8480
|
if (session?.permissionGate.requestId === pending.requestId) {
|
|
8462
8481
|
session.permissionGate.resolve(optionId);
|
|
8463
8482
|
}
|
|
@@ -8477,7 +8496,7 @@ ${escapeHtml(request.description)}`,
|
|
|
8477
8496
|
});
|
|
8478
8497
|
|
|
8479
8498
|
// src/plugins/telegram/activity.ts
|
|
8480
|
-
var
|
|
8499
|
+
var log33, THINKING_REFRESH_MS, THINKING_MAX_MS, ThinkingIndicator, ToolCard, ActivityTracker2;
|
|
8481
8500
|
var init_activity = __esm({
|
|
8482
8501
|
"src/plugins/telegram/activity.ts"() {
|
|
8483
8502
|
"use strict";
|
|
@@ -8487,7 +8506,7 @@ var init_activity = __esm({
|
|
|
8487
8506
|
init_stream_accumulator();
|
|
8488
8507
|
init_stream_accumulator();
|
|
8489
8508
|
init_display_spec_builder();
|
|
8490
|
-
|
|
8509
|
+
log33 = createChildLogger({ module: "telegram:activity" });
|
|
8491
8510
|
THINKING_REFRESH_MS = 15e3;
|
|
8492
8511
|
THINKING_MAX_MS = 3 * 60 * 1e3;
|
|
8493
8512
|
ThinkingIndicator = class {
|
|
@@ -8528,7 +8547,7 @@ var init_activity = __esm({
|
|
|
8528
8547
|
}
|
|
8529
8548
|
}
|
|
8530
8549
|
} catch (err) {
|
|
8531
|
-
|
|
8550
|
+
log33.warn({ err }, "ThinkingIndicator.show() failed");
|
|
8532
8551
|
} finally {
|
|
8533
8552
|
this.sending = false;
|
|
8534
8553
|
}
|
|
@@ -8696,7 +8715,7 @@ var init_activity = __esm({
|
|
|
8696
8715
|
this.tracer?.log("telegram", { action: "telegram:delete:overflow", sessionId: this.sessionId, msgId: staleId });
|
|
8697
8716
|
}
|
|
8698
8717
|
} catch (err) {
|
|
8699
|
-
|
|
8718
|
+
log33.warn({ err }, "[ToolCard] send/edit failed");
|
|
8700
8719
|
}
|
|
8701
8720
|
}
|
|
8702
8721
|
};
|
|
@@ -8800,7 +8819,7 @@ var init_activity = __esm({
|
|
|
8800
8819
|
const entry = this.toolStateMap.merge(id, status, rawInput, content, viewerLinks, diffStats);
|
|
8801
8820
|
if (!existed || !entry) return;
|
|
8802
8821
|
if (viewerLinks || entry.viewerLinks) {
|
|
8803
|
-
|
|
8822
|
+
log33.debug({ toolId: id, status, hasIncomingLinks: !!viewerLinks, hasEntryLinks: !!entry.viewerLinks, entryLinks: entry.viewerLinks }, "toolUpdate: viewer links trace");
|
|
8804
8823
|
}
|
|
8805
8824
|
const spec = this.specBuilder.buildToolSpec(entry, this._outputMode, this.sessionContext);
|
|
8806
8825
|
this.toolCard.updateFromSpec(spec);
|
|
@@ -9119,13 +9138,13 @@ var init_draft_manager = __esm({
|
|
|
9119
9138
|
});
|
|
9120
9139
|
|
|
9121
9140
|
// src/plugins/telegram/skill-command-manager.ts
|
|
9122
|
-
var
|
|
9141
|
+
var log34, SkillCommandManager;
|
|
9123
9142
|
var init_skill_command_manager = __esm({
|
|
9124
9143
|
"src/plugins/telegram/skill-command-manager.ts"() {
|
|
9125
9144
|
"use strict";
|
|
9126
9145
|
init_commands();
|
|
9127
9146
|
init_log();
|
|
9128
|
-
|
|
9147
|
+
log34 = createChildLogger({ module: "skill-commands" });
|
|
9129
9148
|
SkillCommandManager = class {
|
|
9130
9149
|
// sessionId → pinned msgId
|
|
9131
9150
|
constructor(bot, chatId, sendQueue, sessionManager) {
|
|
@@ -9191,7 +9210,7 @@ var init_skill_command_manager = __esm({
|
|
|
9191
9210
|
disable_notification: true
|
|
9192
9211
|
});
|
|
9193
9212
|
} catch (err) {
|
|
9194
|
-
|
|
9213
|
+
log34.error({ err, sessionId }, "Failed to send skill commands");
|
|
9195
9214
|
}
|
|
9196
9215
|
}
|
|
9197
9216
|
async cleanup(sessionId) {
|
|
@@ -9370,7 +9389,7 @@ async function validateBotAdmin(token, chatId) {
|
|
|
9370
9389
|
};
|
|
9371
9390
|
}
|
|
9372
9391
|
const { status } = data.result;
|
|
9373
|
-
|
|
9392
|
+
log35.info(
|
|
9374
9393
|
{ status, can_manage_topics: data.result.can_manage_topics, raw_result: data.result },
|
|
9375
9394
|
"validateBotAdmin: getChatMember raw result"
|
|
9376
9395
|
);
|
|
@@ -9390,7 +9409,7 @@ async function validateBotAdmin(token, chatId) {
|
|
|
9390
9409
|
}
|
|
9391
9410
|
async function checkTopicsPrerequisites(token, chatId) {
|
|
9392
9411
|
const issues = [];
|
|
9393
|
-
|
|
9412
|
+
log35.info({ chatId }, "checkTopicsPrerequisites: starting checks");
|
|
9394
9413
|
try {
|
|
9395
9414
|
const res = await fetch(`https://api.telegram.org/bot${token}/getChat`, {
|
|
9396
9415
|
method: "POST",
|
|
@@ -9398,7 +9417,7 @@ async function checkTopicsPrerequisites(token, chatId) {
|
|
|
9398
9417
|
body: JSON.stringify({ chat_id: chatId })
|
|
9399
9418
|
});
|
|
9400
9419
|
const data = await res.json();
|
|
9401
|
-
|
|
9420
|
+
log35.info(
|
|
9402
9421
|
{ chatId, apiOk: data.ok, is_forum: data.result?.is_forum, type: data.result?.type, title: data.result?.title },
|
|
9403
9422
|
"checkTopicsPrerequisites: getChat result"
|
|
9404
9423
|
);
|
|
@@ -9408,11 +9427,11 @@ async function checkTopicsPrerequisites(token, chatId) {
|
|
|
9408
9427
|
);
|
|
9409
9428
|
}
|
|
9410
9429
|
} catch (err) {
|
|
9411
|
-
|
|
9430
|
+
log35.warn({ err, chatId }, "checkTopicsPrerequisites: getChat failed (network error)");
|
|
9412
9431
|
issues.push("\u274C Could not check if Topics are enabled (network error).");
|
|
9413
9432
|
}
|
|
9414
9433
|
const adminResult = await validateBotAdmin(token, chatId);
|
|
9415
|
-
|
|
9434
|
+
log35.info(
|
|
9416
9435
|
{ chatId, adminOk: adminResult.ok, canManageTopics: adminResult.ok ? adminResult.canManageTopics : void 0, error: !adminResult.ok ? adminResult.error : void 0 },
|
|
9417
9436
|
"checkTopicsPrerequisites: validateBotAdmin result"
|
|
9418
9437
|
);
|
|
@@ -9426,16 +9445,16 @@ async function checkTopicsPrerequisites(token, chatId) {
|
|
|
9426
9445
|
'\u274C Bot cannot manage topics.\n\u2192 In Admin settings, enable the "Manage Topics" permission'
|
|
9427
9446
|
);
|
|
9428
9447
|
}
|
|
9429
|
-
|
|
9448
|
+
log35.info({ chatId, issueCount: issues.length, ok: issues.length === 0 }, "checkTopicsPrerequisites: result");
|
|
9430
9449
|
if (issues.length > 0) return { ok: false, issues };
|
|
9431
9450
|
return { ok: true };
|
|
9432
9451
|
}
|
|
9433
|
-
var
|
|
9452
|
+
var log35;
|
|
9434
9453
|
var init_validators = __esm({
|
|
9435
9454
|
"src/plugins/telegram/validators.ts"() {
|
|
9436
9455
|
"use strict";
|
|
9437
9456
|
init_log();
|
|
9438
|
-
|
|
9457
|
+
log35 = createChildLogger({ module: "telegram-validators" });
|
|
9439
9458
|
}
|
|
9440
9459
|
});
|
|
9441
9460
|
|
|
@@ -9454,7 +9473,7 @@ function patchedFetch(input2, init) {
|
|
|
9454
9473
|
}
|
|
9455
9474
|
return fetch(input2, init);
|
|
9456
9475
|
}
|
|
9457
|
-
var
|
|
9476
|
+
var log36, TelegramAdapter;
|
|
9458
9477
|
var init_adapter = __esm({
|
|
9459
9478
|
"src/plugins/telegram/adapter.ts"() {
|
|
9460
9479
|
"use strict";
|
|
@@ -9474,7 +9493,7 @@ var init_adapter = __esm({
|
|
|
9474
9493
|
init_messaging_adapter();
|
|
9475
9494
|
init_renderer2();
|
|
9476
9495
|
init_output_mode_resolver();
|
|
9477
|
-
|
|
9496
|
+
log36 = createChildLogger({ module: "telegram" });
|
|
9478
9497
|
TelegramAdapter = class extends MessagingAdapter {
|
|
9479
9498
|
name = "telegram";
|
|
9480
9499
|
renderer = new TelegramRenderer();
|
|
@@ -9601,7 +9620,7 @@ var init_adapter = __esm({
|
|
|
9601
9620
|
);
|
|
9602
9621
|
this.bot.catch((err) => {
|
|
9603
9622
|
const rootCause = err.error instanceof Error ? err.error : err;
|
|
9604
|
-
|
|
9623
|
+
log36.error({ err: rootCause }, "Telegram bot error");
|
|
9605
9624
|
});
|
|
9606
9625
|
this.bot.api.config.use(async (prev, method, payload, signal) => {
|
|
9607
9626
|
const maxRetries = 3;
|
|
@@ -9619,7 +9638,7 @@ var init_adapter = __esm({
|
|
|
9619
9638
|
if (rateLimitedMethods.includes(method)) {
|
|
9620
9639
|
this.sendQueue.onRateLimited();
|
|
9621
9640
|
}
|
|
9622
|
-
|
|
9641
|
+
log36.warn(
|
|
9623
9642
|
{ method, retryAfter, attempt: attempt + 1 },
|
|
9624
9643
|
"Rate limited by Telegram, retrying"
|
|
9625
9644
|
);
|
|
@@ -9807,9 +9826,9 @@ ${p}` : p;
|
|
|
9807
9826
|
this.setupRoutes();
|
|
9808
9827
|
this.bot.start({
|
|
9809
9828
|
allowed_updates: ["message", "callback_query"],
|
|
9810
|
-
onStart: () =>
|
|
9829
|
+
onStart: () => log36.info({ chatId: this.telegramConfig.chatId }, "Telegram bot started")
|
|
9811
9830
|
});
|
|
9812
|
-
|
|
9831
|
+
log36.info(
|
|
9813
9832
|
{
|
|
9814
9833
|
chatId: this.telegramConfig.chatId,
|
|
9815
9834
|
notificationTopicId: this.telegramConfig.notificationTopicId,
|
|
@@ -9823,12 +9842,12 @@ ${p}` : p;
|
|
|
9823
9842
|
this.telegramConfig.chatId
|
|
9824
9843
|
);
|
|
9825
9844
|
if (prereqResult.ok) {
|
|
9826
|
-
|
|
9845
|
+
log36.info("Telegram adapter: prerequisites OK, initializing topic-dependent features");
|
|
9827
9846
|
await this.initTopicDependentFeatures();
|
|
9828
9847
|
} else {
|
|
9829
|
-
|
|
9848
|
+
log36.warn({ issues: prereqResult.issues }, "Telegram adapter: prerequisites NOT met, starting watcher");
|
|
9830
9849
|
for (const issue of prereqResult.issues) {
|
|
9831
|
-
|
|
9850
|
+
log36.warn({ issue }, "Telegram prerequisite not met");
|
|
9832
9851
|
}
|
|
9833
9852
|
this.startPrerequisiteWatcher(prereqResult.issues);
|
|
9834
9853
|
}
|
|
@@ -9844,7 +9863,7 @@ ${p}` : p;
|
|
|
9844
9863
|
} catch (err) {
|
|
9845
9864
|
if (attempt === maxRetries) throw err;
|
|
9846
9865
|
const delay = baseDelayMs * Math.pow(2, attempt - 1);
|
|
9847
|
-
|
|
9866
|
+
log36.warn(
|
|
9848
9867
|
{ err, attempt, maxRetries, delayMs: delay, operation: label },
|
|
9849
9868
|
`${label} failed, retrying in ${delay}ms`
|
|
9850
9869
|
);
|
|
@@ -9864,12 +9883,12 @@ ${p}` : p;
|
|
|
9864
9883
|
}),
|
|
9865
9884
|
"setMyCommands"
|
|
9866
9885
|
).catch((err) => {
|
|
9867
|
-
|
|
9886
|
+
log36.warn({ err }, "Failed to register Telegram commands after retries (non-critical)");
|
|
9868
9887
|
});
|
|
9869
9888
|
}
|
|
9870
9889
|
async initTopicDependentFeatures() {
|
|
9871
9890
|
if (this._topicsInitialized) return;
|
|
9872
|
-
|
|
9891
|
+
log36.info(
|
|
9873
9892
|
{ notificationTopicId: this.telegramConfig.notificationTopicId, assistantTopicId: this.telegramConfig.assistantTopicId },
|
|
9874
9893
|
"initTopicDependentFeatures: starting (existing IDs in config)"
|
|
9875
9894
|
);
|
|
@@ -9894,7 +9913,7 @@ ${p}` : p;
|
|
|
9894
9913
|
this.assistantTopicId = topics.assistantTopicId;
|
|
9895
9914
|
this._systemTopicIds.notificationTopicId = topics.notificationTopicId;
|
|
9896
9915
|
this._systemTopicIds.assistantTopicId = topics.assistantTopicId;
|
|
9897
|
-
|
|
9916
|
+
log36.info(
|
|
9898
9917
|
{ notificationTopicId: this.notificationTopicId, assistantTopicId: this.assistantTopicId },
|
|
9899
9918
|
"initTopicDependentFeatures: topics ready"
|
|
9900
9919
|
);
|
|
@@ -9925,7 +9944,7 @@ ${p}` : p;
|
|
|
9925
9944
|
).then((msg) => {
|
|
9926
9945
|
if (msg) this.storeControlMsgId(sessionId, msg.message_id);
|
|
9927
9946
|
}).catch((err) => {
|
|
9928
|
-
|
|
9947
|
+
log36.warn({ err, sessionId }, "Failed to send initial messages for new session");
|
|
9929
9948
|
});
|
|
9930
9949
|
};
|
|
9931
9950
|
this.core.eventBus.on(BusEvent.SESSION_THREAD_READY, this._threadReadyHandler);
|
|
@@ -9934,7 +9953,7 @@ ${p}` : p;
|
|
|
9934
9953
|
});
|
|
9935
9954
|
};
|
|
9936
9955
|
this.core.eventBus.on("session:configChanged", this._configChangedHandler);
|
|
9937
|
-
|
|
9956
|
+
log36.info({ assistantTopicId: this.assistantTopicId }, "initTopicDependentFeatures: sending welcome message");
|
|
9938
9957
|
try {
|
|
9939
9958
|
const config = this.core.configManager.get();
|
|
9940
9959
|
const agents = this.core.agentManager.getAvailableAgents();
|
|
@@ -9956,17 +9975,17 @@ ${p}` : p;
|
|
|
9956
9975
|
this.core.lifecycleManager?.serviceRegistry?.get("menu-registry")
|
|
9957
9976
|
)
|
|
9958
9977
|
});
|
|
9959
|
-
|
|
9978
|
+
log36.info("initTopicDependentFeatures: welcome message sent");
|
|
9960
9979
|
} catch (err) {
|
|
9961
|
-
|
|
9980
|
+
log36.warn({ err }, "Failed to send welcome message");
|
|
9962
9981
|
}
|
|
9963
9982
|
try {
|
|
9964
9983
|
await this.core.assistantManager.getOrSpawn("telegram", String(this.assistantTopicId));
|
|
9965
9984
|
} catch (err) {
|
|
9966
|
-
|
|
9985
|
+
log36.error({ err }, "Failed to spawn assistant");
|
|
9967
9986
|
}
|
|
9968
9987
|
this._topicsInitialized = true;
|
|
9969
|
-
|
|
9988
|
+
log36.info("Telegram adapter fully initialized");
|
|
9970
9989
|
}
|
|
9971
9990
|
startPrerequisiteWatcher(issues) {
|
|
9972
9991
|
const setupMessage = `\u26A0\uFE0F <b>OpenACP needs setup before it can start.</b>
|
|
@@ -9977,7 +9996,7 @@ OpenACP will automatically retry until this is resolved.`;
|
|
|
9977
9996
|
this.bot.api.sendMessage(this.telegramConfig.chatId, setupMessage, {
|
|
9978
9997
|
parse_mode: "HTML"
|
|
9979
9998
|
}).catch((err) => {
|
|
9980
|
-
|
|
9999
|
+
log36.warn({ err }, "Failed to send setup guidance to General topic");
|
|
9981
10000
|
});
|
|
9982
10001
|
const schedule = [5e3, 1e4, 3e4];
|
|
9983
10002
|
let attempt = 1;
|
|
@@ -9990,7 +10009,7 @@ OpenACP will automatically retry until this is resolved.`;
|
|
|
9990
10009
|
);
|
|
9991
10010
|
if (result.ok) {
|
|
9992
10011
|
this._prerequisiteWatcher = null;
|
|
9993
|
-
|
|
10012
|
+
log36.info("Prerequisites met \u2014 completing Telegram adapter initialization");
|
|
9994
10013
|
try {
|
|
9995
10014
|
await this.initTopicDependentFeatures();
|
|
9996
10015
|
await this.bot.api.sendMessage(
|
|
@@ -9999,11 +10018,11 @@ OpenACP will automatically retry until this is resolved.`;
|
|
|
9999
10018
|
{ parse_mode: "HTML" }
|
|
10000
10019
|
);
|
|
10001
10020
|
} catch (err) {
|
|
10002
|
-
|
|
10021
|
+
log36.error({ err }, "Failed to complete initialization after prerequisites met");
|
|
10003
10022
|
}
|
|
10004
10023
|
return;
|
|
10005
10024
|
}
|
|
10006
|
-
|
|
10025
|
+
log36.debug({ issues: result.issues }, "Prerequisites not yet met, retrying");
|
|
10007
10026
|
const delay = schedule[Math.min(attempt, schedule.length - 1)];
|
|
10008
10027
|
attempt++;
|
|
10009
10028
|
this._prerequisiteWatcher = setTimeout(retry, delay);
|
|
@@ -10029,7 +10048,7 @@ OpenACP will automatically retry until this is resolved.`;
|
|
|
10029
10048
|
}
|
|
10030
10049
|
this.sendQueue.clear();
|
|
10031
10050
|
await this.bot.stop();
|
|
10032
|
-
|
|
10051
|
+
log36.info("Telegram bot stopped");
|
|
10033
10052
|
}
|
|
10034
10053
|
// --- CommandRegistry response rendering ---
|
|
10035
10054
|
async renderCommandResponse(response, chatId, topicId) {
|
|
@@ -10153,7 +10172,7 @@ ${lines.join("\n")}`;
|
|
|
10153
10172
|
threadId: String(threadId),
|
|
10154
10173
|
userId: String(ctx.from.id),
|
|
10155
10174
|
text: forwardText
|
|
10156
|
-
}).catch((err) =>
|
|
10175
|
+
}).catch((err) => log36.error({ err }, "handleMessage error"));
|
|
10157
10176
|
});
|
|
10158
10177
|
this.bot.on("message:photo", async (ctx) => {
|
|
10159
10178
|
const threadId = ctx.message.message_thread_id;
|
|
@@ -10242,7 +10261,7 @@ ${lines.join("\n")}`;
|
|
|
10242
10261
|
if (session.archiving) return;
|
|
10243
10262
|
const threadId = Number(session.threadId);
|
|
10244
10263
|
if (!threadId || isNaN(threadId)) {
|
|
10245
|
-
|
|
10264
|
+
log36.warn(
|
|
10246
10265
|
{ sessionId, threadId: session.threadId },
|
|
10247
10266
|
"Session has no valid threadId, skipping message"
|
|
10248
10267
|
);
|
|
@@ -10258,7 +10277,7 @@ ${lines.join("\n")}`;
|
|
|
10258
10277
|
this._sessionThreadIds.delete(sessionId);
|
|
10259
10278
|
}
|
|
10260
10279
|
}).catch((err) => {
|
|
10261
|
-
|
|
10280
|
+
log36.warn({ err, sessionId }, "Dispatch queue error");
|
|
10262
10281
|
});
|
|
10263
10282
|
this._dispatchQueues.set(sessionId, next);
|
|
10264
10283
|
await next;
|
|
@@ -10380,7 +10399,7 @@ ${lines.join("\n")}`;
|
|
|
10380
10399
|
);
|
|
10381
10400
|
usageMsgId = result?.message_id;
|
|
10382
10401
|
} catch (err) {
|
|
10383
|
-
|
|
10402
|
+
log36.warn({ err, sessionId }, "Failed to send usage message");
|
|
10384
10403
|
}
|
|
10385
10404
|
if (this.notificationTopicId && sessionId !== this.core.assistantManager?.get("telegram")?.id) {
|
|
10386
10405
|
const sess = this.core.sessionManager.getSession(sessionId);
|
|
@@ -10408,7 +10427,7 @@ Task completed.
|
|
|
10408
10427
|
if (!content.attachment) return;
|
|
10409
10428
|
const { attachment } = content;
|
|
10410
10429
|
if (attachment.size > 50 * 1024 * 1024) {
|
|
10411
|
-
|
|
10430
|
+
log36.warn(
|
|
10412
10431
|
{
|
|
10413
10432
|
sessionId,
|
|
10414
10433
|
fileName: attachment.fileName,
|
|
@@ -10452,7 +10471,7 @@ Task completed.
|
|
|
10452
10471
|
);
|
|
10453
10472
|
}
|
|
10454
10473
|
} catch (err) {
|
|
10455
|
-
|
|
10474
|
+
log36.error(
|
|
10456
10475
|
{ err, sessionId, fileName: attachment.fileName },
|
|
10457
10476
|
"Failed to send attachment"
|
|
10458
10477
|
);
|
|
@@ -10551,7 +10570,7 @@ Task completed.
|
|
|
10551
10570
|
}
|
|
10552
10571
|
async sendPermissionRequest(sessionId, request) {
|
|
10553
10572
|
this.getTracer(sessionId)?.log("telegram", { action: "permission:send", sessionId, requestId: request.id, description: request.description });
|
|
10554
|
-
|
|
10573
|
+
log36.info({ sessionId, requestId: request.id }, "Permission request sent");
|
|
10555
10574
|
const session = this.core.sessionManager.getSession(sessionId);
|
|
10556
10575
|
if (!session) return;
|
|
10557
10576
|
await this.sendQueue.enqueue(
|
|
@@ -10561,7 +10580,7 @@ Task completed.
|
|
|
10561
10580
|
async sendNotification(notification) {
|
|
10562
10581
|
this.getTracer(notification.sessionId)?.log("telegram", { action: "notification:send", sessionId: notification.sessionId, type: notification.type });
|
|
10563
10582
|
if (notification.sessionId === this.core.assistantManager?.get("telegram")?.id) return;
|
|
10564
|
-
|
|
10583
|
+
log36.info(
|
|
10565
10584
|
{ sessionId: notification.sessionId, type: notification.type },
|
|
10566
10585
|
"Notification sent"
|
|
10567
10586
|
);
|
|
@@ -10600,7 +10619,7 @@ Task completed.
|
|
|
10600
10619
|
}
|
|
10601
10620
|
async createSessionThread(sessionId, name) {
|
|
10602
10621
|
this.getTracer(sessionId)?.log("telegram", { action: "thread:create", sessionId, name });
|
|
10603
|
-
|
|
10622
|
+
log36.info({ sessionId, name }, "Session topic created");
|
|
10604
10623
|
return String(
|
|
10605
10624
|
await createSessionTopic(this.bot, this.telegramConfig.chatId, name)
|
|
10606
10625
|
);
|
|
@@ -10611,7 +10630,7 @@ Task completed.
|
|
|
10611
10630
|
if (!session) return;
|
|
10612
10631
|
const threadId = Number(session.threadId);
|
|
10613
10632
|
if (!threadId) {
|
|
10614
|
-
|
|
10633
|
+
log36.debug({ sessionId, newName }, "Cannot rename thread \u2014 threadId not set yet");
|
|
10615
10634
|
return;
|
|
10616
10635
|
}
|
|
10617
10636
|
await renameSessionTopic(
|
|
@@ -10630,7 +10649,7 @@ Task completed.
|
|
|
10630
10649
|
try {
|
|
10631
10650
|
await this.bot.api.deleteForumTopic(this.telegramConfig.chatId, topicId);
|
|
10632
10651
|
} catch (err) {
|
|
10633
|
-
|
|
10652
|
+
log36.warn(
|
|
10634
10653
|
{ err, sessionId, topicId },
|
|
10635
10654
|
"Failed to delete forum topic (may already be deleted)"
|
|
10636
10655
|
);
|
|
@@ -10674,7 +10693,7 @@ Task completed.
|
|
|
10674
10693
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
10675
10694
|
return { buffer, filePath: file.file_path };
|
|
10676
10695
|
} catch (err) {
|
|
10677
|
-
|
|
10696
|
+
log36.error({ err }, "Failed to download file from Telegram");
|
|
10678
10697
|
return null;
|
|
10679
10698
|
}
|
|
10680
10699
|
}
|
|
@@ -10695,7 +10714,7 @@ Task completed.
|
|
|
10695
10714
|
try {
|
|
10696
10715
|
buffer = await this.fileService.convertOggToWav(buffer);
|
|
10697
10716
|
} catch (err) {
|
|
10698
|
-
|
|
10717
|
+
log36.warn({ err }, "OGG\u2192WAV conversion failed, saving original OGG");
|
|
10699
10718
|
fileName = "voice.ogg";
|
|
10700
10719
|
mimeType = "audio/ogg";
|
|
10701
10720
|
originalFilePath = void 0;
|
|
@@ -10727,7 +10746,7 @@ Task completed.
|
|
|
10727
10746
|
userId: String(userId),
|
|
10728
10747
|
text: text3,
|
|
10729
10748
|
attachments: [att]
|
|
10730
|
-
}).catch((err) =>
|
|
10749
|
+
}).catch((err) => log36.error({ err }, "handleMessage error"));
|
|
10731
10750
|
}
|
|
10732
10751
|
async cleanupSkillCommands(sessionId) {
|
|
10733
10752
|
this._pendingSkillCommands.delete(sessionId);
|
|
@@ -10863,13 +10882,13 @@ init_config();
|
|
|
10863
10882
|
// src/core/agents/agent-instance.ts
|
|
10864
10883
|
import { spawn as spawn2, execFileSync } from "child_process";
|
|
10865
10884
|
import { Transform } from "stream";
|
|
10866
|
-
import
|
|
10867
|
-
import
|
|
10885
|
+
import fs7 from "fs";
|
|
10886
|
+
import path6 from "path";
|
|
10868
10887
|
import { ClientSideConnection, ndJsonStream } from "@agentclientprotocol/sdk";
|
|
10869
10888
|
|
|
10870
10889
|
// src/core/security/path-guard.ts
|
|
10871
|
-
import
|
|
10872
|
-
import
|
|
10890
|
+
import fs4 from "fs";
|
|
10891
|
+
import path4 from "path";
|
|
10873
10892
|
import ignore from "ignore";
|
|
10874
10893
|
var DEFAULT_DENY_PATTERNS = [
|
|
10875
10894
|
".env",
|
|
@@ -10889,15 +10908,15 @@ var PathGuard = class {
|
|
|
10889
10908
|
ig;
|
|
10890
10909
|
constructor(options) {
|
|
10891
10910
|
try {
|
|
10892
|
-
this.cwd =
|
|
10911
|
+
this.cwd = fs4.realpathSync(path4.resolve(options.cwd));
|
|
10893
10912
|
} catch {
|
|
10894
|
-
this.cwd =
|
|
10913
|
+
this.cwd = path4.resolve(options.cwd);
|
|
10895
10914
|
}
|
|
10896
10915
|
this.allowedPaths = options.allowedPaths.map((p) => {
|
|
10897
10916
|
try {
|
|
10898
|
-
return
|
|
10917
|
+
return fs4.realpathSync(path4.resolve(p));
|
|
10899
10918
|
} catch {
|
|
10900
|
-
return
|
|
10919
|
+
return path4.resolve(p);
|
|
10901
10920
|
}
|
|
10902
10921
|
});
|
|
10903
10922
|
this.ig = ignore();
|
|
@@ -10907,19 +10926,19 @@ var PathGuard = class {
|
|
|
10907
10926
|
}
|
|
10908
10927
|
}
|
|
10909
10928
|
validatePath(targetPath, operation) {
|
|
10910
|
-
const resolved =
|
|
10929
|
+
const resolved = path4.resolve(targetPath);
|
|
10911
10930
|
let realPath;
|
|
10912
10931
|
try {
|
|
10913
|
-
realPath =
|
|
10932
|
+
realPath = fs4.realpathSync(resolved);
|
|
10914
10933
|
} catch {
|
|
10915
10934
|
realPath = resolved;
|
|
10916
10935
|
}
|
|
10917
|
-
if (operation === "write" &&
|
|
10936
|
+
if (operation === "write" && path4.basename(realPath) === ".openacpignore") {
|
|
10918
10937
|
return { allowed: false, reason: "Cannot write to .openacpignore" };
|
|
10919
10938
|
}
|
|
10920
|
-
const isWithinCwd = realPath === this.cwd || realPath.startsWith(this.cwd +
|
|
10939
|
+
const isWithinCwd = realPath === this.cwd || realPath.startsWith(this.cwd + path4.sep);
|
|
10921
10940
|
const isWithinAllowed = this.allowedPaths.some(
|
|
10922
|
-
(ap) => realPath === ap || realPath.startsWith(ap +
|
|
10941
|
+
(ap) => realPath === ap || realPath.startsWith(ap + path4.sep)
|
|
10923
10942
|
);
|
|
10924
10943
|
if (!isWithinCwd && !isWithinAllowed) {
|
|
10925
10944
|
return {
|
|
@@ -10928,7 +10947,7 @@ var PathGuard = class {
|
|
|
10928
10947
|
};
|
|
10929
10948
|
}
|
|
10930
10949
|
if (isWithinCwd && !isWithinAllowed) {
|
|
10931
|
-
const relativePath =
|
|
10950
|
+
const relativePath = path4.relative(this.cwd, realPath);
|
|
10932
10951
|
if (relativePath === ".openacpignore") {
|
|
10933
10952
|
return { allowed: true, reason: "" };
|
|
10934
10953
|
}
|
|
@@ -10943,15 +10962,15 @@ var PathGuard = class {
|
|
|
10943
10962
|
}
|
|
10944
10963
|
addAllowedPath(p) {
|
|
10945
10964
|
try {
|
|
10946
|
-
this.allowedPaths.push(
|
|
10965
|
+
this.allowedPaths.push(fs4.realpathSync(path4.resolve(p)));
|
|
10947
10966
|
} catch {
|
|
10948
|
-
this.allowedPaths.push(
|
|
10967
|
+
this.allowedPaths.push(path4.resolve(p));
|
|
10949
10968
|
}
|
|
10950
10969
|
}
|
|
10951
10970
|
static loadIgnoreFile(cwd) {
|
|
10952
|
-
const ignorePath =
|
|
10971
|
+
const ignorePath = path4.join(cwd, ".openacpignore");
|
|
10953
10972
|
try {
|
|
10954
|
-
const content =
|
|
10973
|
+
const content = fs4.readFileSync(ignorePath, "utf-8");
|
|
10955
10974
|
return content.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
10956
10975
|
} catch {
|
|
10957
10976
|
return [];
|
|
@@ -11292,24 +11311,24 @@ var McpManager = class {
|
|
|
11292
11311
|
};
|
|
11293
11312
|
|
|
11294
11313
|
// src/core/utils/debug-tracer.ts
|
|
11295
|
-
import
|
|
11296
|
-
import
|
|
11314
|
+
import fs6 from "fs";
|
|
11315
|
+
import path5 from "path";
|
|
11297
11316
|
var DEBUG_ENABLED = process.env.OPENACP_DEBUG === "true" || process.env.OPENACP_DEBUG === "1";
|
|
11298
11317
|
var DebugTracer = class {
|
|
11299
11318
|
constructor(sessionId, workingDirectory) {
|
|
11300
11319
|
this.sessionId = sessionId;
|
|
11301
11320
|
this.workingDirectory = workingDirectory;
|
|
11302
|
-
this.logDir =
|
|
11321
|
+
this.logDir = path5.join(workingDirectory, ".log");
|
|
11303
11322
|
}
|
|
11304
11323
|
dirCreated = false;
|
|
11305
11324
|
logDir;
|
|
11306
11325
|
log(layer, data) {
|
|
11307
11326
|
try {
|
|
11308
11327
|
if (!this.dirCreated) {
|
|
11309
|
-
|
|
11328
|
+
fs6.mkdirSync(this.logDir, { recursive: true });
|
|
11310
11329
|
this.dirCreated = true;
|
|
11311
11330
|
}
|
|
11312
|
-
const filePath =
|
|
11331
|
+
const filePath = path5.join(this.logDir, `${this.sessionId}_${layer}.jsonl`);
|
|
11313
11332
|
const seen = /* @__PURE__ */ new WeakSet();
|
|
11314
11333
|
const line = JSON.stringify({ ts: Date.now(), ...data }, (_key, value) => {
|
|
11315
11334
|
if (typeof value === "object" && value !== null) {
|
|
@@ -11318,7 +11337,7 @@ var DebugTracer = class {
|
|
|
11318
11337
|
}
|
|
11319
11338
|
return value;
|
|
11320
11339
|
}) + "\n";
|
|
11321
|
-
|
|
11340
|
+
fs6.appendFileSync(filePath, line);
|
|
11322
11341
|
} catch {
|
|
11323
11342
|
}
|
|
11324
11343
|
}
|
|
@@ -11337,11 +11356,11 @@ init_events();
|
|
|
11337
11356
|
var log4 = createChildLogger({ module: "agent-instance" });
|
|
11338
11357
|
function findPackageRoot(startDir) {
|
|
11339
11358
|
let dir = startDir;
|
|
11340
|
-
while (dir !==
|
|
11341
|
-
if (
|
|
11359
|
+
while (dir !== path6.dirname(dir)) {
|
|
11360
|
+
if (fs7.existsSync(path6.join(dir, "package.json"))) {
|
|
11342
11361
|
return dir;
|
|
11343
11362
|
}
|
|
11344
|
-
dir =
|
|
11363
|
+
dir = path6.dirname(dir);
|
|
11345
11364
|
}
|
|
11346
11365
|
return startDir;
|
|
11347
11366
|
}
|
|
@@ -11353,26 +11372,26 @@ function resolveAgentCommand(cmd) {
|
|
|
11353
11372
|
}
|
|
11354
11373
|
for (const root of searchRoots) {
|
|
11355
11374
|
const packageDirs = [
|
|
11356
|
-
|
|
11357
|
-
|
|
11375
|
+
path6.resolve(root, "node_modules", "@zed-industries", cmd, "dist", "index.js"),
|
|
11376
|
+
path6.resolve(root, "node_modules", cmd, "dist", "index.js")
|
|
11358
11377
|
];
|
|
11359
11378
|
for (const jsPath of packageDirs) {
|
|
11360
|
-
if (
|
|
11379
|
+
if (fs7.existsSync(jsPath)) {
|
|
11361
11380
|
return { command: process.execPath, args: [jsPath] };
|
|
11362
11381
|
}
|
|
11363
11382
|
}
|
|
11364
11383
|
}
|
|
11365
11384
|
for (const root of searchRoots) {
|
|
11366
|
-
const localBin =
|
|
11367
|
-
if (
|
|
11368
|
-
const content =
|
|
11385
|
+
const localBin = path6.resolve(root, "node_modules", ".bin", cmd);
|
|
11386
|
+
if (fs7.existsSync(localBin)) {
|
|
11387
|
+
const content = fs7.readFileSync(localBin, "utf-8");
|
|
11369
11388
|
if (content.startsWith("#!/usr/bin/env node")) {
|
|
11370
11389
|
return { command: process.execPath, args: [localBin] };
|
|
11371
11390
|
}
|
|
11372
11391
|
const match = content.match(/"([^"]+\.js)"/);
|
|
11373
11392
|
if (match) {
|
|
11374
|
-
const target =
|
|
11375
|
-
if (
|
|
11393
|
+
const target = path6.resolve(path6.dirname(localBin), match[1]);
|
|
11394
|
+
if (fs7.existsSync(target)) {
|
|
11376
11395
|
return { command: process.execPath, args: [target] };
|
|
11377
11396
|
}
|
|
11378
11397
|
}
|
|
@@ -11382,7 +11401,7 @@ function resolveAgentCommand(cmd) {
|
|
|
11382
11401
|
const fullPath = execFileSync("which", [cmd], { encoding: "utf-8" }).trim();
|
|
11383
11402
|
if (fullPath) {
|
|
11384
11403
|
try {
|
|
11385
|
-
const content =
|
|
11404
|
+
const content = fs7.readFileSync(fullPath, "utf-8");
|
|
11386
11405
|
if (content.startsWith("#!/usr/bin/env node")) {
|
|
11387
11406
|
return { command: process.execPath, args: [fullPath] };
|
|
11388
11407
|
}
|
|
@@ -11401,16 +11420,16 @@ function resolveAgentCommand(cmd) {
|
|
|
11401
11420
|
candidates.push(dir);
|
|
11402
11421
|
}
|
|
11403
11422
|
};
|
|
11404
|
-
addCandidate(
|
|
11423
|
+
addCandidate(path6.dirname(process.execPath));
|
|
11405
11424
|
try {
|
|
11406
|
-
addCandidate(
|
|
11425
|
+
addCandidate(path6.dirname(fs7.realpathSync(process.execPath)));
|
|
11407
11426
|
} catch {
|
|
11408
11427
|
}
|
|
11409
11428
|
addCandidate("/opt/homebrew/bin");
|
|
11410
11429
|
addCandidate("/usr/local/bin");
|
|
11411
11430
|
for (const dir of candidates) {
|
|
11412
|
-
const candidate =
|
|
11413
|
-
if (
|
|
11431
|
+
const candidate = path6.join(dir, cmd);
|
|
11432
|
+
if (fs7.existsSync(candidate)) {
|
|
11414
11433
|
log4.info({ cmd, resolved: candidate }, "Resolved package runner from fallback search");
|
|
11415
11434
|
return { command: candidate, args: [] };
|
|
11416
11435
|
}
|
|
@@ -11577,10 +11596,11 @@ ${stderr}`
|
|
|
11577
11596
|
allowedPaths
|
|
11578
11597
|
);
|
|
11579
11598
|
const resolvedMcp = _AgentInstance.mcpManager.resolve(mcpServers);
|
|
11580
|
-
const response = await
|
|
11581
|
-
cwd: workingDirectory,
|
|
11582
|
-
|
|
11583
|
-
|
|
11599
|
+
const response = await withAgentTimeout(
|
|
11600
|
+
instance.connection.newSession({ cwd: workingDirectory, mcpServers: resolvedMcp }),
|
|
11601
|
+
agentDef.name,
|
|
11602
|
+
"newSession"
|
|
11603
|
+
);
|
|
11584
11604
|
log4.info(response, "newSession response");
|
|
11585
11605
|
instance.sessionId = response.sessionId;
|
|
11586
11606
|
instance.initialSessionResponse = response;
|
|
@@ -11608,11 +11628,11 @@ ${stderr}`
|
|
|
11608
11628
|
const resolvedMcp = _AgentInstance.mcpManager.resolve(mcpServers);
|
|
11609
11629
|
try {
|
|
11610
11630
|
if (instance.agentCapabilities?.loadSession) {
|
|
11611
|
-
const response = await
|
|
11612
|
-
sessionId: agentSessionId,
|
|
11613
|
-
|
|
11614
|
-
|
|
11615
|
-
|
|
11631
|
+
const response = await withAgentTimeout(
|
|
11632
|
+
instance.connection.loadSession({ sessionId: agentSessionId, cwd: workingDirectory, mcpServers: resolvedMcp }),
|
|
11633
|
+
agentDef.name,
|
|
11634
|
+
"loadSession"
|
|
11635
|
+
);
|
|
11616
11636
|
instance.sessionId = agentSessionId;
|
|
11617
11637
|
instance.initialSessionResponse = response;
|
|
11618
11638
|
instance.debugTracer = createDebugTracer(agentSessionId, workingDirectory);
|
|
@@ -11625,10 +11645,11 @@ ${stderr}`
|
|
|
11625
11645
|
"Agent load complete"
|
|
11626
11646
|
);
|
|
11627
11647
|
} else {
|
|
11628
|
-
const response = await
|
|
11629
|
-
sessionId: agentSessionId,
|
|
11630
|
-
|
|
11631
|
-
|
|
11648
|
+
const response = await withAgentTimeout(
|
|
11649
|
+
instance.connection.unstable_resumeSession({ sessionId: agentSessionId, cwd: workingDirectory }),
|
|
11650
|
+
agentDef.name,
|
|
11651
|
+
"resumeSession"
|
|
11652
|
+
);
|
|
11632
11653
|
instance.sessionId = response.sessionId;
|
|
11633
11654
|
instance.initialSessionResponse = response;
|
|
11634
11655
|
instance.debugTracer = createDebugTracer(response.sessionId, workingDirectory);
|
|
@@ -11646,10 +11667,11 @@ ${stderr}`
|
|
|
11646
11667
|
{ err, agentSessionId },
|
|
11647
11668
|
"Resume failed, falling back to new session"
|
|
11648
11669
|
);
|
|
11649
|
-
const response = await
|
|
11650
|
-
cwd: workingDirectory,
|
|
11651
|
-
|
|
11652
|
-
|
|
11670
|
+
const response = await withAgentTimeout(
|
|
11671
|
+
instance.connection.newSession({ cwd: workingDirectory, mcpServers: resolvedMcp }),
|
|
11672
|
+
agentDef.name,
|
|
11673
|
+
"newSession (fallback)"
|
|
11674
|
+
);
|
|
11653
11675
|
instance.sessionId = response.sessionId;
|
|
11654
11676
|
instance.initialSessionResponse = response;
|
|
11655
11677
|
instance.debugTracer = createDebugTracer(response.sessionId, workingDirectory);
|
|
@@ -11841,8 +11863,8 @@ ${stderr}`
|
|
|
11841
11863
|
writePath = result.path;
|
|
11842
11864
|
writeContent = result.content;
|
|
11843
11865
|
}
|
|
11844
|
-
await
|
|
11845
|
-
await
|
|
11866
|
+
await fs7.promises.mkdir(path6.dirname(writePath), { recursive: true });
|
|
11867
|
+
await fs7.promises.writeFile(writePath, writeContent, "utf-8");
|
|
11846
11868
|
return {};
|
|
11847
11869
|
},
|
|
11848
11870
|
// ── Terminal operations (delegated to TerminalManager) ─────────────
|
|
@@ -11950,7 +11972,7 @@ ${skipNote}`;
|
|
|
11950
11972
|
[Attachment access denied: ${attCheck.reason}]`;
|
|
11951
11973
|
continue;
|
|
11952
11974
|
}
|
|
11953
|
-
const data = await
|
|
11975
|
+
const data = await fs7.promises.readFile(att.filePath);
|
|
11954
11976
|
contentBlocks.push({ type: "image", data: data.toString("base64"), mimeType: att.mimeType });
|
|
11955
11977
|
} else if (att.type === "audio" && capabilities.audio) {
|
|
11956
11978
|
const attCheck = this.pathGuard.validatePath(att.filePath, "read");
|
|
@@ -11960,7 +11982,7 @@ ${skipNote}`;
|
|
|
11960
11982
|
[Attachment access denied: ${attCheck.reason}]`;
|
|
11961
11983
|
continue;
|
|
11962
11984
|
}
|
|
11963
|
-
const data = await
|
|
11985
|
+
const data = await fs7.promises.readFile(att.filePath);
|
|
11964
11986
|
contentBlocks.push({ type: "audio", data: data.toString("base64"), mimeType: att.mimeType });
|
|
11965
11987
|
} else {
|
|
11966
11988
|
if (att.type === "image" || att.type === "audio") {
|
|
@@ -12003,6 +12025,26 @@ ${skipNote}`;
|
|
|
12003
12025
|
});
|
|
12004
12026
|
}
|
|
12005
12027
|
};
|
|
12028
|
+
var AGENT_INIT_TIMEOUT_MS = 3e4;
|
|
12029
|
+
function withAgentTimeout(promise, agentName, op) {
|
|
12030
|
+
return new Promise((resolve7, reject) => {
|
|
12031
|
+
const timer = setTimeout(() => {
|
|
12032
|
+
reject(new Error(
|
|
12033
|
+
`Agent "${agentName}" did not respond to ${op} within ${AGENT_INIT_TIMEOUT_MS / 1e3}s. The agent process may have hung during initialization.`
|
|
12034
|
+
));
|
|
12035
|
+
}, AGENT_INIT_TIMEOUT_MS);
|
|
12036
|
+
promise.then(
|
|
12037
|
+
(val) => {
|
|
12038
|
+
clearTimeout(timer);
|
|
12039
|
+
resolve7(val);
|
|
12040
|
+
},
|
|
12041
|
+
(err) => {
|
|
12042
|
+
clearTimeout(timer);
|
|
12043
|
+
reject(err);
|
|
12044
|
+
}
|
|
12045
|
+
);
|
|
12046
|
+
});
|
|
12047
|
+
}
|
|
12006
12048
|
|
|
12007
12049
|
// src/core/agents/agent-manager.ts
|
|
12008
12050
|
var AgentManager = class {
|
|
@@ -12174,7 +12216,7 @@ var PermissionGate = class {
|
|
|
12174
12216
|
|
|
12175
12217
|
// src/core/sessions/session.ts
|
|
12176
12218
|
init_log();
|
|
12177
|
-
import * as
|
|
12219
|
+
import * as fs8 from "fs";
|
|
12178
12220
|
|
|
12179
12221
|
// src/core/sessions/turn-context.ts
|
|
12180
12222
|
import { nanoid } from "nanoid";
|
|
@@ -12500,7 +12542,7 @@ ${text3}`;
|
|
|
12500
12542
|
try {
|
|
12501
12543
|
const audioPath = att.originalFilePath || att.filePath;
|
|
12502
12544
|
const audioMime = att.originalFilePath ? "audio/ogg" : att.mimeType;
|
|
12503
|
-
const audioBuffer = await
|
|
12545
|
+
const audioBuffer = await fs8.promises.readFile(audioPath);
|
|
12504
12546
|
const result = await this.speechService.transcribe(audioBuffer, audioMime);
|
|
12505
12547
|
this.log.info({ provider: "stt", duration: result.duration }, "Voice transcribed");
|
|
12506
12548
|
this.emit(SessionEv.AGENT_EVENT, {
|
|
@@ -12731,7 +12773,7 @@ ${result.text}` : result.text;
|
|
|
12731
12773
|
};
|
|
12732
12774
|
|
|
12733
12775
|
// src/core/message-transformer.ts
|
|
12734
|
-
import * as
|
|
12776
|
+
import * as path7 from "path";
|
|
12735
12777
|
|
|
12736
12778
|
// src/core/utils/extract-file-info.ts
|
|
12737
12779
|
init_apply_patch_detection();
|
|
@@ -13185,7 +13227,7 @@ var MessageTransformer = class {
|
|
|
13185
13227
|
);
|
|
13186
13228
|
return;
|
|
13187
13229
|
}
|
|
13188
|
-
const fileExt =
|
|
13230
|
+
const fileExt = path7.extname(fileInfo.filePath).toLowerCase();
|
|
13189
13231
|
if (BINARY_VIEWER_EXTENSIONS.has(fileExt)) {
|
|
13190
13232
|
log5.debug({ kind, filePath: fileInfo.filePath }, "enrichWithViewerLinks: skipping binary file");
|
|
13191
13233
|
return;
|
|
@@ -13694,12 +13736,12 @@ var SessionBridge = class {
|
|
|
13694
13736
|
break;
|
|
13695
13737
|
case "image_content": {
|
|
13696
13738
|
if (this.deps.fileService) {
|
|
13697
|
-
const
|
|
13739
|
+
const fs35 = this.deps.fileService;
|
|
13698
13740
|
const sid = this.session.id;
|
|
13699
13741
|
const { data, mimeType } = event;
|
|
13700
13742
|
const buffer = Buffer.from(data, "base64");
|
|
13701
|
-
const ext =
|
|
13702
|
-
|
|
13743
|
+
const ext = fs35.extensionFromMime(mimeType);
|
|
13744
|
+
fs35.saveFile(sid, `agent-image${ext}`, buffer, mimeType).then((att) => {
|
|
13703
13745
|
this.sendMessage(sid, {
|
|
13704
13746
|
type: "attachment",
|
|
13705
13747
|
text: "",
|
|
@@ -13711,12 +13753,12 @@ var SessionBridge = class {
|
|
|
13711
13753
|
}
|
|
13712
13754
|
case "audio_content": {
|
|
13713
13755
|
if (this.deps.fileService) {
|
|
13714
|
-
const
|
|
13756
|
+
const fs35 = this.deps.fileService;
|
|
13715
13757
|
const sid = this.session.id;
|
|
13716
13758
|
const { data, mimeType } = event;
|
|
13717
13759
|
const buffer = Buffer.from(data, "base64");
|
|
13718
|
-
const ext =
|
|
13719
|
-
|
|
13760
|
+
const ext = fs35.extensionFromMime(mimeType);
|
|
13761
|
+
fs35.saveFile(sid, `agent-audio${ext}`, buffer, mimeType).then((att) => {
|
|
13720
13762
|
this.sendMessage(sid, {
|
|
13721
13763
|
type: "attachment",
|
|
13722
13764
|
text: "",
|
|
@@ -14268,14 +14310,13 @@ var SessionFactory = class {
|
|
|
14268
14310
|
};
|
|
14269
14311
|
|
|
14270
14312
|
// src/core/core.ts
|
|
14271
|
-
import
|
|
14272
|
-
import os8 from "os";
|
|
14313
|
+
import path14 from "path";
|
|
14273
14314
|
import { nanoid as nanoid3 } from "nanoid";
|
|
14274
14315
|
|
|
14275
14316
|
// src/core/sessions/session-store.ts
|
|
14276
14317
|
init_log();
|
|
14277
|
-
import
|
|
14278
|
-
import
|
|
14318
|
+
import fs9 from "fs";
|
|
14319
|
+
import path8 from "path";
|
|
14279
14320
|
var log8 = createChildLogger({ module: "session-store" });
|
|
14280
14321
|
var DEBOUNCE_MS = 2e3;
|
|
14281
14322
|
var JsonFileSessionStore = class {
|
|
@@ -14357,9 +14398,9 @@ var JsonFileSessionStore = class {
|
|
|
14357
14398
|
version: 1,
|
|
14358
14399
|
sessions: Object.fromEntries(this.records)
|
|
14359
14400
|
};
|
|
14360
|
-
const dir =
|
|
14361
|
-
if (!
|
|
14362
|
-
|
|
14401
|
+
const dir = path8.dirname(this.filePath);
|
|
14402
|
+
if (!fs9.existsSync(dir)) fs9.mkdirSync(dir, { recursive: true });
|
|
14403
|
+
fs9.writeFileSync(this.filePath, JSON.stringify(data, null, 2));
|
|
14363
14404
|
}
|
|
14364
14405
|
destroy() {
|
|
14365
14406
|
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
@@ -14372,10 +14413,10 @@ var JsonFileSessionStore = class {
|
|
|
14372
14413
|
}
|
|
14373
14414
|
}
|
|
14374
14415
|
load() {
|
|
14375
|
-
if (!
|
|
14416
|
+
if (!fs9.existsSync(this.filePath)) return;
|
|
14376
14417
|
try {
|
|
14377
14418
|
const raw = JSON.parse(
|
|
14378
|
-
|
|
14419
|
+
fs9.readFileSync(this.filePath, "utf-8")
|
|
14379
14420
|
);
|
|
14380
14421
|
if (raw.version !== 1) {
|
|
14381
14422
|
log8.warn(
|
|
@@ -14391,7 +14432,7 @@ var JsonFileSessionStore = class {
|
|
|
14391
14432
|
} catch (err) {
|
|
14392
14433
|
log8.error({ err }, "Failed to load session store, backing up corrupt file");
|
|
14393
14434
|
try {
|
|
14394
|
-
|
|
14435
|
+
fs9.renameSync(this.filePath, `${this.filePath}.bak`);
|
|
14395
14436
|
} catch {
|
|
14396
14437
|
}
|
|
14397
14438
|
}
|
|
@@ -14625,120 +14666,34 @@ var AgentSwitchHandler = class {
|
|
|
14625
14666
|
}
|
|
14626
14667
|
};
|
|
14627
14668
|
|
|
14628
|
-
// src/core/agents/agent-catalog.ts
|
|
14629
|
-
import * as fs14 from "fs";
|
|
14630
|
-
import * as path13 from "path";
|
|
14631
|
-
import * as os6 from "os";
|
|
14632
|
-
|
|
14633
|
-
// src/core/agents/agent-store.ts
|
|
14634
|
-
init_log();
|
|
14635
|
-
import * as fs12 from "fs";
|
|
14636
|
-
import * as path11 from "path";
|
|
14637
|
-
import * as os4 from "os";
|
|
14638
|
-
import { z as z2 } from "zod";
|
|
14639
|
-
var log10 = createChildLogger({ module: "agent-store" });
|
|
14640
|
-
var InstalledAgentSchema = z2.object({
|
|
14641
|
-
registryId: z2.string().nullable(),
|
|
14642
|
-
name: z2.string(),
|
|
14643
|
-
version: z2.string(),
|
|
14644
|
-
distribution: z2.enum(["npx", "uvx", "binary", "custom"]),
|
|
14645
|
-
command: z2.string(),
|
|
14646
|
-
args: z2.array(z2.string()).default([]),
|
|
14647
|
-
env: z2.record(z2.string(), z2.string()).default({}),
|
|
14648
|
-
workingDirectory: z2.string().optional(),
|
|
14649
|
-
installedAt: z2.string(),
|
|
14650
|
-
binaryPath: z2.string().nullable().default(null)
|
|
14651
|
-
});
|
|
14652
|
-
var AgentStoreSchema = z2.object({
|
|
14653
|
-
version: z2.number().default(1),
|
|
14654
|
-
installed: z2.record(z2.string(), InstalledAgentSchema).default({})
|
|
14655
|
-
});
|
|
14656
|
-
var AgentStore = class {
|
|
14657
|
-
data = { version: 1, installed: {} };
|
|
14658
|
-
filePath;
|
|
14659
|
-
constructor(filePath) {
|
|
14660
|
-
this.filePath = filePath ?? path11.join(os4.homedir(), ".openacp", "agents.json");
|
|
14661
|
-
}
|
|
14662
|
-
load() {
|
|
14663
|
-
if (!fs12.existsSync(this.filePath)) {
|
|
14664
|
-
this.data = { version: 1, installed: {} };
|
|
14665
|
-
return;
|
|
14666
|
-
}
|
|
14667
|
-
try {
|
|
14668
|
-
const raw = JSON.parse(fs12.readFileSync(this.filePath, "utf-8"));
|
|
14669
|
-
const result = AgentStoreSchema.safeParse(raw);
|
|
14670
|
-
if (result.success) {
|
|
14671
|
-
this.data = result.data;
|
|
14672
|
-
} else {
|
|
14673
|
-
log10.warn({ errors: result.error.issues }, "Invalid agents.json, starting fresh");
|
|
14674
|
-
this.data = { version: 1, installed: {} };
|
|
14675
|
-
}
|
|
14676
|
-
} catch (err) {
|
|
14677
|
-
log10.warn({ err }, "Failed to read agents.json, starting fresh");
|
|
14678
|
-
this.data = { version: 1, installed: {} };
|
|
14679
|
-
}
|
|
14680
|
-
}
|
|
14681
|
-
exists() {
|
|
14682
|
-
return fs12.existsSync(this.filePath);
|
|
14683
|
-
}
|
|
14684
|
-
getInstalled() {
|
|
14685
|
-
return this.data.installed;
|
|
14686
|
-
}
|
|
14687
|
-
getAgent(key) {
|
|
14688
|
-
return this.data.installed[key];
|
|
14689
|
-
}
|
|
14690
|
-
addAgent(key, agent) {
|
|
14691
|
-
this.data.installed[key] = agent;
|
|
14692
|
-
this.save();
|
|
14693
|
-
}
|
|
14694
|
-
removeAgent(key) {
|
|
14695
|
-
delete this.data.installed[key];
|
|
14696
|
-
this.save();
|
|
14697
|
-
}
|
|
14698
|
-
hasAgent(key) {
|
|
14699
|
-
return key in this.data.installed;
|
|
14700
|
-
}
|
|
14701
|
-
save() {
|
|
14702
|
-
fs12.mkdirSync(path11.dirname(this.filePath), { recursive: true });
|
|
14703
|
-
const tmpPath = this.filePath + ".tmp";
|
|
14704
|
-
fs12.writeFileSync(tmpPath, JSON.stringify(this.data, null, 2), { mode: 384 });
|
|
14705
|
-
fs12.renameSync(tmpPath, this.filePath);
|
|
14706
|
-
}
|
|
14707
|
-
};
|
|
14708
|
-
|
|
14709
14669
|
// src/core/agents/agent-catalog.ts
|
|
14710
14670
|
init_agent_installer();
|
|
14711
14671
|
init_agent_dependencies();
|
|
14712
14672
|
init_log();
|
|
14713
|
-
|
|
14673
|
+
import * as fs12 from "fs";
|
|
14674
|
+
import * as path11 from "path";
|
|
14675
|
+
var log11 = createChildLogger({ module: "agent-catalog" });
|
|
14714
14676
|
var REGISTRY_URL = "https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json";
|
|
14715
14677
|
var DEFAULT_TTL_HOURS = 24;
|
|
14716
14678
|
var AgentCatalog = class {
|
|
14717
14679
|
store;
|
|
14718
|
-
globalStore = null;
|
|
14719
14680
|
registryAgents = [];
|
|
14720
14681
|
cachePath;
|
|
14721
14682
|
agentsDir;
|
|
14722
14683
|
constructor(store, cachePath, agentsDir) {
|
|
14723
|
-
this.store = store
|
|
14724
|
-
this.cachePath = cachePath
|
|
14684
|
+
this.store = store;
|
|
14685
|
+
this.cachePath = cachePath;
|
|
14725
14686
|
this.agentsDir = agentsDir;
|
|
14726
|
-
const globalPath = path13.join(os6.homedir(), ".openacp", "agents.json");
|
|
14727
|
-
const storePath = this.store.filePath;
|
|
14728
|
-
if (path13.resolve(storePath) !== path13.resolve(globalPath)) {
|
|
14729
|
-
this.globalStore = new AgentStore(globalPath);
|
|
14730
|
-
}
|
|
14731
14687
|
}
|
|
14732
14688
|
load() {
|
|
14733
14689
|
this.store.load();
|
|
14734
|
-
this.globalStore?.load();
|
|
14735
14690
|
this.loadRegistryFromCacheOrSnapshot();
|
|
14736
14691
|
this.enrichInstalledFromRegistry();
|
|
14737
14692
|
}
|
|
14738
14693
|
// --- Registry ---
|
|
14739
14694
|
async fetchRegistry() {
|
|
14740
14695
|
try {
|
|
14741
|
-
|
|
14696
|
+
log11.info("Fetching agent registry from CDN...");
|
|
14742
14697
|
const response = await fetch(REGISTRY_URL);
|
|
14743
14698
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
14744
14699
|
const data = await response.json();
|
|
@@ -14748,11 +14703,11 @@ var AgentCatalog = class {
|
|
|
14748
14703
|
ttlHours: DEFAULT_TTL_HOURS,
|
|
14749
14704
|
data
|
|
14750
14705
|
};
|
|
14751
|
-
|
|
14752
|
-
|
|
14753
|
-
|
|
14706
|
+
fs12.mkdirSync(path11.dirname(this.cachePath), { recursive: true });
|
|
14707
|
+
fs12.writeFileSync(this.cachePath, JSON.stringify(cache, null, 2), { mode: 384 });
|
|
14708
|
+
log11.info({ count: this.registryAgents.length }, "Registry updated");
|
|
14754
14709
|
} catch (err) {
|
|
14755
|
-
|
|
14710
|
+
log11.warn({ err }, "Failed to fetch registry, using cached data");
|
|
14756
14711
|
}
|
|
14757
14712
|
}
|
|
14758
14713
|
async refreshRegistryIfStale() {
|
|
@@ -14771,16 +14726,15 @@ var AgentCatalog = class {
|
|
|
14771
14726
|
if (byId) return byId;
|
|
14772
14727
|
return this.registryAgents.find((a) => getAgentAlias(a.id) === keyOrId);
|
|
14773
14728
|
}
|
|
14774
|
-
// --- Installed
|
|
14729
|
+
// --- Installed ---
|
|
14775
14730
|
getInstalled() {
|
|
14776
|
-
|
|
14777
|
-
return Object.values(merged);
|
|
14731
|
+
return Object.values(this.store.getInstalled());
|
|
14778
14732
|
}
|
|
14779
14733
|
getInstalledEntries() {
|
|
14780
|
-
return
|
|
14734
|
+
return this.store.getInstalled();
|
|
14781
14735
|
}
|
|
14782
14736
|
getInstalledAgent(key) {
|
|
14783
|
-
return this.store.getAgent(key)
|
|
14737
|
+
return this.store.getAgent(key);
|
|
14784
14738
|
}
|
|
14785
14739
|
// --- Discovery ---
|
|
14786
14740
|
getAvailable() {
|
|
@@ -14862,14 +14816,11 @@ var AgentCatalog = class {
|
|
|
14862
14816
|
await uninstallAgent(key, this.store);
|
|
14863
14817
|
return { ok: true };
|
|
14864
14818
|
}
|
|
14865
|
-
if (this.globalStore?.getAgent(key)) {
|
|
14866
|
-
return { ok: false, error: `"${key}" is installed globally. Uninstall it from the global instance instead.` };
|
|
14867
|
-
}
|
|
14868
14819
|
return { ok: false, error: `"${key}" is not installed.` };
|
|
14869
14820
|
}
|
|
14870
14821
|
// --- Resolution (for AgentManager) ---
|
|
14871
14822
|
resolve(key) {
|
|
14872
|
-
const agent = this.store.getAgent(key)
|
|
14823
|
+
const agent = this.store.getAgent(key);
|
|
14873
14824
|
if (!agent) return void 0;
|
|
14874
14825
|
return {
|
|
14875
14826
|
name: key,
|
|
@@ -14908,7 +14859,15 @@ var AgentCatalog = class {
|
|
|
14908
14859
|
const dist = resolveDistribution(regAgent);
|
|
14909
14860
|
if (dist) {
|
|
14910
14861
|
agent.distribution = dist.type;
|
|
14911
|
-
|
|
14862
|
+
if (dist.type === "npx") {
|
|
14863
|
+
agent.command = "npx";
|
|
14864
|
+
agent.args = [stripNpmVersion(dist.package), ...dist.args];
|
|
14865
|
+
updated = true;
|
|
14866
|
+
} else if (dist.type === "uvx") {
|
|
14867
|
+
agent.command = "uvx";
|
|
14868
|
+
agent.args = [stripPythonVersion(dist.package), ...dist.args];
|
|
14869
|
+
updated = true;
|
|
14870
|
+
}
|
|
14912
14871
|
}
|
|
14913
14872
|
}
|
|
14914
14873
|
if (updated) {
|
|
@@ -14917,13 +14876,13 @@ var AgentCatalog = class {
|
|
|
14917
14876
|
}
|
|
14918
14877
|
}
|
|
14919
14878
|
if (changed) {
|
|
14920
|
-
|
|
14879
|
+
log11.info("Enriched installed agents with registry data");
|
|
14921
14880
|
}
|
|
14922
14881
|
}
|
|
14923
14882
|
isCacheStale() {
|
|
14924
|
-
if (!
|
|
14883
|
+
if (!fs12.existsSync(this.cachePath)) return true;
|
|
14925
14884
|
try {
|
|
14926
|
-
const raw = JSON.parse(
|
|
14885
|
+
const raw = JSON.parse(fs12.readFileSync(this.cachePath, "utf-8"));
|
|
14927
14886
|
const fetchedAt = new Date(raw.fetchedAt).getTime();
|
|
14928
14887
|
const ttlMs = (raw.ttlHours ?? DEFAULT_TTL_HOURS) * 60 * 60 * 1e3;
|
|
14929
14888
|
return Date.now() - fetchedAt > ttlMs;
|
|
@@ -14932,37 +14891,128 @@ var AgentCatalog = class {
|
|
|
14932
14891
|
}
|
|
14933
14892
|
}
|
|
14934
14893
|
loadRegistryFromCacheOrSnapshot() {
|
|
14935
|
-
if (
|
|
14894
|
+
if (fs12.existsSync(this.cachePath)) {
|
|
14936
14895
|
try {
|
|
14937
|
-
const raw = JSON.parse(
|
|
14896
|
+
const raw = JSON.parse(fs12.readFileSync(this.cachePath, "utf-8"));
|
|
14938
14897
|
if (raw.data?.agents) {
|
|
14939
14898
|
this.registryAgents = raw.data.agents;
|
|
14940
|
-
|
|
14899
|
+
log11.debug({ count: this.registryAgents.length }, "Loaded registry from cache");
|
|
14941
14900
|
return;
|
|
14942
14901
|
}
|
|
14943
14902
|
} catch {
|
|
14944
|
-
|
|
14903
|
+
log11.warn("Failed to load registry cache");
|
|
14945
14904
|
}
|
|
14946
14905
|
}
|
|
14947
14906
|
try {
|
|
14948
14907
|
const candidates = [
|
|
14949
|
-
|
|
14950
|
-
|
|
14951
|
-
|
|
14908
|
+
path11.join(import.meta.dirname, "data", "registry-snapshot.json"),
|
|
14909
|
+
path11.join(import.meta.dirname, "..", "data", "registry-snapshot.json"),
|
|
14910
|
+
path11.join(import.meta.dirname, "..", "..", "data", "registry-snapshot.json")
|
|
14952
14911
|
];
|
|
14953
14912
|
for (const candidate of candidates) {
|
|
14954
|
-
if (
|
|
14955
|
-
const raw = JSON.parse(
|
|
14913
|
+
if (fs12.existsSync(candidate)) {
|
|
14914
|
+
const raw = JSON.parse(fs12.readFileSync(candidate, "utf-8"));
|
|
14956
14915
|
this.registryAgents = raw.agents ?? [];
|
|
14957
|
-
|
|
14916
|
+
log11.debug({ count: this.registryAgents.length }, "Loaded registry from bundled snapshot");
|
|
14958
14917
|
return;
|
|
14959
14918
|
}
|
|
14960
14919
|
}
|
|
14961
|
-
|
|
14920
|
+
log11.warn("No registry data available (no cache, no snapshot)");
|
|
14962
14921
|
} catch {
|
|
14963
|
-
|
|
14922
|
+
log11.warn("Failed to load bundled registry snapshot");
|
|
14923
|
+
}
|
|
14924
|
+
}
|
|
14925
|
+
};
|
|
14926
|
+
function stripNpmVersion(pkg) {
|
|
14927
|
+
if (pkg.startsWith("@")) {
|
|
14928
|
+
const slashIdx = pkg.indexOf("/");
|
|
14929
|
+
if (slashIdx === -1) return pkg;
|
|
14930
|
+
const versionAt = pkg.indexOf("@", slashIdx + 1);
|
|
14931
|
+
return versionAt === -1 ? pkg : pkg.slice(0, versionAt);
|
|
14932
|
+
}
|
|
14933
|
+
const at = pkg.indexOf("@");
|
|
14934
|
+
return at === -1 ? pkg : pkg.slice(0, at);
|
|
14935
|
+
}
|
|
14936
|
+
function stripPythonVersion(pkg) {
|
|
14937
|
+
const pyMatch = pkg.match(/^([^=@><!]+)/);
|
|
14938
|
+
if (pyMatch && pkg.includes("==")) return pyMatch[1];
|
|
14939
|
+
const at = pkg.indexOf("@");
|
|
14940
|
+
return at === -1 ? pkg : pkg.slice(0, at);
|
|
14941
|
+
}
|
|
14942
|
+
|
|
14943
|
+
// src/core/agents/agent-store.ts
|
|
14944
|
+
init_log();
|
|
14945
|
+
import * as fs13 from "fs";
|
|
14946
|
+
import * as path12 from "path";
|
|
14947
|
+
import { z as z2 } from "zod";
|
|
14948
|
+
var log12 = createChildLogger({ module: "agent-store" });
|
|
14949
|
+
var InstalledAgentSchema = z2.object({
|
|
14950
|
+
registryId: z2.string().nullable(),
|
|
14951
|
+
name: z2.string(),
|
|
14952
|
+
version: z2.string(),
|
|
14953
|
+
distribution: z2.enum(["npx", "uvx", "binary", "custom"]),
|
|
14954
|
+
command: z2.string(),
|
|
14955
|
+
args: z2.array(z2.string()).default([]),
|
|
14956
|
+
env: z2.record(z2.string(), z2.string()).default({}),
|
|
14957
|
+
workingDirectory: z2.string().optional(),
|
|
14958
|
+
installedAt: z2.string(),
|
|
14959
|
+
binaryPath: z2.string().nullable().default(null)
|
|
14960
|
+
});
|
|
14961
|
+
var AgentStoreSchema = z2.object({
|
|
14962
|
+
version: z2.number().default(1),
|
|
14963
|
+
installed: z2.record(z2.string(), InstalledAgentSchema).default({})
|
|
14964
|
+
});
|
|
14965
|
+
var AgentStore = class {
|
|
14966
|
+
data = { version: 1, installed: {} };
|
|
14967
|
+
filePath;
|
|
14968
|
+
constructor(filePath) {
|
|
14969
|
+
this.filePath = filePath;
|
|
14970
|
+
}
|
|
14971
|
+
load() {
|
|
14972
|
+
if (!fs13.existsSync(this.filePath)) {
|
|
14973
|
+
this.data = { version: 1, installed: {} };
|
|
14974
|
+
return;
|
|
14975
|
+
}
|
|
14976
|
+
try {
|
|
14977
|
+
const raw = JSON.parse(fs13.readFileSync(this.filePath, "utf-8"));
|
|
14978
|
+
const result = AgentStoreSchema.safeParse(raw);
|
|
14979
|
+
if (result.success) {
|
|
14980
|
+
this.data = result.data;
|
|
14981
|
+
} else {
|
|
14982
|
+
log12.warn({ errors: result.error.issues }, "Invalid agents.json, starting fresh");
|
|
14983
|
+
this.data = { version: 1, installed: {} };
|
|
14984
|
+
}
|
|
14985
|
+
} catch (err) {
|
|
14986
|
+
log12.warn({ err }, "Failed to read agents.json, starting fresh");
|
|
14987
|
+
this.data = { version: 1, installed: {} };
|
|
14964
14988
|
}
|
|
14965
14989
|
}
|
|
14990
|
+
exists() {
|
|
14991
|
+
return fs13.existsSync(this.filePath);
|
|
14992
|
+
}
|
|
14993
|
+
getInstalled() {
|
|
14994
|
+
return this.data.installed;
|
|
14995
|
+
}
|
|
14996
|
+
getAgent(key) {
|
|
14997
|
+
return this.data.installed[key];
|
|
14998
|
+
}
|
|
14999
|
+
addAgent(key, agent) {
|
|
15000
|
+
this.data.installed[key] = agent;
|
|
15001
|
+
this.save();
|
|
15002
|
+
}
|
|
15003
|
+
removeAgent(key) {
|
|
15004
|
+
delete this.data.installed[key];
|
|
15005
|
+
this.save();
|
|
15006
|
+
}
|
|
15007
|
+
hasAgent(key) {
|
|
15008
|
+
return key in this.data.installed;
|
|
15009
|
+
}
|
|
15010
|
+
save() {
|
|
15011
|
+
fs13.mkdirSync(path12.dirname(this.filePath), { recursive: true });
|
|
15012
|
+
const tmpPath = this.filePath + ".tmp";
|
|
15013
|
+
fs13.writeFileSync(tmpPath, JSON.stringify(this.data, null, 2), { mode: 384 });
|
|
15014
|
+
fs13.renameSync(tmpPath, this.filePath);
|
|
15015
|
+
}
|
|
14966
15016
|
};
|
|
14967
15017
|
|
|
14968
15018
|
// src/core/event-bus.ts
|
|
@@ -14971,7 +15021,7 @@ var EventBus = class extends TypedEmitter {
|
|
|
14971
15021
|
|
|
14972
15022
|
// src/core/plugin/plugin-loader.ts
|
|
14973
15023
|
import { createHash } from "crypto";
|
|
14974
|
-
import { readFileSync as
|
|
15024
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
14975
15025
|
function resolveLoadOrder(plugins) {
|
|
14976
15026
|
const overrideTargets = /* @__PURE__ */ new Set();
|
|
14977
15027
|
for (const p of plugins) {
|
|
@@ -15212,32 +15262,28 @@ var ErrorTracker = class {
|
|
|
15212
15262
|
}
|
|
15213
15263
|
};
|
|
15214
15264
|
|
|
15215
|
-
// src/core/plugin/plugin-context.ts
|
|
15216
|
-
import path15 from "path";
|
|
15217
|
-
import os7 from "os";
|
|
15218
|
-
|
|
15219
15265
|
// src/core/plugin/plugin-storage.ts
|
|
15220
|
-
import
|
|
15221
|
-
import
|
|
15266
|
+
import fs14 from "fs";
|
|
15267
|
+
import path13 from "path";
|
|
15222
15268
|
var PluginStorageImpl = class {
|
|
15223
15269
|
kvPath;
|
|
15224
15270
|
dataDir;
|
|
15225
15271
|
writeChain = Promise.resolve();
|
|
15226
15272
|
constructor(baseDir) {
|
|
15227
|
-
this.dataDir =
|
|
15228
|
-
this.kvPath =
|
|
15229
|
-
|
|
15273
|
+
this.dataDir = path13.join(baseDir, "data");
|
|
15274
|
+
this.kvPath = path13.join(baseDir, "kv.json");
|
|
15275
|
+
fs14.mkdirSync(baseDir, { recursive: true });
|
|
15230
15276
|
}
|
|
15231
15277
|
readKv() {
|
|
15232
15278
|
try {
|
|
15233
|
-
const raw =
|
|
15279
|
+
const raw = fs14.readFileSync(this.kvPath, "utf-8");
|
|
15234
15280
|
return JSON.parse(raw);
|
|
15235
15281
|
} catch {
|
|
15236
15282
|
return {};
|
|
15237
15283
|
}
|
|
15238
15284
|
}
|
|
15239
15285
|
writeKv(data) {
|
|
15240
|
-
|
|
15286
|
+
fs14.writeFileSync(this.kvPath, JSON.stringify(data), "utf-8");
|
|
15241
15287
|
}
|
|
15242
15288
|
async get(key) {
|
|
15243
15289
|
const data = this.readKv();
|
|
@@ -15263,7 +15309,7 @@ var PluginStorageImpl = class {
|
|
|
15263
15309
|
return Object.keys(this.readKv());
|
|
15264
15310
|
}
|
|
15265
15311
|
getDataDir() {
|
|
15266
|
-
|
|
15312
|
+
fs14.mkdirSync(this.dataDir, { recursive: true });
|
|
15267
15313
|
return this.dataDir;
|
|
15268
15314
|
}
|
|
15269
15315
|
};
|
|
@@ -15287,7 +15333,7 @@ function createPluginContext(opts) {
|
|
|
15287
15333
|
config,
|
|
15288
15334
|
core
|
|
15289
15335
|
} = opts;
|
|
15290
|
-
const instanceRoot = opts.instanceRoot
|
|
15336
|
+
const instanceRoot = opts.instanceRoot;
|
|
15291
15337
|
const registeredListeners = [];
|
|
15292
15338
|
const registeredCommands = [];
|
|
15293
15339
|
const registeredMenuItemIds = [];
|
|
@@ -15310,7 +15356,7 @@ function createPluginContext(opts) {
|
|
|
15310
15356
|
}
|
|
15311
15357
|
};
|
|
15312
15358
|
const baseLog = opts.log ?? noopLog;
|
|
15313
|
-
const
|
|
15359
|
+
const log37 = typeof baseLog.child === "function" ? baseLog.child({ plugin: pluginName }) : baseLog;
|
|
15314
15360
|
const storageImpl = new PluginStorageImpl(storagePath);
|
|
15315
15361
|
const storage = {
|
|
15316
15362
|
async get(key) {
|
|
@@ -15337,7 +15383,7 @@ function createPluginContext(opts) {
|
|
|
15337
15383
|
const ctx = {
|
|
15338
15384
|
pluginName,
|
|
15339
15385
|
pluginConfig,
|
|
15340
|
-
log:
|
|
15386
|
+
log: log37,
|
|
15341
15387
|
storage,
|
|
15342
15388
|
on(event, handler) {
|
|
15343
15389
|
requirePermission(permissions, "events:read", "on()");
|
|
@@ -15372,7 +15418,7 @@ function createPluginContext(opts) {
|
|
|
15372
15418
|
const registry = serviceRegistry.get("command-registry");
|
|
15373
15419
|
if (registry && typeof registry.register === "function") {
|
|
15374
15420
|
registry.register(def, pluginName);
|
|
15375
|
-
|
|
15421
|
+
log37.debug(`Command '/${def.name}' registered`);
|
|
15376
15422
|
}
|
|
15377
15423
|
},
|
|
15378
15424
|
async sendMessage(_sessionId, _content) {
|
|
@@ -15415,7 +15461,7 @@ function createPluginContext(opts) {
|
|
|
15415
15461
|
const registry = serviceRegistry.get("field-registry");
|
|
15416
15462
|
if (registry && typeof registry.register === "function") {
|
|
15417
15463
|
registry.register(pluginName, fields);
|
|
15418
|
-
|
|
15464
|
+
log37.debug(`Registered ${fields.length} editable field(s) for ${pluginName}`);
|
|
15419
15465
|
}
|
|
15420
15466
|
},
|
|
15421
15467
|
get sessions() {
|
|
@@ -15798,7 +15844,7 @@ Examples:
|
|
|
15798
15844
|
${baseCmd} api status
|
|
15799
15845
|
${baseCmd} api new claude-code ~/my-project --channel <current_channel>
|
|
15800
15846
|
${baseCmd} api cancel <id>
|
|
15801
|
-
${baseCmd} config set
|
|
15847
|
+
${baseCmd} config set logging.level debug
|
|
15802
15848
|
${baseCmd} agents install gemini
|
|
15803
15849
|
\`\`\`
|
|
15804
15850
|
|
|
@@ -15971,10 +16017,10 @@ function createConfigSection(core) {
|
|
|
15971
16017
|
title: "Configuration",
|
|
15972
16018
|
priority: 30,
|
|
15973
16019
|
buildContext: () => {
|
|
15974
|
-
const
|
|
16020
|
+
const workspace = core.configManager.resolveWorkspace();
|
|
15975
16021
|
const speechSvc = core.lifecycleManager?.serviceRegistry.get("speech");
|
|
15976
16022
|
const sttActive = speechSvc ? speechSvc.isSTTAvailable() : false;
|
|
15977
|
-
return `Workspace base: ${
|
|
16023
|
+
return `Workspace base: ${workspace}
|
|
15978
16024
|
STT: ${sttActive ? "configured \u2705" : "Not configured"}`;
|
|
15979
16025
|
},
|
|
15980
16026
|
commands: [
|
|
@@ -16159,13 +16205,13 @@ var OpenACPCore = class {
|
|
|
16159
16205
|
this.instanceContext = ctx;
|
|
16160
16206
|
const config = configManager.get();
|
|
16161
16207
|
this.agentCatalog = new AgentCatalog(
|
|
16162
|
-
|
|
16163
|
-
ctx
|
|
16164
|
-
ctx
|
|
16208
|
+
new AgentStore(ctx.paths.agents),
|
|
16209
|
+
ctx.paths.registryCache,
|
|
16210
|
+
ctx.paths.agentsDir
|
|
16165
16211
|
);
|
|
16166
16212
|
this.agentCatalog.load();
|
|
16167
16213
|
this.agentManager = new AgentManager(this.agentCatalog);
|
|
16168
|
-
const storePath = ctx
|
|
16214
|
+
const storePath = ctx.paths.sessions;
|
|
16169
16215
|
this.sessionStore = new JsonFileSessionStore(
|
|
16170
16216
|
storePath,
|
|
16171
16217
|
config.sessionStore.ttlDays
|
|
@@ -16179,7 +16225,7 @@ var OpenACPCore = class {
|
|
|
16179
16225
|
this.sessionManager,
|
|
16180
16226
|
() => this.speechService,
|
|
16181
16227
|
this.eventBus,
|
|
16182
|
-
ctx
|
|
16228
|
+
ctx.root
|
|
16183
16229
|
);
|
|
16184
16230
|
this.lifecycleManager = new LifecycleManager({
|
|
16185
16231
|
serviceRegistry: new ServiceRegistry(),
|
|
@@ -16189,8 +16235,8 @@ var OpenACPCore = class {
|
|
|
16189
16235
|
sessions: this.sessionManager,
|
|
16190
16236
|
config: this.configManager,
|
|
16191
16237
|
core: this,
|
|
16192
|
-
storagePath: ctx
|
|
16193
|
-
instanceRoot: ctx
|
|
16238
|
+
storagePath: ctx.paths.pluginsData,
|
|
16239
|
+
instanceRoot: ctx.root,
|
|
16194
16240
|
log: createChildLogger({ module: "plugin" })
|
|
16195
16241
|
});
|
|
16196
16242
|
this.sessionFactory.middlewareChain = this.lifecycleManager.middlewareChain;
|
|
@@ -16254,9 +16300,7 @@ var OpenACPCore = class {
|
|
|
16254
16300
|
}
|
|
16255
16301
|
);
|
|
16256
16302
|
registerCoreMenuItems(this.menuRegistry);
|
|
16257
|
-
|
|
16258
|
-
this.assistantRegistry.setInstanceRoot(path16.dirname(ctx.root));
|
|
16259
|
-
}
|
|
16303
|
+
this.assistantRegistry.setInstanceRoot(path14.dirname(ctx.root));
|
|
16260
16304
|
this.assistantRegistry.register(createSessionsSection(this));
|
|
16261
16305
|
this.assistantRegistry.register(createAgentsSection(this));
|
|
16262
16306
|
this.assistantRegistry.register(createConfigSection(this));
|
|
@@ -16555,8 +16599,8 @@ ${text3}`;
|
|
|
16555
16599
|
message: `Agent '${agentName}' not found`
|
|
16556
16600
|
};
|
|
16557
16601
|
}
|
|
16558
|
-
const { existsSync:
|
|
16559
|
-
if (!
|
|
16602
|
+
const { existsSync: existsSync18 } = await import("fs");
|
|
16603
|
+
if (!existsSync18(cwd)) {
|
|
16560
16604
|
return {
|
|
16561
16605
|
ok: false,
|
|
16562
16606
|
error: "invalid_cwd",
|
|
@@ -16902,19 +16946,30 @@ init_doctor();
|
|
|
16902
16946
|
init_config_registry();
|
|
16903
16947
|
|
|
16904
16948
|
// src/core/config/config-editor.ts
|
|
16905
|
-
import * as
|
|
16949
|
+
import * as path30 from "path";
|
|
16906
16950
|
import * as clack2 from "@clack/prompts";
|
|
16907
16951
|
|
|
16908
16952
|
// src/cli/autostart.ts
|
|
16909
16953
|
init_log();
|
|
16910
16954
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
16911
|
-
import * as
|
|
16912
|
-
import * as
|
|
16913
|
-
import * as
|
|
16955
|
+
import * as fs25 from "fs";
|
|
16956
|
+
import * as path23 from "path";
|
|
16957
|
+
import * as os7 from "os";
|
|
16914
16958
|
var log18 = createChildLogger({ module: "autostart" });
|
|
16915
|
-
var
|
|
16916
|
-
var
|
|
16917
|
-
|
|
16959
|
+
var LEGACY_LAUNCHD_PLIST_PATH = path23.join(os7.homedir(), "Library", "LaunchAgents", "com.openacp.daemon.plist");
|
|
16960
|
+
var LEGACY_SYSTEMD_SERVICE_PATH = path23.join(os7.homedir(), ".config", "systemd", "user", "openacp.service");
|
|
16961
|
+
function getLaunchdLabel(instanceId) {
|
|
16962
|
+
return `com.openacp.daemon.${instanceId}`;
|
|
16963
|
+
}
|
|
16964
|
+
function getLaunchdPlistPath(instanceId) {
|
|
16965
|
+
return path23.join(os7.homedir(), "Library", "LaunchAgents", `${getLaunchdLabel(instanceId)}.plist`);
|
|
16966
|
+
}
|
|
16967
|
+
function getSystemdServiceName(instanceId) {
|
|
16968
|
+
return `openacp-${instanceId}`;
|
|
16969
|
+
}
|
|
16970
|
+
function getSystemdServicePath(instanceId) {
|
|
16971
|
+
return path23.join(os7.homedir(), ".config", "systemd", "user", `${getSystemdServiceName(instanceId)}.service`);
|
|
16972
|
+
}
|
|
16918
16973
|
function isAutoStartSupported() {
|
|
16919
16974
|
return process.platform === "darwin" || process.platform === "linux";
|
|
16920
16975
|
}
|
|
@@ -16925,20 +16980,26 @@ function escapeSystemdValue(str) {
|
|
|
16925
16980
|
const escaped = str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\$/g, "$$$$").replace(/%/g, "%%");
|
|
16926
16981
|
return `"${escaped}"`;
|
|
16927
16982
|
}
|
|
16928
|
-
function generateLaunchdPlist(nodePath, cliPath, logDir2) {
|
|
16929
|
-
const
|
|
16983
|
+
function generateLaunchdPlist(nodePath, cliPath, logDir2, instanceRoot, instanceId) {
|
|
16984
|
+
const label = getLaunchdLabel(instanceId);
|
|
16985
|
+
const logFile = path23.join(logDir2, "openacp.log");
|
|
16930
16986
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
16931
16987
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
16932
16988
|
<plist version="1.0">
|
|
16933
16989
|
<dict>
|
|
16934
16990
|
<key>Label</key>
|
|
16935
|
-
<string>${
|
|
16991
|
+
<string>${label}</string>
|
|
16936
16992
|
<key>ProgramArguments</key>
|
|
16937
16993
|
<array>
|
|
16938
16994
|
<string>${escapeXml(nodePath)}</string>
|
|
16939
16995
|
<string>${escapeXml(cliPath)}</string>
|
|
16940
16996
|
<string>--daemon-child</string>
|
|
16941
16997
|
</array>
|
|
16998
|
+
<key>EnvironmentVariables</key>
|
|
16999
|
+
<dict>
|
|
17000
|
+
<key>OPENACP_INSTANCE_ROOT</key>
|
|
17001
|
+
<string>${escapeXml(instanceRoot)}</string>
|
|
17002
|
+
</dict>
|
|
16942
17003
|
<key>RunAtLoad</key>
|
|
16943
17004
|
<true/>
|
|
16944
17005
|
<key>KeepAlive</key>
|
|
@@ -16954,43 +17015,85 @@ function generateLaunchdPlist(nodePath, cliPath, logDir2) {
|
|
|
16954
17015
|
</plist>
|
|
16955
17016
|
`;
|
|
16956
17017
|
}
|
|
16957
|
-
function generateSystemdUnit(nodePath, cliPath) {
|
|
17018
|
+
function generateSystemdUnit(nodePath, cliPath, instanceRoot, instanceId) {
|
|
17019
|
+
const serviceName = getSystemdServiceName(instanceId);
|
|
16958
17020
|
return `[Unit]
|
|
16959
|
-
Description=OpenACP Daemon
|
|
17021
|
+
Description=OpenACP Daemon (${instanceId})
|
|
16960
17022
|
|
|
16961
17023
|
[Service]
|
|
16962
17024
|
ExecStart=${escapeSystemdValue(nodePath)} ${escapeSystemdValue(cliPath)} --daemon-child
|
|
17025
|
+
Environment=OPENACP_INSTANCE_ROOT=${escapeSystemdValue(instanceRoot)}
|
|
16963
17026
|
Restart=on-failure
|
|
16964
17027
|
|
|
16965
17028
|
[Install]
|
|
16966
17029
|
WantedBy=default.target
|
|
17030
|
+
# Service name: ${serviceName}
|
|
16967
17031
|
`;
|
|
16968
17032
|
}
|
|
16969
|
-
function
|
|
17033
|
+
function migrateLegacy() {
|
|
17034
|
+
if (process.platform === "darwin" && fs25.existsSync(LEGACY_LAUNCHD_PLIST_PATH)) {
|
|
17035
|
+
try {
|
|
17036
|
+
const uid = process.getuid();
|
|
17037
|
+
execFileSync6("launchctl", ["bootout", `gui/${uid}`, "com.openacp.daemon"], { stdio: "pipe" });
|
|
17038
|
+
} catch {
|
|
17039
|
+
}
|
|
17040
|
+
try {
|
|
17041
|
+
fs25.unlinkSync(LEGACY_LAUNCHD_PLIST_PATH);
|
|
17042
|
+
} catch {
|
|
17043
|
+
}
|
|
17044
|
+
log18.info("Removed legacy single-instance LaunchAgent");
|
|
17045
|
+
}
|
|
17046
|
+
if (process.platform === "linux" && fs25.existsSync(LEGACY_SYSTEMD_SERVICE_PATH)) {
|
|
17047
|
+
try {
|
|
17048
|
+
execFileSync6("systemctl", ["--user", "disable", "openacp"], { stdio: "pipe" });
|
|
17049
|
+
} catch {
|
|
17050
|
+
}
|
|
17051
|
+
try {
|
|
17052
|
+
fs25.unlinkSync(LEGACY_SYSTEMD_SERVICE_PATH);
|
|
17053
|
+
} catch {
|
|
17054
|
+
}
|
|
17055
|
+
try {
|
|
17056
|
+
execFileSync6("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
|
|
17057
|
+
} catch {
|
|
17058
|
+
}
|
|
17059
|
+
log18.info("Removed legacy single-instance systemd service");
|
|
17060
|
+
}
|
|
17061
|
+
}
|
|
17062
|
+
function installAutoStart(logDir2, instanceRoot, instanceId) {
|
|
16970
17063
|
if (!isAutoStartSupported()) {
|
|
16971
17064
|
return { success: false, error: "Auto-start not supported on this platform" };
|
|
16972
17065
|
}
|
|
16973
17066
|
const nodePath = process.execPath;
|
|
16974
|
-
const cliPath =
|
|
16975
|
-
const resolvedLogDir = logDir2.startsWith("~") ?
|
|
17067
|
+
const cliPath = path23.resolve(process.argv[1]);
|
|
17068
|
+
const resolvedLogDir = logDir2.startsWith("~") ? path23.join(os7.homedir(), logDir2.slice(1)) : logDir2;
|
|
16976
17069
|
try {
|
|
17070
|
+
migrateLegacy();
|
|
16977
17071
|
if (process.platform === "darwin") {
|
|
16978
|
-
const
|
|
16979
|
-
const
|
|
16980
|
-
|
|
16981
|
-
|
|
16982
|
-
|
|
16983
|
-
|
|
17072
|
+
const plistPath = getLaunchdPlistPath(instanceId);
|
|
17073
|
+
const plist = generateLaunchdPlist(nodePath, cliPath, resolvedLogDir, instanceRoot, instanceId);
|
|
17074
|
+
const dir = path23.dirname(plistPath);
|
|
17075
|
+
fs25.mkdirSync(dir, { recursive: true });
|
|
17076
|
+
fs25.writeFileSync(plistPath, plist);
|
|
17077
|
+
const uid = process.getuid();
|
|
17078
|
+
const domain = `gui/${uid}`;
|
|
17079
|
+
try {
|
|
17080
|
+
execFileSync6("launchctl", ["bootout", domain, plistPath], { stdio: "pipe" });
|
|
17081
|
+
} catch {
|
|
17082
|
+
}
|
|
17083
|
+
execFileSync6("launchctl", ["bootstrap", domain, plistPath], { stdio: "pipe" });
|
|
17084
|
+
log18.info({ instanceId }, "LaunchAgent installed");
|
|
16984
17085
|
return { success: true };
|
|
16985
17086
|
}
|
|
16986
17087
|
if (process.platform === "linux") {
|
|
16987
|
-
const
|
|
16988
|
-
const
|
|
16989
|
-
|
|
16990
|
-
|
|
17088
|
+
const servicePath = getSystemdServicePath(instanceId);
|
|
17089
|
+
const serviceName = getSystemdServiceName(instanceId);
|
|
17090
|
+
const unit = generateSystemdUnit(nodePath, cliPath, instanceRoot, instanceId);
|
|
17091
|
+
const dir = path23.dirname(servicePath);
|
|
17092
|
+
fs25.mkdirSync(dir, { recursive: true });
|
|
17093
|
+
fs25.writeFileSync(servicePath, unit);
|
|
16991
17094
|
execFileSync6("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
|
|
16992
|
-
execFileSync6("systemctl", ["--user", "enable",
|
|
16993
|
-
log18.info("systemd user service installed");
|
|
17095
|
+
execFileSync6("systemctl", ["--user", "enable", serviceName], { stdio: "pipe" });
|
|
17096
|
+
log18.info({ instanceId }, "systemd user service installed");
|
|
16994
17097
|
return { success: true };
|
|
16995
17098
|
}
|
|
16996
17099
|
return { success: false, error: "Unsupported platform" };
|
|
@@ -17000,31 +17103,35 @@ function installAutoStart(logDir2) {
|
|
|
17000
17103
|
return { success: false, error: msg };
|
|
17001
17104
|
}
|
|
17002
17105
|
}
|
|
17003
|
-
function uninstallAutoStart() {
|
|
17106
|
+
function uninstallAutoStart(instanceId) {
|
|
17004
17107
|
if (!isAutoStartSupported()) {
|
|
17005
17108
|
return { success: false, error: "Auto-start not supported on this platform" };
|
|
17006
17109
|
}
|
|
17007
17110
|
try {
|
|
17008
17111
|
if (process.platform === "darwin") {
|
|
17009
|
-
|
|
17112
|
+
const plistPath = getLaunchdPlistPath(instanceId);
|
|
17113
|
+
if (fs25.existsSync(plistPath)) {
|
|
17114
|
+
const uid = process.getuid();
|
|
17010
17115
|
try {
|
|
17011
|
-
execFileSync6("launchctl", ["
|
|
17116
|
+
execFileSync6("launchctl", ["bootout", `gui/${uid}`, plistPath], { stdio: "pipe" });
|
|
17012
17117
|
} catch {
|
|
17013
17118
|
}
|
|
17014
|
-
|
|
17015
|
-
log18.info("LaunchAgent removed");
|
|
17119
|
+
fs25.unlinkSync(plistPath);
|
|
17120
|
+
log18.info({ instanceId }, "LaunchAgent removed");
|
|
17016
17121
|
}
|
|
17017
17122
|
return { success: true };
|
|
17018
17123
|
}
|
|
17019
17124
|
if (process.platform === "linux") {
|
|
17020
|
-
|
|
17125
|
+
const servicePath = getSystemdServicePath(instanceId);
|
|
17126
|
+
const serviceName = getSystemdServiceName(instanceId);
|
|
17127
|
+
if (fs25.existsSync(servicePath)) {
|
|
17021
17128
|
try {
|
|
17022
|
-
execFileSync6("systemctl", ["--user", "disable",
|
|
17129
|
+
execFileSync6("systemctl", ["--user", "disable", serviceName], { stdio: "pipe" });
|
|
17023
17130
|
} catch {
|
|
17024
17131
|
}
|
|
17025
|
-
|
|
17132
|
+
fs25.unlinkSync(servicePath);
|
|
17026
17133
|
execFileSync6("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
|
|
17027
|
-
log18.info("systemd user service removed");
|
|
17134
|
+
log18.info({ instanceId }, "systemd user service removed");
|
|
17028
17135
|
}
|
|
17029
17136
|
return { success: true };
|
|
17030
17137
|
}
|
|
@@ -17035,16 +17142,41 @@ function uninstallAutoStart() {
|
|
|
17035
17142
|
return { success: false, error: msg };
|
|
17036
17143
|
}
|
|
17037
17144
|
}
|
|
17038
|
-
function isAutoStartInstalled() {
|
|
17145
|
+
function isAutoStartInstalled(instanceId) {
|
|
17039
17146
|
if (process.platform === "darwin") {
|
|
17040
|
-
return
|
|
17147
|
+
return fs25.existsSync(getLaunchdPlistPath(instanceId));
|
|
17041
17148
|
}
|
|
17042
17149
|
if (process.platform === "linux") {
|
|
17043
|
-
return
|
|
17150
|
+
return fs25.existsSync(getSystemdServicePath(instanceId));
|
|
17044
17151
|
}
|
|
17045
17152
|
return false;
|
|
17046
17153
|
}
|
|
17047
17154
|
|
|
17155
|
+
// src/cli/resolve-instance-id.ts
|
|
17156
|
+
init_instance_context();
|
|
17157
|
+
init_instance_registry();
|
|
17158
|
+
init_log();
|
|
17159
|
+
import fs28 from "fs";
|
|
17160
|
+
import path26 from "path";
|
|
17161
|
+
var log19 = createChildLogger({ module: "resolve-instance-id" });
|
|
17162
|
+
function resolveInstanceId(instanceRoot) {
|
|
17163
|
+
try {
|
|
17164
|
+
const configPath = path26.join(instanceRoot, "config.json");
|
|
17165
|
+
const raw = JSON.parse(fs28.readFileSync(configPath, "utf-8"));
|
|
17166
|
+
if (raw.id && typeof raw.id === "string") return raw.id;
|
|
17167
|
+
} catch {
|
|
17168
|
+
}
|
|
17169
|
+
try {
|
|
17170
|
+
const reg = new InstanceRegistry(path26.join(getGlobalRoot(), "instances.json"));
|
|
17171
|
+
reg.load();
|
|
17172
|
+
const entry = reg.getByRoot(instanceRoot);
|
|
17173
|
+
if (entry?.id) return entry.id;
|
|
17174
|
+
} catch (err) {
|
|
17175
|
+
log19.debug({ err: err.message, instanceRoot }, "Could not read instance registry, using fallback id");
|
|
17176
|
+
}
|
|
17177
|
+
return path26.basename(path26.dirname(instanceRoot)).replace(/[^a-zA-Z0-9-]/g, "-") || "default";
|
|
17178
|
+
}
|
|
17179
|
+
|
|
17048
17180
|
// src/core/config/config-editor.ts
|
|
17049
17181
|
init_config();
|
|
17050
17182
|
async function select3(opts) {
|
|
@@ -17170,29 +17302,28 @@ async function ensureDiscordPlugin() {
|
|
|
17170
17302
|
}
|
|
17171
17303
|
}
|
|
17172
17304
|
}
|
|
17173
|
-
async function editDiscord(_config, _updates) {
|
|
17305
|
+
async function editDiscord(_config, _updates, settingsManager, instanceRoot) {
|
|
17174
17306
|
const pluginModule = await ensureDiscordPlugin();
|
|
17175
17307
|
if (!pluginModule) return;
|
|
17176
17308
|
const plugin = pluginModule.default;
|
|
17177
17309
|
if (plugin?.configure) {
|
|
17178
|
-
|
|
17310
|
+
if (!settingsManager || !instanceRoot) {
|
|
17311
|
+
console.log(warn("Cannot configure Discord \u2014 instance context not available."));
|
|
17312
|
+
return;
|
|
17313
|
+
}
|
|
17179
17314
|
const { createInstallContext: createInstallContext2 } = await Promise.resolve().then(() => (init_install_context(), install_context_exports));
|
|
17180
|
-
const { getGlobalRoot: getGlobalRoot2 } = await Promise.resolve().then(() => (init_instance_context(), instance_context_exports));
|
|
17181
|
-
const root = getGlobalRoot2();
|
|
17182
|
-
const basePath = path28.join(root, "plugins", "data");
|
|
17183
|
-
const settingsManager = new SettingsManager2(basePath);
|
|
17184
17315
|
const ctx = createInstallContext2({
|
|
17185
17316
|
pluginName: plugin.name,
|
|
17186
17317
|
settingsManager,
|
|
17187
|
-
basePath,
|
|
17188
|
-
instanceRoot
|
|
17318
|
+
basePath: settingsManager.getBasePath(),
|
|
17319
|
+
instanceRoot
|
|
17189
17320
|
});
|
|
17190
17321
|
await plugin.configure(ctx);
|
|
17191
17322
|
} else {
|
|
17192
17323
|
console.log(warn("This plugin does not have a configure() method yet."));
|
|
17193
17324
|
}
|
|
17194
17325
|
}
|
|
17195
|
-
async function editChannels(config, updates, settingsManager) {
|
|
17326
|
+
async function editChannels(config, updates, settingsManager, instanceRoot) {
|
|
17196
17327
|
let tgConfigured = false;
|
|
17197
17328
|
let dcConfigured = false;
|
|
17198
17329
|
if (settingsManager) {
|
|
@@ -17216,7 +17347,7 @@ async function editChannels(config, updates, settingsManager) {
|
|
|
17216
17347
|
});
|
|
17217
17348
|
if (choice === "back") break;
|
|
17218
17349
|
if (choice === "telegram") await editTelegram(config, updates, settingsManager);
|
|
17219
|
-
if (choice === "discord") await editDiscord(config, updates);
|
|
17350
|
+
if (choice === "discord") await editDiscord(config, updates, settingsManager, instanceRoot);
|
|
17220
17351
|
}
|
|
17221
17352
|
}
|
|
17222
17353
|
async function editAgent(config, updates) {
|
|
@@ -17249,19 +17380,6 @@ async function editAgent(config, updates) {
|
|
|
17249
17380
|
}
|
|
17250
17381
|
}
|
|
17251
17382
|
}
|
|
17252
|
-
async function editWorkspace(config, updates) {
|
|
17253
|
-
const currentDir = config.workspace?.baseDir ?? "~/openacp-workspace";
|
|
17254
|
-
console.log(header("Workspace"));
|
|
17255
|
-
console.log(` Base directory : ${currentDir}`);
|
|
17256
|
-
console.log("");
|
|
17257
|
-
const newDir = await input({
|
|
17258
|
-
message: "Workspace base directory:",
|
|
17259
|
-
default: currentDir,
|
|
17260
|
-
validate: (val) => val.trim().length > 0 || "Path cannot be empty"
|
|
17261
|
-
});
|
|
17262
|
-
updates.workspace = { baseDir: newDir.trim() };
|
|
17263
|
-
console.log(ok(`Workspace set to ${newDir.trim()}`));
|
|
17264
|
-
}
|
|
17265
17383
|
async function editSecurity(config, updates, settingsManager) {
|
|
17266
17384
|
const ps = settingsManager ? await settingsManager.loadSettings("@openacp/security") : {};
|
|
17267
17385
|
const sec = {
|
|
@@ -17360,10 +17478,11 @@ async function editLogging(config, updates) {
|
|
|
17360
17478
|
}
|
|
17361
17479
|
}
|
|
17362
17480
|
}
|
|
17363
|
-
async function editRunMode(config, updates) {
|
|
17481
|
+
async function editRunMode(config, updates, instanceRoot) {
|
|
17364
17482
|
const currentMode = config.runMode ?? "foreground";
|
|
17365
17483
|
const currentAutoStart = config.autoStart ?? false;
|
|
17366
|
-
const
|
|
17484
|
+
const instanceId = instanceRoot ? resolveInstanceId(instanceRoot) : "default";
|
|
17485
|
+
const autoStartInstalled = isAutoStartInstalled(instanceId);
|
|
17367
17486
|
const autoStartSupported = isAutoStartSupported();
|
|
17368
17487
|
console.log(header("Run Mode"));
|
|
17369
17488
|
console.log(` Current mode : ${c.bold}${currentMode}${c.reset}`);
|
|
@@ -17396,7 +17515,7 @@ async function editRunMode(config, updates) {
|
|
|
17396
17515
|
if (choice === "daemon") {
|
|
17397
17516
|
updates.runMode = "daemon";
|
|
17398
17517
|
const logDir2 = config.logging?.logDir ?? "~/.openacp/logs";
|
|
17399
|
-
const result = installAutoStart(
|
|
17518
|
+
const result = installAutoStart(expandHome2(logDir2), instanceRoot, instanceId);
|
|
17400
17519
|
if (result.success) {
|
|
17401
17520
|
updates.autoStart = true;
|
|
17402
17521
|
console.log(ok("Switched to daemon mode with auto-start"));
|
|
@@ -17407,7 +17526,7 @@ async function editRunMode(config, updates) {
|
|
|
17407
17526
|
if (choice === "foreground") {
|
|
17408
17527
|
updates.runMode = "foreground";
|
|
17409
17528
|
updates.autoStart = false;
|
|
17410
|
-
uninstallAutoStart();
|
|
17529
|
+
uninstallAutoStart(instanceId);
|
|
17411
17530
|
console.log(ok("Switched to foreground mode"));
|
|
17412
17531
|
}
|
|
17413
17532
|
if (choice === "toggleAutoStart") {
|
|
@@ -17416,7 +17535,7 @@ async function editRunMode(config, updates) {
|
|
|
17416
17535
|
return currentAutoStart;
|
|
17417
17536
|
})();
|
|
17418
17537
|
if (autoStartCurrent) {
|
|
17419
|
-
const result = uninstallAutoStart();
|
|
17538
|
+
const result = uninstallAutoStart(instanceId);
|
|
17420
17539
|
updates.autoStart = false;
|
|
17421
17540
|
if (result.success) {
|
|
17422
17541
|
console.log(ok("Auto-start disabled"));
|
|
@@ -17425,7 +17544,7 @@ async function editRunMode(config, updates) {
|
|
|
17425
17544
|
}
|
|
17426
17545
|
} else {
|
|
17427
17546
|
const logDir2 = config.logging?.logDir ?? "~/.openacp/logs";
|
|
17428
|
-
const result = installAutoStart(
|
|
17547
|
+
const result = installAutoStart(expandHome2(logDir2), instanceRoot, instanceId);
|
|
17429
17548
|
updates.autoStart = result.success;
|
|
17430
17549
|
if (result.success) {
|
|
17431
17550
|
console.log(ok("Auto-start enabled"));
|
|
@@ -17627,6 +17746,7 @@ async function runConfigEditor(configManager, mode = "file", apiPort, settingsMa
|
|
|
17627
17746
|
await configManager.load();
|
|
17628
17747
|
const config = configManager.get();
|
|
17629
17748
|
const updates = {};
|
|
17749
|
+
const instanceRoot = path30.dirname(configManager.getConfigPath());
|
|
17630
17750
|
console.log(`
|
|
17631
17751
|
${c.cyan}${c.bold}OpenACP Config Editor${c.reset}`);
|
|
17632
17752
|
console.log(dim(`Config: ${configManager.getConfigPath()}`));
|
|
@@ -17639,7 +17759,6 @@ ${c.cyan}${c.bold}OpenACP Config Editor${c.reset}`);
|
|
|
17639
17759
|
choices: [
|
|
17640
17760
|
{ name: "Channels", value: "channels" },
|
|
17641
17761
|
{ name: "Agent", value: "agent" },
|
|
17642
|
-
{ name: "Workspace", value: "workspace" },
|
|
17643
17762
|
{ name: "Security", value: "security" },
|
|
17644
17763
|
{ name: "Logging", value: "logging" },
|
|
17645
17764
|
{ name: "Run Mode", value: "runMode" },
|
|
@@ -17658,12 +17777,11 @@ ${c.cyan}${c.bold}OpenACP Config Editor${c.reset}`);
|
|
|
17658
17777
|
break;
|
|
17659
17778
|
}
|
|
17660
17779
|
const sectionUpdates = {};
|
|
17661
|
-
if (choice === "channels") await editChannels(config, sectionUpdates, settingsManager);
|
|
17780
|
+
if (choice === "channels") await editChannels(config, sectionUpdates, settingsManager, instanceRoot);
|
|
17662
17781
|
else if (choice === "agent") await editAgent(config, sectionUpdates);
|
|
17663
|
-
else if (choice === "workspace") await editWorkspace(config, sectionUpdates);
|
|
17664
17782
|
else if (choice === "security") await editSecurity(config, sectionUpdates, settingsManager);
|
|
17665
17783
|
else if (choice === "logging") await editLogging(config, sectionUpdates);
|
|
17666
|
-
else if (choice === "runMode") await editRunMode(config, sectionUpdates);
|
|
17784
|
+
else if (choice === "runMode") await editRunMode(config, sectionUpdates, instanceRoot);
|
|
17667
17785
|
else if (choice === "api") await editApi(config, sectionUpdates, settingsManager);
|
|
17668
17786
|
else if (choice === "tunnel") await editTunnel(config, sectionUpdates, settingsManager);
|
|
17669
17787
|
if (mode === "api" && Object.keys(sectionUpdates).length > 0) {
|
|
@@ -17685,17 +17803,17 @@ ${c.cyan}${c.bold}OpenACP Config Editor${c.reset}`);
|
|
|
17685
17803
|
async function sendConfigViaApi(port, updates) {
|
|
17686
17804
|
const { apiCall: call } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
17687
17805
|
const paths = flattenToPaths(updates);
|
|
17688
|
-
for (const { path:
|
|
17806
|
+
for (const { path: path35, value } of paths) {
|
|
17689
17807
|
const res = await call(port, "/api/config", {
|
|
17690
17808
|
method: "PATCH",
|
|
17691
17809
|
headers: { "Content-Type": "application/json" },
|
|
17692
|
-
body: JSON.stringify({ path:
|
|
17810
|
+
body: JSON.stringify({ path: path35, value })
|
|
17693
17811
|
});
|
|
17694
17812
|
const data = await res.json();
|
|
17695
17813
|
if (!res.ok) {
|
|
17696
|
-
console.log(warn(`Failed to update ${
|
|
17814
|
+
console.log(warn(`Failed to update ${path35}: ${data.error}`));
|
|
17697
17815
|
} else if (data.needsRestart) {
|
|
17698
|
-
console.log(warn(`${
|
|
17816
|
+
console.log(warn(`${path35} updated \u2014 restart required`));
|
|
17699
17817
|
}
|
|
17700
17818
|
}
|
|
17701
17819
|
}
|
|
@@ -17715,30 +17833,25 @@ function flattenToPaths(obj, prefix = "") {
|
|
|
17715
17833
|
// src/cli/daemon.ts
|
|
17716
17834
|
init_config();
|
|
17717
17835
|
import { spawn as spawn3 } from "child_process";
|
|
17718
|
-
import * as
|
|
17719
|
-
import * as
|
|
17720
|
-
import * as os14 from "os";
|
|
17721
|
-
var DEFAULT_ROOT2 = path29.join(os14.homedir(), ".openacp");
|
|
17836
|
+
import * as fs31 from "fs";
|
|
17837
|
+
import * as path31 from "path";
|
|
17722
17838
|
function getPidPath(root) {
|
|
17723
|
-
|
|
17724
|
-
return path29.join(base, "openacp.pid");
|
|
17839
|
+
return path31.join(root, "openacp.pid");
|
|
17725
17840
|
}
|
|
17726
17841
|
function getLogDir(root) {
|
|
17727
|
-
|
|
17728
|
-
return path29.join(base, "logs");
|
|
17842
|
+
return path31.join(root, "logs");
|
|
17729
17843
|
}
|
|
17730
17844
|
function getRunningMarker(root) {
|
|
17731
|
-
|
|
17732
|
-
return path29.join(base, "running");
|
|
17845
|
+
return path31.join(root, "running");
|
|
17733
17846
|
}
|
|
17734
17847
|
function writePidFile(pidPath, pid) {
|
|
17735
|
-
const dir =
|
|
17736
|
-
|
|
17737
|
-
|
|
17848
|
+
const dir = path31.dirname(pidPath);
|
|
17849
|
+
fs31.mkdirSync(dir, { recursive: true });
|
|
17850
|
+
fs31.writeFileSync(pidPath, String(pid));
|
|
17738
17851
|
}
|
|
17739
17852
|
function readPidFile(pidPath) {
|
|
17740
17853
|
try {
|
|
17741
|
-
const content =
|
|
17854
|
+
const content = fs31.readFileSync(pidPath, "utf-8").trim();
|
|
17742
17855
|
const pid = parseInt(content, 10);
|
|
17743
17856
|
return isNaN(pid) ? null : pid;
|
|
17744
17857
|
} catch {
|
|
@@ -17747,7 +17860,7 @@ function readPidFile(pidPath) {
|
|
|
17747
17860
|
}
|
|
17748
17861
|
function removePidFile(pidPath) {
|
|
17749
17862
|
try {
|
|
17750
|
-
|
|
17863
|
+
fs31.unlinkSync(pidPath);
|
|
17751
17864
|
} catch {
|
|
17752
17865
|
}
|
|
17753
17866
|
}
|
|
@@ -17762,7 +17875,7 @@ function isProcessRunning(pidPath) {
|
|
|
17762
17875
|
return false;
|
|
17763
17876
|
}
|
|
17764
17877
|
}
|
|
17765
|
-
function getStatus(pidPath
|
|
17878
|
+
function getStatus(pidPath) {
|
|
17766
17879
|
const pid = readPidFile(pidPath);
|
|
17767
17880
|
if (pid === null) return { running: false };
|
|
17768
17881
|
try {
|
|
@@ -17773,19 +17886,19 @@ function getStatus(pidPath = getPidPath()) {
|
|
|
17773
17886
|
return { running: false };
|
|
17774
17887
|
}
|
|
17775
17888
|
}
|
|
17776
|
-
function startDaemon(pidPath
|
|
17889
|
+
function startDaemon(pidPath, logDir2, instanceRoot) {
|
|
17777
17890
|
markRunning(instanceRoot);
|
|
17778
17891
|
if (isProcessRunning(pidPath)) {
|
|
17779
17892
|
const pid = readPidFile(pidPath);
|
|
17780
17893
|
return { error: `Already running (PID ${pid})` };
|
|
17781
17894
|
}
|
|
17782
|
-
const resolvedLogDir = logDir2 ?
|
|
17783
|
-
|
|
17784
|
-
const logFile =
|
|
17785
|
-
const cliPath =
|
|
17895
|
+
const resolvedLogDir = logDir2 ? expandHome2(logDir2) : getLogDir(instanceRoot);
|
|
17896
|
+
fs31.mkdirSync(resolvedLogDir, { recursive: true });
|
|
17897
|
+
const logFile = path31.join(resolvedLogDir, "openacp.log");
|
|
17898
|
+
const cliPath = path31.resolve(process.argv[1]);
|
|
17786
17899
|
const nodePath = process.execPath;
|
|
17787
|
-
const out =
|
|
17788
|
-
const err =
|
|
17900
|
+
const out = fs31.openSync(logFile, "a");
|
|
17901
|
+
const err = fs31.openSync(logFile, "a");
|
|
17789
17902
|
const child = spawn3(nodePath, [cliPath, "--daemon-child"], {
|
|
17790
17903
|
detached: true,
|
|
17791
17904
|
stdio: ["ignore", out, err],
|
|
@@ -17794,8 +17907,8 @@ function startDaemon(pidPath = getPidPath(), logDir2, instanceRoot) {
|
|
|
17794
17907
|
...instanceRoot ? { OPENACP_INSTANCE_ROOT: instanceRoot } : {}
|
|
17795
17908
|
}
|
|
17796
17909
|
});
|
|
17797
|
-
|
|
17798
|
-
|
|
17910
|
+
fs31.closeSync(out);
|
|
17911
|
+
fs31.closeSync(err);
|
|
17799
17912
|
if (!child.pid) {
|
|
17800
17913
|
return { error: "Failed to spawn daemon process" };
|
|
17801
17914
|
}
|
|
@@ -17803,7 +17916,7 @@ function startDaemon(pidPath = getPidPath(), logDir2, instanceRoot) {
|
|
|
17803
17916
|
child.unref();
|
|
17804
17917
|
return { pid: child.pid };
|
|
17805
17918
|
}
|
|
17806
|
-
function
|
|
17919
|
+
function sleep2(ms) {
|
|
17807
17920
|
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
17808
17921
|
}
|
|
17809
17922
|
function isProcessAlive2(pid) {
|
|
@@ -17816,7 +17929,7 @@ function isProcessAlive2(pid) {
|
|
|
17816
17929
|
return "dead";
|
|
17817
17930
|
}
|
|
17818
17931
|
}
|
|
17819
|
-
async function stopDaemon(pidPath
|
|
17932
|
+
async function stopDaemon(pidPath, instanceRoot) {
|
|
17820
17933
|
const pid = readPidFile(pidPath);
|
|
17821
17934
|
if (pid === null) return { stopped: false, error: "Not running (no PID file)" };
|
|
17822
17935
|
const status = isProcessAlive2(pid);
|
|
@@ -17838,7 +17951,7 @@ async function stopDaemon(pidPath = getPidPath(), instanceRoot) {
|
|
|
17838
17951
|
const TIMEOUT = 5e3;
|
|
17839
17952
|
const start = Date.now();
|
|
17840
17953
|
while (Date.now() - start < TIMEOUT) {
|
|
17841
|
-
await
|
|
17954
|
+
await sleep2(POLL_INTERVAL);
|
|
17842
17955
|
const s = isProcessAlive2(pid);
|
|
17843
17956
|
if (s === "dead" || s === "eperm") {
|
|
17844
17957
|
removePidFile(pidPath);
|
|
@@ -17855,7 +17968,7 @@ async function stopDaemon(pidPath = getPidPath(), instanceRoot) {
|
|
|
17855
17968
|
}
|
|
17856
17969
|
const killStart = Date.now();
|
|
17857
17970
|
while (Date.now() - killStart < 1e3) {
|
|
17858
|
-
await
|
|
17971
|
+
await sleep2(POLL_INTERVAL);
|
|
17859
17972
|
const s = isProcessAlive2(pid);
|
|
17860
17973
|
if (s === "dead" || s === "eperm") {
|
|
17861
17974
|
removePidFile(pidPath);
|
|
@@ -17866,12 +17979,12 @@ async function stopDaemon(pidPath = getPidPath(), instanceRoot) {
|
|
|
17866
17979
|
}
|
|
17867
17980
|
function markRunning(root) {
|
|
17868
17981
|
const marker = getRunningMarker(root);
|
|
17869
|
-
|
|
17870
|
-
|
|
17982
|
+
fs31.mkdirSync(path31.dirname(marker), { recursive: true });
|
|
17983
|
+
fs31.writeFileSync(marker, "");
|
|
17871
17984
|
}
|
|
17872
17985
|
function clearRunning(root) {
|
|
17873
17986
|
try {
|
|
17874
|
-
|
|
17987
|
+
fs31.unlinkSync(getRunningMarker(root));
|
|
17875
17988
|
} catch {
|
|
17876
17989
|
}
|
|
17877
17990
|
}
|
|
@@ -17887,7 +18000,7 @@ init_static_server();
|
|
|
17887
18000
|
|
|
17888
18001
|
// src/plugins/telegram/topic-manager.ts
|
|
17889
18002
|
init_log();
|
|
17890
|
-
var
|
|
18003
|
+
var log22 = createChildLogger({ module: "topic-manager" });
|
|
17891
18004
|
var TopicManager = class {
|
|
17892
18005
|
constructor(sessionManager, adapter, systemTopicIds) {
|
|
17893
18006
|
this.sessionManager = sessionManager;
|
|
@@ -17926,7 +18039,7 @@ var TopicManager = class {
|
|
|
17926
18039
|
try {
|
|
17927
18040
|
await this.adapter.deleteSessionThread?.(sessionId);
|
|
17928
18041
|
} catch (err) {
|
|
17929
|
-
|
|
18042
|
+
log22.warn({ err, sessionId, topicId }, "Failed to delete platform thread, removing record anyway");
|
|
17930
18043
|
}
|
|
17931
18044
|
}
|
|
17932
18045
|
await this.sessionManager.removeRecord(sessionId);
|
|
@@ -17949,7 +18062,7 @@ var TopicManager = class {
|
|
|
17949
18062
|
try {
|
|
17950
18063
|
await this.adapter.deleteSessionThread?.(record.sessionId);
|
|
17951
18064
|
} catch (err) {
|
|
17952
|
-
|
|
18065
|
+
log22.warn({ err, sessionId: record.sessionId }, "Failed to delete platform thread during cleanup");
|
|
17953
18066
|
}
|
|
17954
18067
|
}
|
|
17955
18068
|
await this.sessionManager.removeRecord(record.sessionId);
|
|
@@ -18571,7 +18684,7 @@ Config file: \`~/.openacp/config.json\`
|
|
|
18571
18684
|
- Agent list is fetched from the ACP Registry CDN and cached locally (24h)
|
|
18572
18685
|
|
|
18573
18686
|
### Workspace
|
|
18574
|
-
-
|
|
18687
|
+
- Workspace directory is the parent of \`.openacp/\` (where you ran \`openacp\` setup)
|
|
18575
18688
|
|
|
18576
18689
|
### Security
|
|
18577
18690
|
- **security.allowedUserIds** \u2014 Restrict who can use the bot (empty = everyone)
|
|
@@ -18727,7 +18840,7 @@ export {
|
|
|
18727
18840
|
createChildLogger,
|
|
18728
18841
|
createSessionLogger,
|
|
18729
18842
|
createTurnContext,
|
|
18730
|
-
|
|
18843
|
+
expandHome2 as expandHome,
|
|
18731
18844
|
extractContentText,
|
|
18732
18845
|
formatTokens,
|
|
18733
18846
|
formatToolSummary,
|