@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/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
- import * as fs3 from "fs";
396
- import * as path3 from "path";
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(path34) {
404
- const def = getFieldDef(path34);
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, path34) {
412
- const parts = path34.split(".");
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 configAgents = Object.keys(config.agents ?? {});
466
- if (configAgents.length > 0) return configAgents;
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 fs4 from "fs";
526
- import * as path4 from "path";
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 expandHome3(p) {
439
+ function expandHome2(p) {
531
440
  if (p.startsWith("~")) {
532
- return path4.join(os3.homedir(), p.slice(1));
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 || expandHome3("~/.openacp/config.json");
498
+ this.configPath = process.env.OPENACP_CONFIG_PATH || configPath || expandHome2("~/.openacp/config.json");
590
499
  }
591
500
  async load() {
592
- const dir = path4.dirname(this.configPath);
593
- fs4.mkdirSync(dir, { recursive: true });
594
- if (!fs4.existsSync(this.configPath)) {
595
- fs4.writeFileSync(
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(fs4.readFileSync(this.configPath, "utf-8"));
606
- const { changed: configUpdated } = applyMigrations(raw, void 0, { configDir: path4.dirname(this.configPath) });
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
- fs4.writeFileSync(this.configPath, JSON.stringify(raw, null, 2));
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(fs4.readFileSync(this.configPath, "utf-8"));
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
- fs4.writeFileSync(tmpPath, JSON.stringify(raw, null, 2), "utf-8");
638
- fs4.renameSync(tmpPath, this.configPath);
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
- const resolved2 = expandHome3(this.config.workspace.baseDir);
670
- fs4.mkdirSync(resolved2, { recursive: true });
671
- return resolved2;
672
- }
673
- if (input2.startsWith("/") || input2.startsWith("~")) {
674
- const resolved2 = expandHome3(input2);
675
- const base = expandHome3(this.config.workspace.baseDir);
676
- const isInternal = resolved2 === base || resolved2.startsWith(base + path4.sep);
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 "${this.config.workspace.baseDir}". Set allowExternalWorkspaces: true to allow this.`
590
+ `Workspace path "${input2}" is outside base directory "${workspaceBase}". Set allowExternalWorkspaces: true to allow this.`
681
591
  );
682
592
  }
683
- if (!fs4.existsSync(resolved2)) {
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 resolved2;
596
+ return resolved;
689
597
  }
690
- fs4.mkdirSync(resolved2, { recursive: true });
691
- return resolved2;
598
+ fs3.mkdirSync(resolved, { recursive: true });
599
+ return resolved;
692
600
  }
693
- const name = input2.replace(/[^a-zA-Z0-9_-]/g, "");
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 resolved = path4.join(
700
- expandHome3(this.config.workspace.baseDir),
701
- name.toLowerCase()
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 fs4.existsSync(this.configPath);
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 = path4.dirname(this.configPath);
714
- fs4.mkdirSync(dir, { recursive: true });
715
- fs4.writeFileSync(this.configPath, JSON.stringify(config, null, 2));
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 fs6 from "fs";
702
+ import fs5 from "fs";
799
703
  async function readTextFileWithRange(filePath, options) {
800
- const content = await fs6.promises.readFile(filePath, "utf-8");
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 fs11 from "fs";
958
- import * as path10 from "path";
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 = path10.join(dir, "node_modules", ".bin", cmd);
983
- if (fs11.existsSync(binPath)) return true;
984
- const parent = path10.dirname(dir);
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 fs13 from "fs";
1258
- import * as path12 from "path";
1259
- import * as os5 from "os";
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 = path12.resolve(binaryPath);
1274
- const realAgentsDir = path12.resolve(agentsDir);
1275
- if (!realPath.startsWith(realAgentsDir + path12.sep) && realPath !== 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 = path12.resolve(binaryPath, dist.cmd);
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 = path12.join(agentsDir ?? DEFAULT_AGENTS_DIR, agentId);
1396
- fs13.mkdirSync(destDir, { recursive: true });
1299
+ const destDir = path10.join(agentsDir ?? DEFAULT_AGENTS_DIR, agentId);
1300
+ fs11.mkdirSync(destDir, { recursive: true });
1397
1301
  await progress?.onStep("Downloading...");
1398
- log11.info({ agentId, url: archiveUrl }, "Downloading agent binary");
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 = fs13.realpathSync(destDir);
1438
- const entries = fs13.readdirSync(destDir, { recursive: true, withFileTypes: true });
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 = path12.join(parentPath, entry.name);
1346
+ const fullPath = path10.join(parentPath, entry.name);
1443
1347
  let realPath;
1444
1348
  try {
1445
- realPath = fs13.realpathSync(fullPath);
1349
+ realPath = fs11.realpathSync(fullPath);
1446
1350
  } catch {
1447
- const linkTarget = fs13.readlinkSync(fullPath);
1448
- realPath = path12.resolve(path12.dirname(fullPath), linkTarget);
1351
+ const linkTarget = fs11.readlinkSync(fullPath);
1352
+ realPath = path10.resolve(path10.dirname(fullPath), linkTarget);
1449
1353
  }
1450
- if (!realPath.startsWith(realDest + path12.sep) && realPath !== realDest) {
1451
- fs13.rmSync(destDir, { recursive: true, force: true });
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 = path12.join(destDir, "_archive.tar.gz");
1459
- fs13.writeFileSync(tmpFile, buffer);
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
- fs13.unlinkSync(tmpFile);
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 = path12.join(destDir, "_archive.zip");
1472
- fs13.writeFileSync(tmpFile, buffer);
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
- fs13.unlinkSync(tmpFile);
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 && fs13.existsSync(agent.binaryPath)) {
1390
+ if (agent.binaryPath && fs11.existsSync(agent.binaryPath)) {
1487
1391
  validateUninstallPath(agent.binaryPath, agentsDir ?? DEFAULT_AGENTS_DIR);
1488
- fs13.rmSync(agent.binaryPath, { recursive: true, force: true });
1489
- log11.info({ agentKey, binaryPath: agent.binaryPath }, "Deleted agent binary");
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 log11, DEFAULT_AGENTS_DIR, MAX_DOWNLOAD_SIZE, validateTarContents, ARCH_MAP, PLATFORM_MAP;
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
- log11 = createChildLogger({ module: "agent-installer" });
1500
- DEFAULT_AGENTS_DIR = path12.join(os5.homedir(), ".openacp", "agents");
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 fs16 from "fs";
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 (!fs16.existsSync(ctx.configPath)) {
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(fs16.readFileSync(ctx.configPath, "utf-8"));
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
- fs16.writeFileSync(ctx.configPath, JSON.stringify(raw, null, 2));
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 fs17 from "fs";
1579
- import * as path17 from "path";
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 = path17.join(dir, "node_modules", ".bin", cmd);
1589
- if (fs17.existsSync(binPath)) return true;
1590
- const parent = path17.dirname(dir);
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 = path17.join(ctx.dataDir, "agents.json");
1613
- if (fs17.existsSync(agentsPath)) {
1614
- const data = JSON.parse(fs17.readFileSync(agentsPath, "utf-8"));
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 fs18 from "fs";
1653
- import path18 from "path";
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 = fs18.readFileSync(settingsPath, "utf-8");
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 path18.join(this.basePath, pluginName, "settings.json");
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 = fs18.readFileSync(this.settingsPath, "utf-8");
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 = path18.dirname(this.settingsPath);
1719
- fs18.mkdirSync(dir, { recursive: true });
1720
- fs18.writeFileSync(this.settingsPath, JSON.stringify(data, null, 2));
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 path19 from "path";
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(path19.join(ctx.pluginsDir, "data"));
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 fs19 from "fs";
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 (!fs19.existsSync(ctx.dataDir)) {
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
- fs19.mkdirSync(ctx.dataDir, { recursive: true });
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
- fs19.accessSync(ctx.dataDir, fs19.constants.W_OK);
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 (fs19.existsSync(ctx.sessionsPath)) {
1787
+ if (fs18.existsSync(ctx.sessionsPath)) {
1884
1788
  try {
1885
- const content = fs19.readFileSync(ctx.sessionsPath, "utf-8");
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
- fs19.writeFileSync(ctx.sessionsPath, JSON.stringify({ version: 1, sessions: {} }, null, 2));
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
- fs19.writeFileSync(ctx.sessionsPath, JSON.stringify({ version: 1, sessions: {} }, null, 2));
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 (!fs19.existsSync(ctx.logsDir)) {
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
- fs19.mkdirSync(ctx.logsDir, { recursive: true });
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
- fs19.accessSync(ctx.logsDir, fs19.constants.W_OK);
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 fs20 from "fs";
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
- if (!ctx.config) {
1954
- results.push({ status: "fail", message: "Cannot check workspace \u2014 config not loaded" });
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: ${baseDir}`,
1861
+ message: `Workspace directory does not exist: ${workspace}`,
1962
1862
  fixable: true,
1963
1863
  fixRisk: "safe",
1964
1864
  fix: async () => {
1965
- fs20.mkdirSync(baseDir, { recursive: true });
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
- fs20.accessSync(baseDir, fs20.constants.W_OK);
1972
- results.push({ status: "pass", message: `Workspace directory exists: ${baseDir}` });
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: ${baseDir}` });
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 fs21 from "fs";
1985
- import * as path20 from "path";
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 (!fs21.existsSync(ctx.pluginsDir)) {
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
- fs21.mkdirSync(ctx.pluginsDir, { recursive: true });
2003
- fs21.writeFileSync(
2004
- path20.join(ctx.pluginsDir, "package.json"),
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 = path20.join(ctx.pluginsDir, "package.json");
2014
- if (!fs21.existsSync(pkgPath)) {
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
- fs21.writeFileSync(
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(fs21.readFileSync(pkgPath, "utf-8"));
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
- fs21.writeFileSync(
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 fs22 from "fs";
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 (fs22.existsSync(ctx.pidPath)) {
2088
- const content = fs22.readFileSync(ctx.pidPath, "utf-8").trim();
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
- fs22.unlinkSync(ctx.pidPath);
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
- fs22.unlinkSync(ctx.pidPath);
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 (fs22.existsSync(ctx.portFilePath)) {
2117
- const content = fs22.readFileSync(ctx.portFilePath, "utf-8").trim();
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
- fs22.unlinkSync(ctx.portFilePath);
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 (fs22.existsSync(ctx.pidPath)) {
2139
- const pid = parseInt(fs22.readFileSync(ctx.pidPath, "utf-8").trim(), 10);
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 fs23 from "fs";
2160
- import path21 from "path";
2059
+ import fs22 from "fs";
2060
+ import path20 from "path";
2161
2061
  import https from "https";
2162
- import os9 from "os";
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 = fs23.createWriteStream(dest);
2066
+ const file = fs22.createWriteStream(dest);
2167
2067
  const cleanup = () => {
2168
2068
  try {
2169
- if (fs23.existsSync(dest)) fs23.unlinkSync(dest);
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 = os9.platform();
2225
- const arch = os9.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 = path21.join(resolvedBinDir, binName);
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 (fs23.existsSync(binPath)) {
2241
- if (!IS_WINDOWS) fs23.chmodSync(binPath, "755");
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
- fs23.mkdirSync(resolvedBinDir, { recursive: true });
2146
+ fs22.mkdirSync(resolvedBinDir, { recursive: true });
2247
2147
  const url = getDownloadUrl(spec);
2248
2148
  const isArchive = spec.isArchive?.(url) ?? false;
2249
- const downloadDest = isArchive ? path21.join(resolvedBinDir, `${spec.name}.tgz`) : binPath;
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
- fs23.unlinkSync(downloadDest);
2156
+ fs22.unlinkSync(downloadDest);
2257
2157
  } catch {
2258
2158
  }
2259
2159
  }
2260
- if (!fs23.existsSync(binPath)) {
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
- fs23.chmodSync(binPath, "755");
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 = path21.join(os9.homedir(), ".openacp", "bin");
2278
- IS_WINDOWS = os9.platform() === "win32";
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 fs24 from "fs";
2319
- import * as path22 from "path";
2320
- import * as os10 from "os";
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(path22.join(ctx.pluginsDir, "data"));
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 = os10.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
2348
- const binPath = path22.join(ctx.dataDir, "bin", binName);
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 (fs24.existsSync(binPath)) {
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 fs25 from "fs";
2392
- import * as path23 from "path";
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?.dataDir ?? getGlobalRoot();
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 || path23.join(dataDir, "config.json");
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 = fs25.readFileSync(configPath, "utf-8");
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 ? expandHome3(config.logging.logDir) : path23.join(dataDir, "logs");
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: path23.join(dataDir, "sessions.json"),
2492
- pidPath: path23.join(dataDir, "openacp.pid"),
2493
- portFilePath: path23.join(dataDir, "api.port"),
2494
- pluginsDir: path23.join(dataDir, "plugins"),
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 fs27 from "fs/promises";
2511
- import * as os12 from "os";
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 = path25.join(dir, "node_modules", ...packageName.split("/"));
2516
- const pkgJsonPath = path25.join(pkgDir, "package.json");
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 fs27.readFile(pkgJsonPath, "utf-8"));
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 = path25.join(pkgDir, entry);
2547
+ const entryPath = path27.join(pkgDir, entry);
2533
2548
  try {
2534
- await fs27.access(entryPath);
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 ?? path25.join(os12.homedir(), ".openacp", "plugins");
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 path26 from "path";
2641
+ import path28 from "path";
2627
2642
  function createInstallContext(opts) {
2628
2643
  const { pluginName, settingsManager, basePath, instanceRoot } = opts;
2629
- const dataDir = path26.join(basePath, pluginName, "data");
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 fs28 from "fs";
2656
- import * as path27 from "path";
2657
- import * as os13 from "os";
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 path27.join(root ?? DEFAULT_ROOT, "api.port");
2675
+ return path29.join(root, "api.port");
2660
2676
  }
2661
2677
  function defaultSecretFile(root) {
2662
- return path27.join(root ?? DEFAULT_ROOT, "api-secret");
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 = fs28.readFileSync(filePath, "utf-8").trim();
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 = fs28.readFileSync(filePath, "utf-8").trim();
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
- fs28.unlinkSync(filePath);
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 fs30 from "fs";
2737
- import path30 from "path";
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 fs30.promises.readdir(this.baseDir, { withFileTypes: true });
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 = path30.join(this.baseDir, entry.name);
2819
+ const dirPath = path32.join(this.baseDir, entry.name);
2797
2820
  try {
2798
- const stat = await fs30.promises.stat(dirPath);
2821
+ const stat = await fs32.promises.stat(dirPath);
2799
2822
  if (stat.mtimeMs < cutoff) {
2800
- await fs30.promises.rm(dirPath, { recursive: true, force: true });
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 = path30.join(this.baseDir, sessionId);
2812
- await fs30.promises.mkdir(sessionDir, { recursive: true });
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 = path30.join(sessionDir, safeName);
2815
- await fs30.promises.writeFile(filePath, data);
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 fs30.promises.stat(filePath);
2849
+ const stat = await fs32.promises.stat(filePath);
2827
2850
  if (!stat.isFile()) return null;
2828
- const ext = path30.extname(filePath).toLowerCase();
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: path30.basename(filePath),
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
- log20.warn(
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 log20;
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
- log20 = createChildLogger({ module: "api-auth" });
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 fs31 from "fs";
3400
- import * as path31 from "path";
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 = path31.resolve(path31.dirname(__filename), "../../ui/dist");
3425
- if (fs31.existsSync(path31.join(candidate, "index.html"))) {
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 = path31.resolve(
3430
- path31.dirname(__filename),
3452
+ const publishCandidate = path33.resolve(
3453
+ path33.dirname(__filename),
3431
3454
  "../ui"
3432
3455
  );
3433
- if (fs31.existsSync(path31.join(publishCandidate, "index.html"))) {
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 = path31.normalize(urlPath);
3446
- const filePath = path31.join(this.uiDir, safePath);
3447
- if (!filePath.startsWith(this.uiDir + path31.sep) && filePath !== 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 = fs31.realpathSync(filePath);
3474
+ realFilePath = fs33.realpathSync(filePath);
3452
3475
  } catch {
3453
3476
  realFilePath = null;
3454
3477
  }
3455
3478
  if (realFilePath !== null) {
3456
- const realUiDir = fs31.realpathSync(this.uiDir);
3457
- if (!realFilePath.startsWith(realUiDir + path31.sep) && realFilePath !== 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 && fs31.existsSync(realFilePath) && fs31.statSync(realFilePath).isFile()) {
3461
- const ext = path31.extname(filePath);
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
- fs31.createReadStream(realFilePath).pipe(res);
3492
+ fs33.createReadStream(realFilePath).pipe(res);
3470
3493
  return true;
3471
3494
  }
3472
- const indexPath = path31.join(this.uiDir, "index.html");
3473
- if (fs31.existsSync(indexPath)) {
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
- fs31.createReadStream(indexPath).pipe(res);
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 fs32 from "fs";
3634
- import * as path32 from "path";
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
- fs32.mkdirSync(cacheDir, { recursive: true });
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 path32.join(this.cacheDir, `${this.keyHash(repoPath, queryKey)}.json`);
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 = fs32.statSync(fp);
3679
+ const stat = fs34.statSync(fp);
3657
3680
  if (Date.now() - stat.mtimeMs > this.ttlMs) {
3658
- fs32.unlinkSync(fp);
3681
+ fs34.unlinkSync(fp);
3659
3682
  return null;
3660
3683
  }
3661
- return JSON.parse(fs32.readFileSync(fp, "utf-8"));
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
- fs32.writeFileSync(this.filePath(repoPath, queryKey), JSON.stringify(result));
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 ?? path33.join(os15.homedir(), ".openacp", "cache", "entire"));
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 path34 = args.path ?? "";
4605
- return pattern ? `\u{1F50D} Grep "${pattern}"${path34 ? ` in ${path34}` : ""}` : `\u{1F527} ${name}`;
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 path34 = args.path ?? "";
4642
- return pattern ? `"${pattern}"${path34 ? ` in ${path34}` : ""}` : name;
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 dirname10, join as join20, resolve as resolve6 } from "path";
5846
- import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
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 = dirname10(fileURLToPath2(import.meta.url));
5869
+ let dir = dirname12(fileURLToPath2(import.meta.url));
5849
5870
  for (let i = 0; i < 5; i++) {
5850
- const candidate = join20(dir, "package.json");
5851
- if (existsSync17(candidate)) return candidate;
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(readFileSync15(pkgPath, "utf-8"));
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
- log22.info({ sessionId, wantOn }, "Bypass permissions toggled via button");
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
- log22.info(
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 log22, OUTPUT_MODE_LABELS;
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
- log22 = createChildLogger({ module: "telegram-cmd-admin" });
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
- log23.info({ userId: ctx.from?.id, agentName, workspace }, "New session command (direct)");
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
- log23.error({ err }, "Session creation failed");
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 config = core.configManager.get();
6333
- const baseDir = config.workspace.baseDir;
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 log23, WS_CACHE_MAX, workspaceCache, nextWsId, _forceReplyMap;
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
- log23 = createChildLogger({ module: "telegram-cmd-new-session" });
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
- log24.error({ err }, "handleTopics error");
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
- log24.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
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
- log24.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
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
- log24.warn({ err, sessionId: record.sessionId }, "Failed to cancel session during cleanup");
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
- log24.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
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
- log24.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
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 log24;
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
- log24 = createChildLogger({ module: "telegram-cmd-session" });
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 existsSync18, mkdirSync as mkdirSync11, readFileSync as readFileSync16, writeFileSync as writeFileSync11, unlinkSync as unlinkSync7, chmodSync, rmdirSync } from "fs";
6732
- import { join as join21, dirname as dirname11 } from "path";
6733
- import { homedir as homedir10 } from "os";
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(/^~/, homedir10());
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 (existsSync18(fullPath)) {
6894
- const raw = readFileSync16(fullPath, "utf-8");
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(dirname11(fullPath), { recursive: true });
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 (existsSync18(fullPath)) {
6917
- const raw = readFileSync16(fullPath, "utf-8");
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(dirname11(fullPath), { recursive: true });
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 (!existsSync18(fullPath)) return;
6935
- const raw = readFileSync16(fullPath, "utf-8");
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 (!existsSync18(fullPath)) return;
6950
- const raw = readFileSync16(fullPath, "utf-8");
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 = join21(hooksDir, "openacp-inject-session.sh");
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 = join21(hooksDir, "openacp-handoff.sh");
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(join21(spec.commandsPath, spec.handoffCommandName));
7002
+ const skillDir = expandPath(join17(spec.commandsPath, spec.handoffCommandName));
6984
7003
  mkdirSync11(skillDir, { recursive: true });
6985
- const skillPath = join21(skillDir, "SKILL.md");
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 = join21(cmdsDir, `${spec.handoffCommandName}.md`);
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 = join21(hooksDir, "openacp-inject-session.sh");
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 = join21(hooksDir, filename);
7015
- if (existsSync18(filePath)) {
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(join21(spec.commandsPath, spec.handoffCommandName));
7023
- const skillPath = join21(skillDir, "SKILL.md");
7024
- if (existsSync18(skillPath)) {
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(join21(spec.commandsPath, `${spec.handoffCommandName}.md`));
7034
- if (existsSync18(cmdPath)) {
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 = join21(commandsDir, spec.handoffCommandFile);
7076
+ const commandPath = join17(commandsDir, spec.handoffCommandFile);
7058
7077
  const pluginsDir = expandPath(spec.pluginsPath);
7059
7078
  mkdirSync11(pluginsDir, { recursive: true });
7060
- const pluginPath = join21(pluginsDir, spec.pluginFileName);
7061
- if (existsSync18(commandPath) && existsSync18(pluginPath)) {
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 (existsSync18(commandPath) || existsSync18(pluginPath)) {
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 = join21(expandPath(spec.commandsPath), spec.handoffCommandFile);
7100
+ const commandPath = join17(expandPath(spec.commandsPath), spec.handoffCommandFile);
7082
7101
  let removedCount = 0;
7083
- if (existsSync18(commandPath)) {
7102
+ if (existsSync17(commandPath)) {
7084
7103
  unlinkSync7(commandPath);
7085
7104
  logs.push(`Removed ${commandPath}`);
7086
7105
  removedCount += 1;
7087
7106
  }
7088
- const pluginPath = join21(expandPath(spec.pluginsPath), spec.pluginFileName);
7089
- if (existsSync18(pluginPath)) {
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 existsSync18(join21(hooksDir, "openacp-inject-session.sh")) && existsSync18(join21(hooksDir, "openacp-handoff.sh"));
7142
+ return existsSync17(join17(hooksDir, "openacp-inject-session.sh")) && existsSync17(join17(hooksDir, "openacp-handoff.sh"));
7124
7143
  }
7125
- const commandPath = join21(expandPath(spec.commandsPath), spec.handoffCommandFile);
7126
- const pluginPath = join21(expandPath(spec.pluginsPath), spec.pluginFileName);
7127
- return existsSync18(commandPath) && existsSync18(pluginPath);
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 join21(getSkillBasePath(hooksSpec), "openacp-tunnel", "SKILL.md");
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 existsSync18(getTunnelPath());
7168
+ return existsSync17(getTunnelPath());
7150
7169
  },
7151
7170
  async install() {
7152
7171
  const logs = [];
7153
7172
  try {
7154
7173
  const skillPath = getTunnelPath();
7155
- mkdirSync11(dirname11(skillPath), { recursive: true });
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 (existsSync18(skillPath)) {
7187
+ if (existsSync17(skillPath)) {
7169
7188
  unlinkSync7(skillPath);
7170
7189
  try {
7171
- rmdirSync(dirname11(skillPath));
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 log25;
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
- log25 = createChildLogger({ module: "telegram-cmd-resume" });
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
- log26.error({ err, fieldPath }, "Failed to toggle config");
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
- log26.error({ err, fieldPath }, "Failed to set config");
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 log26;
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
- log26 = createChildLogger({ module: "telegram-settings" });
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
- log27.error({ err }, "Doctor command failed");
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
- log27.error({ err, index }, "Doctor fix callback failed");
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 log27, pendingFixesStore;
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
- log27 = createChildLogger({ module: "telegram-cmd-doctor" });
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 log28;
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
- log28 = createChildLogger({ module: "telegram-cmd-tunnel" });
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
- log29.info({ sessionId, agentName, resumed }, "Agent switched via /switch");
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
- log29.warn({ sessionId, agentName, err: err.message }, "Agent switch failed");
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 log29;
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
- log29 = createChildLogger({ module: "telegram-cmd-switch" });
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.get().workspace.baseDir
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
- log30.warn({ itemId }, "Menu item not found in registry");
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 log30, STATIC_COMMANDS;
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
- log30 = createChildLogger({ module: "telegram-menu-callbacks" });
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 log31, PermissionHandler;
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
- log31 = createChildLogger({ module: "telegram-permissions" });
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
- log31.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
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 log32, THINKING_REFRESH_MS, THINKING_MAX_MS, ThinkingIndicator, ToolCard, ActivityTracker2;
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
- log32 = createChildLogger({ module: "telegram:activity" });
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
- log32.warn({ err }, "ThinkingIndicator.show() failed");
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
- log32.warn({ err }, "[ToolCard] send/edit failed");
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
- log32.debug({ toolId: id, status, hasIncomingLinks: !!viewerLinks, hasEntryLinks: !!entry.viewerLinks, entryLinks: entry.viewerLinks }, "toolUpdate: viewer links trace");
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 log33, SkillCommandManager;
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
- log33 = createChildLogger({ module: "skill-commands" });
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
- log33.error({ err, sessionId }, "Failed to send skill commands");
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
- log34.info(
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
- log34.info({ chatId }, "checkTopicsPrerequisites: starting checks");
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
- log34.info(
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
- log34.warn({ err, chatId }, "checkTopicsPrerequisites: getChat failed (network error)");
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
- log34.info(
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
- log34.info({ chatId, issueCount: issues.length, ok: issues.length === 0 }, "checkTopicsPrerequisites: result");
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 log34;
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
- log34 = createChildLogger({ module: "telegram-validators" });
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 log35, TelegramAdapter;
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
- log35 = createChildLogger({ module: "telegram" });
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
- log35.error({ err: rootCause }, "Telegram bot error");
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
- log35.warn(
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: () => log35.info({ chatId: this.telegramConfig.chatId }, "Telegram bot started")
9829
+ onStart: () => log36.info({ chatId: this.telegramConfig.chatId }, "Telegram bot started")
9811
9830
  });
9812
- log35.info(
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
- log35.info("Telegram adapter: prerequisites OK, initializing topic-dependent features");
9845
+ log36.info("Telegram adapter: prerequisites OK, initializing topic-dependent features");
9827
9846
  await this.initTopicDependentFeatures();
9828
9847
  } else {
9829
- log35.warn({ issues: prereqResult.issues }, "Telegram adapter: prerequisites NOT met, starting watcher");
9848
+ log36.warn({ issues: prereqResult.issues }, "Telegram adapter: prerequisites NOT met, starting watcher");
9830
9849
  for (const issue of prereqResult.issues) {
9831
- log35.warn({ issue }, "Telegram prerequisite not met");
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
- log35.warn(
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
- log35.warn({ err }, "Failed to register Telegram commands after retries (non-critical)");
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
- log35.info(
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
- log35.info(
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
- log35.warn({ err, sessionId }, "Failed to send initial messages for new session");
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
- log35.info({ assistantTopicId: this.assistantTopicId }, "initTopicDependentFeatures: sending welcome message");
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
- log35.info("initTopicDependentFeatures: welcome message sent");
9978
+ log36.info("initTopicDependentFeatures: welcome message sent");
9960
9979
  } catch (err) {
9961
- log35.warn({ err }, "Failed to send welcome message");
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
- log35.error({ err }, "Failed to spawn assistant");
9985
+ log36.error({ err }, "Failed to spawn assistant");
9967
9986
  }
9968
9987
  this._topicsInitialized = true;
9969
- log35.info("Telegram adapter fully initialized");
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
- log35.warn({ err }, "Failed to send setup guidance to General topic");
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
- log35.info("Prerequisites met \u2014 completing Telegram adapter initialization");
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
- log35.error({ err }, "Failed to complete initialization after prerequisites met");
10021
+ log36.error({ err }, "Failed to complete initialization after prerequisites met");
10003
10022
  }
10004
10023
  return;
10005
10024
  }
10006
- log35.debug({ issues: result.issues }, "Prerequisites not yet met, retrying");
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
- log35.info("Telegram bot stopped");
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) => log35.error({ err }, "handleMessage error"));
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
- log35.warn(
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
- log35.warn({ err, sessionId }, "Dispatch queue error");
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
- log35.warn({ err, sessionId }, "Failed to send usage message");
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
- log35.warn(
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
- log35.error(
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
- log35.info({ sessionId, requestId: request.id }, "Permission request sent");
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
- log35.info(
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
- log35.info({ sessionId, name }, "Session topic created");
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
- log35.debug({ sessionId, newName }, "Cannot rename thread \u2014 threadId not set yet");
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
- log35.warn(
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
- log35.error({ err }, "Failed to download file from Telegram");
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
- log35.warn({ err }, "OGG\u2192WAV conversion failed, saving original OGG");
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) => log35.error({ err }, "handleMessage error"));
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 fs8 from "fs";
10867
- import path7 from "path";
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 fs5 from "fs";
10872
- import path5 from "path";
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 = fs5.realpathSync(path5.resolve(options.cwd));
10911
+ this.cwd = fs4.realpathSync(path4.resolve(options.cwd));
10893
10912
  } catch {
10894
- this.cwd = path5.resolve(options.cwd);
10913
+ this.cwd = path4.resolve(options.cwd);
10895
10914
  }
10896
10915
  this.allowedPaths = options.allowedPaths.map((p) => {
10897
10916
  try {
10898
- return fs5.realpathSync(path5.resolve(p));
10917
+ return fs4.realpathSync(path4.resolve(p));
10899
10918
  } catch {
10900
- return path5.resolve(p);
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 = path5.resolve(targetPath);
10929
+ const resolved = path4.resolve(targetPath);
10911
10930
  let realPath;
10912
10931
  try {
10913
- realPath = fs5.realpathSync(resolved);
10932
+ realPath = fs4.realpathSync(resolved);
10914
10933
  } catch {
10915
10934
  realPath = resolved;
10916
10935
  }
10917
- if (operation === "write" && path5.basename(realPath) === ".openacpignore") {
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 + path5.sep);
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 + path5.sep)
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 = path5.relative(this.cwd, realPath);
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(fs5.realpathSync(path5.resolve(p)));
10965
+ this.allowedPaths.push(fs4.realpathSync(path4.resolve(p)));
10947
10966
  } catch {
10948
- this.allowedPaths.push(path5.resolve(p));
10967
+ this.allowedPaths.push(path4.resolve(p));
10949
10968
  }
10950
10969
  }
10951
10970
  static loadIgnoreFile(cwd) {
10952
- const ignorePath = path5.join(cwd, ".openacpignore");
10971
+ const ignorePath = path4.join(cwd, ".openacpignore");
10953
10972
  try {
10954
- const content = fs5.readFileSync(ignorePath, "utf-8");
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 fs7 from "fs";
11296
- import path6 from "path";
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 = path6.join(workingDirectory, ".log");
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
- fs7.mkdirSync(this.logDir, { recursive: true });
11328
+ fs6.mkdirSync(this.logDir, { recursive: true });
11310
11329
  this.dirCreated = true;
11311
11330
  }
11312
- const filePath = path6.join(this.logDir, `${this.sessionId}_${layer}.jsonl`);
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
- fs7.appendFileSync(filePath, line);
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 !== path7.dirname(dir)) {
11341
- if (fs8.existsSync(path7.join(dir, "package.json"))) {
11359
+ while (dir !== path6.dirname(dir)) {
11360
+ if (fs7.existsSync(path6.join(dir, "package.json"))) {
11342
11361
  return dir;
11343
11362
  }
11344
- dir = path7.dirname(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
- path7.resolve(root, "node_modules", "@zed-industries", cmd, "dist", "index.js"),
11357
- path7.resolve(root, "node_modules", cmd, "dist", "index.js")
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 (fs8.existsSync(jsPath)) {
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 = path7.resolve(root, "node_modules", ".bin", cmd);
11367
- if (fs8.existsSync(localBin)) {
11368
- const content = fs8.readFileSync(localBin, "utf-8");
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 = path7.resolve(path7.dirname(localBin), match[1]);
11375
- if (fs8.existsSync(target)) {
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 = fs8.readFileSync(fullPath, "utf-8");
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(path7.dirname(process.execPath));
11423
+ addCandidate(path6.dirname(process.execPath));
11405
11424
  try {
11406
- addCandidate(path7.dirname(fs8.realpathSync(process.execPath)));
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 = path7.join(dir, cmd);
11413
- if (fs8.existsSync(candidate)) {
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 instance.connection.newSession({
11581
- cwd: workingDirectory,
11582
- mcpServers: resolvedMcp
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 instance.connection.loadSession({
11612
- sessionId: agentSessionId,
11613
- cwd: workingDirectory,
11614
- mcpServers: resolvedMcp
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 instance.connection.unstable_resumeSession({
11629
- sessionId: agentSessionId,
11630
- cwd: workingDirectory
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 instance.connection.newSession({
11650
- cwd: workingDirectory,
11651
- mcpServers: resolvedMcp
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 fs8.promises.mkdir(path7.dirname(writePath), { recursive: true });
11845
- await fs8.promises.writeFile(writePath, writeContent, "utf-8");
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 fs8.promises.readFile(att.filePath);
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 fs8.promises.readFile(att.filePath);
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 fs9 from "fs";
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 fs9.promises.readFile(audioPath);
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 path8 from "path";
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 = path8.extname(fileInfo.filePath).toLowerCase();
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 fs33 = this.deps.fileService;
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 = fs33.extensionFromMime(mimeType);
13702
- fs33.saveFile(sid, `agent-image${ext}`, buffer, mimeType).then((att) => {
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 fs33 = this.deps.fileService;
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 = fs33.extensionFromMime(mimeType);
13719
- fs33.saveFile(sid, `agent-audio${ext}`, buffer, mimeType).then((att) => {
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 path16 from "path";
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 fs10 from "fs";
14278
- import path9 from "path";
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 = path9.dirname(this.filePath);
14361
- if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
14362
- fs10.writeFileSync(this.filePath, JSON.stringify(data, null, 2));
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 (!fs10.existsSync(this.filePath)) return;
14416
+ if (!fs9.existsSync(this.filePath)) return;
14376
14417
  try {
14377
14418
  const raw = JSON.parse(
14378
- fs10.readFileSync(this.filePath, "utf-8")
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
- fs10.renameSync(this.filePath, `${this.filePath}.bak`);
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
- var log12 = createChildLogger({ module: "agent-catalog" });
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 ?? new AgentStore();
14724
- this.cachePath = cachePath ?? path13.join(os6.homedir(), ".openacp", "registry-cache.json");
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
- log12.info("Fetching agent registry from CDN...");
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
- fs14.mkdirSync(path13.dirname(this.cachePath), { recursive: true });
14752
- fs14.writeFileSync(this.cachePath, JSON.stringify(cache, null, 2), { mode: 384 });
14753
- log12.info({ count: this.registryAgents.length }, "Registry updated");
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
- log12.warn({ err }, "Failed to fetch registry, using cached data");
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 (instance-first, global-fallback) ---
14729
+ // --- Installed ---
14775
14730
  getInstalled() {
14776
- const merged = { ...this.globalStore?.getInstalled(), ...this.store.getInstalled() };
14777
- return Object.values(merged);
14731
+ return Object.values(this.store.getInstalled());
14778
14732
  }
14779
14733
  getInstalledEntries() {
14780
- return { ...this.globalStore?.getInstalled(), ...this.store.getInstalled() };
14734
+ return this.store.getInstalled();
14781
14735
  }
14782
14736
  getInstalledAgent(key) {
14783
- return this.store.getAgent(key) ?? this.globalStore?.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) ?? this.globalStore?.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
- updated = true;
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
- log12.info("Enriched installed agents with registry data");
14879
+ log11.info("Enriched installed agents with registry data");
14921
14880
  }
14922
14881
  }
14923
14882
  isCacheStale() {
14924
- if (!fs14.existsSync(this.cachePath)) return true;
14883
+ if (!fs12.existsSync(this.cachePath)) return true;
14925
14884
  try {
14926
- const raw = JSON.parse(fs14.readFileSync(this.cachePath, "utf-8"));
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 (fs14.existsSync(this.cachePath)) {
14894
+ if (fs12.existsSync(this.cachePath)) {
14936
14895
  try {
14937
- const raw = JSON.parse(fs14.readFileSync(this.cachePath, "utf-8"));
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
- log12.debug({ count: this.registryAgents.length }, "Loaded registry from cache");
14899
+ log11.debug({ count: this.registryAgents.length }, "Loaded registry from cache");
14941
14900
  return;
14942
14901
  }
14943
14902
  } catch {
14944
- log12.warn("Failed to load registry cache");
14903
+ log11.warn("Failed to load registry cache");
14945
14904
  }
14946
14905
  }
14947
14906
  try {
14948
14907
  const candidates = [
14949
- path13.join(import.meta.dirname, "data", "registry-snapshot.json"),
14950
- path13.join(import.meta.dirname, "..", "data", "registry-snapshot.json"),
14951
- path13.join(import.meta.dirname, "..", "..", "data", "registry-snapshot.json")
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 (fs14.existsSync(candidate)) {
14955
- const raw = JSON.parse(fs14.readFileSync(candidate, "utf-8"));
14913
+ if (fs12.existsSync(candidate)) {
14914
+ const raw = JSON.parse(fs12.readFileSync(candidate, "utf-8"));
14956
14915
  this.registryAgents = raw.agents ?? [];
14957
- log12.debug({ count: this.registryAgents.length }, "Loaded registry from bundled snapshot");
14916
+ log11.debug({ count: this.registryAgents.length }, "Loaded registry from bundled snapshot");
14958
14917
  return;
14959
14918
  }
14960
14919
  }
14961
- log12.warn("No registry data available (no cache, no snapshot)");
14920
+ log11.warn("No registry data available (no cache, no snapshot)");
14962
14921
  } catch {
14963
- log12.warn("Failed to load bundled registry snapshot");
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 readFileSync5 } from "fs";
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 fs15 from "fs";
15221
- import path14 from "path";
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 = path14.join(baseDir, "data");
15228
- this.kvPath = path14.join(baseDir, "kv.json");
15229
- fs15.mkdirSync(baseDir, { recursive: true });
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 = fs15.readFileSync(this.kvPath, "utf-8");
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
- fs15.writeFileSync(this.kvPath, JSON.stringify(data), "utf-8");
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
- fs15.mkdirSync(this.dataDir, { recursive: true });
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 ?? path15.join(os7.homedir(), ".openacp");
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 log36 = typeof baseLog.child === "function" ? baseLog.child({ plugin: pluginName }) : baseLog;
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: log36,
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
- log36.debug(`Command '/${def.name}' registered`);
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
- log36.debug(`Registered ${fields.length} editable field(s) for ${pluginName}`);
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 workspace.baseDir ~/code
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 config = core.configManager.get();
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: ${config.workspace.baseDir}
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
- ctx ? new AgentStore(ctx.paths.agents) : void 0,
16163
- ctx?.paths.registryCache,
16164
- ctx?.paths.agentsDir
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?.paths.sessions ?? path16.join(os8.homedir(), ".openacp", "sessions.json");
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?.root
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?.paths.pluginsData ?? path16.join(os8.homedir(), ".openacp", "plugins", "data"),
16193
- instanceRoot: ctx?.root,
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
- if (ctx?.root) {
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: existsSync19 } = await import("fs");
16559
- if (!existsSync19(cwd)) {
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 path28 from "path";
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 fs26 from "fs";
16912
- import * as path24 from "path";
16913
- import * as os11 from "os";
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 LAUNCHD_LABEL = "com.openacp.daemon";
16916
- var LAUNCHD_PLIST_PATH = path24.join(os11.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
16917
- var SYSTEMD_SERVICE_PATH = path24.join(os11.homedir(), ".config", "systemd", "user", "openacp.service");
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 logFile = path24.join(logDir2, "openacp.log");
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>${LAUNCHD_LABEL}</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 installAutoStart(logDir2) {
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 = path24.resolve(process.argv[1]);
16975
- const resolvedLogDir = logDir2.startsWith("~") ? path24.join(os11.homedir(), logDir2.slice(1)) : logDir2;
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 plist = generateLaunchdPlist(nodePath, cliPath, resolvedLogDir);
16979
- const dir = path24.dirname(LAUNCHD_PLIST_PATH);
16980
- fs26.mkdirSync(dir, { recursive: true });
16981
- fs26.writeFileSync(LAUNCHD_PLIST_PATH, plist);
16982
- execFileSync6("launchctl", ["load", LAUNCHD_PLIST_PATH], { stdio: "pipe" });
16983
- log18.info("LaunchAgent installed");
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 unit = generateSystemdUnit(nodePath, cliPath);
16988
- const dir = path24.dirname(SYSTEMD_SERVICE_PATH);
16989
- fs26.mkdirSync(dir, { recursive: true });
16990
- fs26.writeFileSync(SYSTEMD_SERVICE_PATH, unit);
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", "openacp"], { stdio: "pipe" });
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
- if (fs26.existsSync(LAUNCHD_PLIST_PATH)) {
17112
+ const plistPath = getLaunchdPlistPath(instanceId);
17113
+ if (fs25.existsSync(plistPath)) {
17114
+ const uid = process.getuid();
17010
17115
  try {
17011
- execFileSync6("launchctl", ["unload", LAUNCHD_PLIST_PATH], { stdio: "pipe" });
17116
+ execFileSync6("launchctl", ["bootout", `gui/${uid}`, plistPath], { stdio: "pipe" });
17012
17117
  } catch {
17013
17118
  }
17014
- fs26.unlinkSync(LAUNCHD_PLIST_PATH);
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
- if (fs26.existsSync(SYSTEMD_SERVICE_PATH)) {
17125
+ const servicePath = getSystemdServicePath(instanceId);
17126
+ const serviceName = getSystemdServiceName(instanceId);
17127
+ if (fs25.existsSync(servicePath)) {
17021
17128
  try {
17022
- execFileSync6("systemctl", ["--user", "disable", "openacp"], { stdio: "pipe" });
17129
+ execFileSync6("systemctl", ["--user", "disable", serviceName], { stdio: "pipe" });
17023
17130
  } catch {
17024
17131
  }
17025
- fs26.unlinkSync(SYSTEMD_SERVICE_PATH);
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 fs26.existsSync(LAUNCHD_PLIST_PATH);
17147
+ return fs25.existsSync(getLaunchdPlistPath(instanceId));
17041
17148
  }
17042
17149
  if (process.platform === "linux") {
17043
- return fs26.existsSync(SYSTEMD_SERVICE_PATH);
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
- const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
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: root
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 autoStartInstalled = isAutoStartInstalled();
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(expandHome3(logDir2));
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(expandHome3(logDir2));
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: path34, value } of paths) {
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: path34, value })
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 ${path34}: ${data.error}`));
17814
+ console.log(warn(`Failed to update ${path35}: ${data.error}`));
17697
17815
  } else if (data.needsRestart) {
17698
- console.log(warn(`${path34} updated \u2014 restart required`));
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 fs29 from "fs";
17719
- import * as path29 from "path";
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
- const base = root ?? DEFAULT_ROOT2;
17724
- return path29.join(base, "openacp.pid");
17839
+ return path31.join(root, "openacp.pid");
17725
17840
  }
17726
17841
  function getLogDir(root) {
17727
- const base = root ?? DEFAULT_ROOT2;
17728
- return path29.join(base, "logs");
17842
+ return path31.join(root, "logs");
17729
17843
  }
17730
17844
  function getRunningMarker(root) {
17731
- const base = root ?? DEFAULT_ROOT2;
17732
- return path29.join(base, "running");
17845
+ return path31.join(root, "running");
17733
17846
  }
17734
17847
  function writePidFile(pidPath, pid) {
17735
- const dir = path29.dirname(pidPath);
17736
- fs29.mkdirSync(dir, { recursive: true });
17737
- fs29.writeFileSync(pidPath, String(pid));
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 = fs29.readFileSync(pidPath, "utf-8").trim();
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
- fs29.unlinkSync(pidPath);
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 = getPidPath()) {
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 = getPidPath(), logDir2, instanceRoot) {
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 ? expandHome3(logDir2) : getLogDir(instanceRoot);
17783
- fs29.mkdirSync(resolvedLogDir, { recursive: true });
17784
- const logFile = path29.join(resolvedLogDir, "openacp.log");
17785
- const cliPath = path29.resolve(process.argv[1]);
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 = fs29.openSync(logFile, "a");
17788
- const err = fs29.openSync(logFile, "a");
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
- fs29.closeSync(out);
17798
- fs29.closeSync(err);
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 sleep(ms) {
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 = getPidPath(), instanceRoot) {
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 sleep(POLL_INTERVAL);
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 sleep(POLL_INTERVAL);
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
- fs29.mkdirSync(path29.dirname(marker), { recursive: true });
17870
- fs29.writeFileSync(marker, "");
17982
+ fs31.mkdirSync(path31.dirname(marker), { recursive: true });
17983
+ fs31.writeFileSync(marker, "");
17871
17984
  }
17872
17985
  function clearRunning(root) {
17873
17986
  try {
17874
- fs29.unlinkSync(getRunningMarker(root));
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 log21 = createChildLogger({ module: "topic-manager" });
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
- log21.warn({ err, sessionId, topicId }, "Failed to delete platform thread, removing record anyway");
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
- log21.warn({ err, sessionId: record.sessionId }, "Failed to delete platform thread during cleanup");
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
- - **workspace.baseDir** \u2014 Base directory for project folders (default: \`~/openacp-workspace\`)
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
- expandHome3 as expandHome,
18843
+ expandHome2 as expandHome,
18731
18844
  extractContentText,
18732
18845
  formatTokens,
18733
18846
  formatToolSummary,