@openacp/cli 2026.408.3 → 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
@@ -198,11 +198,11 @@ async function shutdownLogger() {
198
198
  logDir = void 0;
199
199
  initialized = false;
200
200
  if (transport) {
201
- await new Promise((resolve6) => {
202
- const timeout = setTimeout(resolve6, 3e3);
201
+ await new Promise((resolve7) => {
202
+ const timeout = setTimeout(resolve7, 3e3);
203
203
  transport.on("close", () => {
204
204
  clearTimeout(timeout);
205
- resolve6();
205
+ resolve7();
206
206
  });
207
207
  transport.end();
208
208
  });
@@ -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,75 +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
- });
294
- import path2 from "path";
295
- import fs2 from "fs";
296
- import os2 from "os";
297
- function createInstanceContext(opts) {
298
- const { id, root, isGlobal } = opts;
299
- return {
300
- id,
301
- root,
302
- isGlobal,
303
- paths: {
304
- config: path2.join(root, "config.json"),
305
- sessions: path2.join(root, "sessions.json"),
306
- agents: path2.join(root, "agents.json"),
307
- registryCache: path2.join(root, "registry-cache.json"),
308
- plugins: path2.join(root, "plugins"),
309
- pluginsData: path2.join(root, "plugins", "data"),
310
- pluginRegistry: path2.join(root, "plugins.json"),
311
- logs: path2.join(root, "logs"),
312
- pid: path2.join(root, "openacp.pid"),
313
- running: path2.join(root, "running"),
314
- apiPort: path2.join(root, "api.port"),
315
- apiSecret: path2.join(root, "api-secret"),
316
- bin: path2.join(root, "bin"),
317
- cache: path2.join(root, "cache"),
318
- tunnels: path2.join(root, "tunnels.json"),
319
- agentsDir: path2.join(root, "agents")
320
- }
321
- };
322
- }
323
- function generateSlug(name) {
324
- const slug = name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
325
- return slug || "openacp";
326
- }
327
- function expandHome2(p) {
328
- if (p.startsWith("~")) return path2.join(os2.homedir(), p.slice(1));
329
- return p;
330
- }
331
- function resolveInstanceRoot(opts) {
332
- const cwd = opts.cwd ?? process.cwd();
333
- if (opts.dir) return path2.join(expandHome2(opts.dir), ".openacp");
334
- if (opts.local) return path2.join(cwd, ".openacp");
335
- if (opts.global) return path2.join(os2.homedir(), ".openacp");
336
- const localRoot = path2.join(cwd, ".openacp");
337
- if (fs2.existsSync(localRoot)) return localRoot;
338
- if (process.env.OPENACP_INSTANCE_ROOT) return process.env.OPENACP_INSTANCE_ROOT;
339
- return null;
340
- }
341
- function getGlobalRoot() {
342
- return path2.join(os2.homedir(), ".openacp");
343
- }
344
- var init_instance_context = __esm({
345
- "src/core/instance/instance-context.ts"() {
346
- "use strict";
347
- }
348
- });
349
-
350
312
  // src/core/config/config-registry.ts
351
313
  var config_registry_exports = {};
352
314
  __export(config_registry_exports, {
@@ -359,24 +321,22 @@ __export(config_registry_exports, {
359
321
  resolveOptions: () => resolveOptions,
360
322
  setFieldValueAsync: () => setFieldValueAsync
361
323
  });
362
- import * as fs3 from "fs";
363
- import * as path3 from "path";
364
- function getFieldDef(path34) {
365
- return CONFIG_REGISTRY.find((f) => f.path === path34);
324
+ function getFieldDef(path35) {
325
+ return CONFIG_REGISTRY.find((f) => f.path === path35);
366
326
  }
367
327
  function getSafeFields() {
368
328
  return CONFIG_REGISTRY.filter((f) => f.scope === "safe");
369
329
  }
370
- function isHotReloadable(path34) {
371
- const def = getFieldDef(path34);
330
+ function isHotReloadable(path35) {
331
+ const def = getFieldDef(path35);
372
332
  return def?.hotReload ?? false;
373
333
  }
374
334
  function resolveOptions(def, config) {
375
335
  if (!def.options) return void 0;
376
336
  return typeof def.options === "function" ? def.options(config) : def.options;
377
337
  }
378
- function getConfigValue(config, path34) {
379
- const parts = path34.split(".");
338
+ function getConfigValue(config, path35) {
339
+ const parts = path35.split(".");
380
340
  let current = config;
381
341
  for (const part of parts) {
382
342
  if (current && typeof current === "object" && part in current) {
@@ -421,7 +381,6 @@ var CONFIG_REGISTRY, ConfigValidationError;
421
381
  var init_config_registry = __esm({
422
382
  "src/core/config/config-registry.ts"() {
423
383
  "use strict";
424
- init_instance_context();
425
384
  CONFIG_REGISTRY = [
426
385
  {
427
386
  path: "defaultAgent",
@@ -429,17 +388,8 @@ var init_config_registry = __esm({
429
388
  group: "agent",
430
389
  type: "select",
431
390
  options: (config) => {
432
- const configAgents = Object.keys(config.agents ?? {});
433
- if (configAgents.length > 0) return configAgents;
434
- try {
435
- const agentsPath = path3.join(getGlobalRoot(), "agents.json");
436
- if (fs3.existsSync(agentsPath)) {
437
- const data = JSON.parse(fs3.readFileSync(agentsPath, "utf-8"));
438
- return Object.keys(data.installed ?? {});
439
- }
440
- } catch {
441
- }
442
- return [];
391
+ const current = config.defaultAgent;
392
+ return current ? [current] : [];
443
393
  },
444
394
  scope: "safe",
445
395
  hotReload: true
@@ -453,14 +403,6 @@ var init_config_registry = __esm({
453
403
  scope: "safe",
454
404
  hotReload: true
455
405
  },
456
- {
457
- path: "workspace.baseDir",
458
- displayName: "Workspace Directory",
459
- group: "workspace",
460
- type: "string",
461
- scope: "safe",
462
- hotReload: true
463
- },
464
406
  {
465
407
  path: "sessionStore.ttlDays",
466
408
  displayName: "Session Store TTL (days)",
@@ -489,14 +431,14 @@ var init_config_registry = __esm({
489
431
 
490
432
  // src/core/config/config.ts
491
433
  import { z } from "zod";
492
- import * as fs4 from "fs";
493
- import * as path4 from "path";
434
+ import * as fs3 from "fs";
435
+ import * as path3 from "path";
494
436
  import * as os3 from "os";
495
437
  import { randomBytes } from "crypto";
496
438
  import { EventEmitter } from "events";
497
- function expandHome3(p) {
439
+ function expandHome2(p) {
498
440
  if (p.startsWith("~")) {
499
- return path4.join(os3.homedir(), p.slice(1));
441
+ return path3.join(os3.homedir(), p.slice(1));
500
442
  }
501
443
  return p;
502
444
  }
@@ -515,10 +457,11 @@ var init_config = __esm({
515
457
  sessionLogRetentionDays: z.number().default(30)
516
458
  }).default({});
517
459
  ConfigSchema = z.object({
460
+ id: z.string().optional(),
461
+ // instance UUID, written once at creation time
518
462
  instanceName: z.string().optional(),
519
463
  defaultAgent: z.string(),
520
464
  workspace: z.object({
521
- baseDir: z.string().default("~/openacp-workspace"),
522
465
  allowExternalWorkspaces: z.boolean().default(true),
523
466
  security: z.object({
524
467
  allowedPaths: z.array(z.string()).default([]),
@@ -545,7 +488,6 @@ var init_config = __esm({
545
488
  });
546
489
  DEFAULT_CONFIG = {
547
490
  defaultAgent: "claude",
548
- workspace: { baseDir: "~/openacp-workspace" },
549
491
  sessionStore: { ttlDays: 30 }
550
492
  };
551
493
  ConfigManager = class extends EventEmitter {
@@ -553,13 +495,13 @@ var init_config = __esm({
553
495
  configPath;
554
496
  constructor(configPath) {
555
497
  super();
556
- 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");
557
499
  }
558
500
  async load() {
559
- const dir = path4.dirname(this.configPath);
560
- fs4.mkdirSync(dir, { recursive: true });
561
- if (!fs4.existsSync(this.configPath)) {
562
- fs4.writeFileSync(
501
+ const dir = path3.dirname(this.configPath);
502
+ fs3.mkdirSync(dir, { recursive: true });
503
+ if (!fs3.existsSync(this.configPath)) {
504
+ fs3.writeFileSync(
563
505
  this.configPath,
564
506
  JSON.stringify(DEFAULT_CONFIG, null, 2)
565
507
  );
@@ -569,10 +511,10 @@ var init_config = __esm({
569
511
  );
570
512
  process.exit(1);
571
513
  }
572
- const raw = JSON.parse(fs4.readFileSync(this.configPath, "utf-8"));
573
- 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) });
574
516
  if (configUpdated) {
575
- fs4.writeFileSync(this.configPath, JSON.stringify(raw, null, 2));
517
+ fs3.writeFileSync(this.configPath, JSON.stringify(raw, null, 2));
576
518
  }
577
519
  this.applyEnvOverrides(raw);
578
520
  const result = ConfigSchema.safeParse(raw);
@@ -593,7 +535,7 @@ var init_config = __esm({
593
535
  }
594
536
  async save(updates, changePath) {
595
537
  const oldConfig = this.config ? structuredClone(this.config) : void 0;
596
- const raw = JSON.parse(fs4.readFileSync(this.configPath, "utf-8"));
538
+ const raw = JSON.parse(fs3.readFileSync(this.configPath, "utf-8"));
597
539
  this.deepMerge(raw, updates);
598
540
  const result = ConfigSchema.safeParse(raw);
599
541
  if (!result.success) {
@@ -601,8 +543,8 @@ var init_config = __esm({
601
543
  return;
602
544
  }
603
545
  const tmpPath = this.configPath + `.tmp.${randomBytes(4).toString("hex")}`;
604
- fs4.writeFileSync(tmpPath, JSON.stringify(raw, null, 2), "utf-8");
605
- fs4.renameSync(tmpPath, this.configPath);
546
+ fs3.writeFileSync(tmpPath, JSON.stringify(raw, null, 2), "utf-8");
547
+ fs3.renameSync(tmpPath, this.configPath);
606
548
  this.config = result.data;
607
549
  if (changePath) {
608
550
  const { getConfigValue: getConfigValue2 } = await Promise.resolve().then(() => (init_config_registry(), config_registry_exports));
@@ -632,54 +574,49 @@ var init_config = __esm({
632
574
  await this.save(updates, dotPath);
633
575
  }
634
576
  resolveWorkspace(input2) {
577
+ const workspaceBase = path3.dirname(path3.dirname(this.configPath));
635
578
  if (!input2) {
636
- const resolved2 = expandHome3(this.config.workspace.baseDir);
637
- fs4.mkdirSync(resolved2, { recursive: true });
638
- return resolved2;
639
- }
640
- if (input2.startsWith("/") || input2.startsWith("~")) {
641
- const resolved2 = expandHome3(input2);
642
- const base = expandHome3(this.config.workspace.baseDir);
643
- 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);
644
587
  if (!isInternal) {
645
588
  if (!this.config.workspace.allowExternalWorkspaces) {
646
589
  throw new Error(
647
- `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.`
648
591
  );
649
592
  }
650
- if (!fs4.existsSync(resolved2)) {
651
- throw new Error(
652
- `Workspace path "${input2}" does not exist.`
653
- );
593
+ if (!fs3.existsSync(resolved)) {
594
+ throw new Error(`Workspace path "${resolved}" does not exist.`);
654
595
  }
655
- return resolved2;
596
+ return resolved;
656
597
  }
657
- fs4.mkdirSync(resolved2, { recursive: true });
658
- return resolved2;
598
+ fs3.mkdirSync(resolved, { recursive: true });
599
+ return resolved;
659
600
  }
660
- const name = input2.replace(/[^a-zA-Z0-9_-]/g, "");
661
- if (name !== input2) {
601
+ if (!/^[a-zA-Z0-9_-]+$/.test(input2)) {
662
602
  throw new Error(
663
603
  `Invalid workspace name: "${input2}". Only alphanumeric characters, hyphens, and underscores are allowed.`
664
604
  );
665
605
  }
666
- const resolved = path4.join(
667
- expandHome3(this.config.workspace.baseDir),
668
- name.toLowerCase()
669
- );
670
- fs4.mkdirSync(resolved, { recursive: true });
671
- return resolved;
606
+ const namedPath = path3.join(workspaceBase, input2.toLowerCase());
607
+ fs3.mkdirSync(namedPath, { recursive: true });
608
+ return namedPath;
672
609
  }
673
610
  async exists() {
674
- return fs4.existsSync(this.configPath);
611
+ return fs3.existsSync(this.configPath);
675
612
  }
676
613
  getConfigPath() {
677
614
  return this.configPath;
678
615
  }
679
616
  async writeNew(config) {
680
- const dir = path4.dirname(this.configPath);
681
- fs4.mkdirSync(dir, { recursive: true });
682
- 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));
683
620
  }
684
621
  async applyEnvToPluginSettings(settingsManager) {
685
622
  const pluginOverrides = [
@@ -762,9 +699,9 @@ var read_text_file_exports = {};
762
699
  __export(read_text_file_exports, {
763
700
  readTextFileWithRange: () => readTextFileWithRange
764
701
  });
765
- import fs6 from "fs";
702
+ import fs5 from "fs";
766
703
  async function readTextFileWithRange(filePath, options) {
767
- const content = await fs6.promises.readFile(filePath, "utf-8");
704
+ const content = await fs5.promises.readFile(filePath, "utf-8");
768
705
  if (!options?.line && !options?.limit) return content;
769
706
  const lines = content.split("\n");
770
707
  const start = Math.max(0, (options.line ?? 1) - 1);
@@ -875,6 +812,25 @@ var init_events = __esm({
875
812
  }
876
813
  });
877
814
 
815
+ // src/core/utils/apply-patch-detection.ts
816
+ function asRecord(value) {
817
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
818
+ }
819
+ function hasApplyPatchPatchText(rawInput) {
820
+ const input2 = asRecord(rawInput);
821
+ return !!input2 && (typeof input2.patchText === "string" || typeof input2.patch_text === "string");
822
+ }
823
+ function isApplyPatchOtherTool(kind, name, rawInput) {
824
+ if (kind !== "other") return false;
825
+ if (name.toLowerCase() === "apply_patch") return true;
826
+ return hasApplyPatchPatchText(rawInput);
827
+ }
828
+ var init_apply_patch_detection = __esm({
829
+ "src/core/utils/apply-patch-detection.ts"() {
830
+ "use strict";
831
+ }
832
+ });
833
+
878
834
  // src/core/utils/bypass-detection.ts
879
835
  function isPermissionBypass(value) {
880
836
  const lower = value.toLowerCase();
@@ -902,8 +858,8 @@ __export(agent_dependencies_exports, {
902
858
  listAgentsWithIntegration: () => listAgentsWithIntegration
903
859
  });
904
860
  import { execFileSync as execFileSync2 } from "child_process";
905
- import * as fs11 from "fs";
906
- import * as path10 from "path";
861
+ import * as fs10 from "fs";
862
+ import * as path9 from "path";
907
863
  function getAgentSetup(registryId) {
908
864
  return AGENT_SETUP[registryId];
909
865
  }
@@ -927,9 +883,9 @@ function commandExists(cmd) {
927
883
  }
928
884
  let dir = process.cwd();
929
885
  while (true) {
930
- const binPath = path10.join(dir, "node_modules", ".bin", cmd);
931
- if (fs11.existsSync(binPath)) return true;
932
- 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);
933
889
  if (parent === dir) break;
934
890
  dir = parent;
935
891
  }
@@ -1202,9 +1158,9 @@ var init_agent_registry = __esm({
1202
1158
  });
1203
1159
 
1204
1160
  // src/core/agents/agent-installer.ts
1205
- import * as fs13 from "fs";
1206
- import * as path12 from "path";
1207
- import * as os5 from "os";
1161
+ import * as fs11 from "fs";
1162
+ import * as path10 from "path";
1163
+ import * as os4 from "os";
1208
1164
  import crypto from "crypto";
1209
1165
  function validateArchiveContents(entries, destDir) {
1210
1166
  for (const entry of entries) {
@@ -1218,9 +1174,9 @@ function validateArchiveContents(entries, destDir) {
1218
1174
  }
1219
1175
  }
1220
1176
  function validateUninstallPath(binaryPath, agentsDir) {
1221
- const realPath = path12.resolve(binaryPath);
1222
- const realAgentsDir = path12.resolve(agentsDir);
1223
- 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) {
1224
1180
  throw new Error(`Refusing to delete path outside agents directory: ${realPath}`);
1225
1181
  }
1226
1182
  }
@@ -1274,7 +1230,7 @@ function buildInstalledAgent(registryId, name, version, dist, binaryPath) {
1274
1230
  binaryPath: null
1275
1231
  };
1276
1232
  }
1277
- const absCmd = path12.resolve(binaryPath, dist.cmd);
1233
+ const absCmd = path10.resolve(binaryPath, dist.cmd);
1278
1234
  return {
1279
1235
  registryId,
1280
1236
  name,
@@ -1340,10 +1296,10 @@ Install it with: pip install uv`;
1340
1296
  return { ok: true, agentKey, setupSteps: setupSteps.length > 0 ? setupSteps : void 0 };
1341
1297
  }
1342
1298
  async function downloadAndExtract(agentId, archiveUrl, progress, agentsDir) {
1343
- const destDir = path12.join(agentsDir ?? DEFAULT_AGENTS_DIR, agentId);
1344
- fs13.mkdirSync(destDir, { recursive: true });
1299
+ const destDir = path10.join(agentsDir ?? DEFAULT_AGENTS_DIR, agentId);
1300
+ fs11.mkdirSync(destDir, { recursive: true });
1345
1301
  await progress?.onStep("Downloading...");
1346
- log11.info({ agentId, url: archiveUrl }, "Downloading agent binary");
1302
+ log10.info({ agentId, url: archiveUrl }, "Downloading agent binary");
1347
1303
  const response = await fetch(archiveUrl);
1348
1304
  if (!response.ok) {
1349
1305
  throw new Error(`Download failed: ${response.status} ${response.statusText}`);
@@ -1382,70 +1338,70 @@ async function readResponseWithProgress(response, contentLength, progress) {
1382
1338
  return Buffer.concat(chunks);
1383
1339
  }
1384
1340
  function validateExtractedPaths(destDir) {
1385
- const realDest = fs13.realpathSync(destDir);
1386
- 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 });
1387
1343
  for (const entry of entries) {
1388
1344
  const dirent = entry;
1389
1345
  const parentPath = dirent.parentPath ?? dirent.path ?? destDir;
1390
- const fullPath = path12.join(parentPath, entry.name);
1346
+ const fullPath = path10.join(parentPath, entry.name);
1391
1347
  let realPath;
1392
1348
  try {
1393
- realPath = fs13.realpathSync(fullPath);
1349
+ realPath = fs11.realpathSync(fullPath);
1394
1350
  } catch {
1395
- const linkTarget = fs13.readlinkSync(fullPath);
1396
- realPath = path12.resolve(path12.dirname(fullPath), linkTarget);
1351
+ const linkTarget = fs11.readlinkSync(fullPath);
1352
+ realPath = path10.resolve(path10.dirname(fullPath), linkTarget);
1397
1353
  }
1398
- if (!realPath.startsWith(realDest + path12.sep) && realPath !== realDest) {
1399
- fs13.rmSync(destDir, { recursive: true, force: true });
1354
+ if (!realPath.startsWith(realDest + path10.sep) && realPath !== realDest) {
1355
+ fs11.rmSync(destDir, { recursive: true, force: true });
1400
1356
  throw new Error(`Archive contains unsafe path: ${entry.name}`);
1401
1357
  }
1402
1358
  }
1403
1359
  }
1404
1360
  async function extractTarGz(buffer, destDir) {
1405
1361
  const { execFileSync: execFileSync8 } = await import("child_process");
1406
- const tmpFile = path12.join(destDir, "_archive.tar.gz");
1407
- fs13.writeFileSync(tmpFile, buffer);
1362
+ const tmpFile = path10.join(destDir, "_archive.tar.gz");
1363
+ fs11.writeFileSync(tmpFile, buffer);
1408
1364
  try {
1409
1365
  const listing = execFileSync8("tar", ["tf", tmpFile], { stdio: "pipe" }).toString().trim().split("\n").filter(Boolean);
1410
1366
  validateArchiveContents(listing, destDir);
1411
1367
  execFileSync8("tar", ["xzf", tmpFile, "-C", destDir], { stdio: "pipe" });
1412
1368
  } finally {
1413
- fs13.unlinkSync(tmpFile);
1369
+ fs11.unlinkSync(tmpFile);
1414
1370
  }
1415
1371
  validateExtractedPaths(destDir);
1416
1372
  }
1417
1373
  async function extractZip(buffer, destDir) {
1418
1374
  const { execFileSync: execFileSync8 } = await import("child_process");
1419
- const tmpFile = path12.join(destDir, "_archive.zip");
1420
- fs13.writeFileSync(tmpFile, buffer);
1375
+ const tmpFile = path10.join(destDir, "_archive.zip");
1376
+ fs11.writeFileSync(tmpFile, buffer);
1421
1377
  try {
1422
1378
  const listing = execFileSync8("unzip", ["-l", tmpFile], { stdio: "pipe" }).toString().trim().split("\n").filter(Boolean);
1423
1379
  const entries = listing.slice(3, -2).map((line) => line.trim().split(/\s+/).slice(3).join(" ")).filter(Boolean);
1424
1380
  validateArchiveContents(entries, destDir);
1425
1381
  execFileSync8("unzip", ["-o", tmpFile, "-d", destDir], { stdio: "pipe" });
1426
1382
  } finally {
1427
- fs13.unlinkSync(tmpFile);
1383
+ fs11.unlinkSync(tmpFile);
1428
1384
  }
1429
1385
  validateExtractedPaths(destDir);
1430
1386
  }
1431
1387
  async function uninstallAgent(agentKey, store, agentsDir) {
1432
1388
  const agent = store.getAgent(agentKey);
1433
1389
  if (!agent) return;
1434
- if (agent.binaryPath && fs13.existsSync(agent.binaryPath)) {
1390
+ if (agent.binaryPath && fs11.existsSync(agent.binaryPath)) {
1435
1391
  validateUninstallPath(agent.binaryPath, agentsDir ?? DEFAULT_AGENTS_DIR);
1436
- fs13.rmSync(agent.binaryPath, { recursive: true, force: true });
1437
- 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");
1438
1394
  }
1439
1395
  store.removeAgent(agentKey);
1440
1396
  }
1441
- 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;
1442
1398
  var init_agent_installer = __esm({
1443
1399
  "src/core/agents/agent-installer.ts"() {
1444
1400
  "use strict";
1445
1401
  init_log();
1446
1402
  init_agent_dependencies();
1447
- log11 = createChildLogger({ module: "agent-installer" });
1448
- 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");
1449
1405
  MAX_DOWNLOAD_SIZE = 500 * 1024 * 1024;
1450
1406
  validateTarContents = validateArchiveContents;
1451
1407
  ARCH_MAP = {
@@ -1461,7 +1417,7 @@ var init_agent_installer = __esm({
1461
1417
  });
1462
1418
 
1463
1419
  // src/core/doctor/checks/config.ts
1464
- import * as fs16 from "fs";
1420
+ import * as fs15 from "fs";
1465
1421
  var configCheck;
1466
1422
  var init_config2 = __esm({
1467
1423
  "src/core/doctor/checks/config.ts"() {
@@ -1473,14 +1429,14 @@ var init_config2 = __esm({
1473
1429
  order: 1,
1474
1430
  async run(ctx) {
1475
1431
  const results = [];
1476
- if (!fs16.existsSync(ctx.configPath)) {
1432
+ if (!fs15.existsSync(ctx.configPath)) {
1477
1433
  results.push({ status: "fail", message: "Config file not found" });
1478
1434
  return results;
1479
1435
  }
1480
1436
  results.push({ status: "pass", message: "Config file exists" });
1481
1437
  let raw;
1482
1438
  try {
1483
- raw = JSON.parse(fs16.readFileSync(ctx.configPath, "utf-8"));
1439
+ raw = JSON.parse(fs15.readFileSync(ctx.configPath, "utf-8"));
1484
1440
  } catch (err) {
1485
1441
  results.push({
1486
1442
  status: "fail",
@@ -1499,7 +1455,7 @@ var init_config2 = __esm({
1499
1455
  fixRisk: "safe",
1500
1456
  fix: async () => {
1501
1457
  applyMigrations(raw);
1502
- fs16.writeFileSync(ctx.configPath, JSON.stringify(raw, null, 2));
1458
+ fs15.writeFileSync(ctx.configPath, JSON.stringify(raw, null, 2));
1503
1459
  return { success: true, message: "applied migrations" };
1504
1460
  }
1505
1461
  });
@@ -1523,8 +1479,8 @@ var init_config2 = __esm({
1523
1479
 
1524
1480
  // src/core/doctor/checks/agents.ts
1525
1481
  import { execFileSync as execFileSync3 } from "child_process";
1526
- import * as fs17 from "fs";
1527
- import * as path17 from "path";
1482
+ import * as fs16 from "fs";
1483
+ import * as path15 from "path";
1528
1484
  function commandExists2(cmd) {
1529
1485
  try {
1530
1486
  execFileSync3("which", [cmd], { stdio: "pipe" });
@@ -1533,9 +1489,9 @@ function commandExists2(cmd) {
1533
1489
  }
1534
1490
  let dir = process.cwd();
1535
1491
  while (true) {
1536
- const binPath = path17.join(dir, "node_modules", ".bin", cmd);
1537
- if (fs17.existsSync(binPath)) return true;
1538
- 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);
1539
1495
  if (parent === dir) break;
1540
1496
  dir = parent;
1541
1497
  }
@@ -1557,9 +1513,9 @@ var init_agents = __esm({
1557
1513
  const defaultAgent = ctx.config.defaultAgent;
1558
1514
  let agents = {};
1559
1515
  try {
1560
- const agentsPath = path17.join(ctx.dataDir, "agents.json");
1561
- if (fs17.existsSync(agentsPath)) {
1562
- 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"));
1563
1519
  agents = data.installed ?? {};
1564
1520
  }
1565
1521
  } catch {
@@ -1597,8 +1553,8 @@ var settings_manager_exports = {};
1597
1553
  __export(settings_manager_exports, {
1598
1554
  SettingsManager: () => SettingsManager
1599
1555
  });
1600
- import fs18 from "fs";
1601
- import path18 from "path";
1556
+ import fs17 from "fs";
1557
+ import path16 from "path";
1602
1558
  var SettingsManager, SettingsAPIImpl;
1603
1559
  var init_settings_manager = __esm({
1604
1560
  "src/core/plugin/settings-manager.ts"() {
@@ -1617,7 +1573,7 @@ var init_settings_manager = __esm({
1617
1573
  async loadSettings(pluginName) {
1618
1574
  const settingsPath = this.getSettingsPath(pluginName);
1619
1575
  try {
1620
- const content = fs18.readFileSync(settingsPath, "utf-8");
1576
+ const content = fs17.readFileSync(settingsPath, "utf-8");
1621
1577
  return JSON.parse(content);
1622
1578
  } catch {
1623
1579
  return {};
@@ -1635,7 +1591,7 @@ var init_settings_manager = __esm({
1635
1591
  };
1636
1592
  }
1637
1593
  getSettingsPath(pluginName) {
1638
- return path18.join(this.basePath, pluginName, "settings.json");
1594
+ return path16.join(this.basePath, pluginName, "settings.json");
1639
1595
  }
1640
1596
  async getPluginSettings(pluginName) {
1641
1597
  return this.loadSettings(pluginName);
@@ -1654,7 +1610,7 @@ var init_settings_manager = __esm({
1654
1610
  readFile() {
1655
1611
  if (this.cache !== null) return this.cache;
1656
1612
  try {
1657
- const content = fs18.readFileSync(this.settingsPath, "utf-8");
1613
+ const content = fs17.readFileSync(this.settingsPath, "utf-8");
1658
1614
  this.cache = JSON.parse(content);
1659
1615
  return this.cache;
1660
1616
  } catch {
@@ -1663,9 +1619,9 @@ var init_settings_manager = __esm({
1663
1619
  }
1664
1620
  }
1665
1621
  writeFile(data) {
1666
- const dir = path18.dirname(this.settingsPath);
1667
- fs18.mkdirSync(dir, { recursive: true });
1668
- 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));
1669
1625
  this.cache = data;
1670
1626
  }
1671
1627
  async get(key) {
@@ -1700,7 +1656,7 @@ var init_settings_manager = __esm({
1700
1656
  });
1701
1657
 
1702
1658
  // src/core/doctor/checks/telegram.ts
1703
- import * as path19 from "path";
1659
+ import * as path17 from "path";
1704
1660
  var BOT_TOKEN_REGEX, telegramCheck;
1705
1661
  var init_telegram = __esm({
1706
1662
  "src/core/doctor/checks/telegram.ts"() {
@@ -1716,7 +1672,7 @@ var init_telegram = __esm({
1716
1672
  return results;
1717
1673
  }
1718
1674
  const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
1719
- const sm = new SettingsManager2(path19.join(ctx.pluginsDir, "data"));
1675
+ const sm = new SettingsManager2(path17.join(ctx.pluginsDir, "data"));
1720
1676
  const ps = await sm.loadSettings("@openacp/telegram");
1721
1677
  const botToken = ps.botToken;
1722
1678
  const chatId = ps.chatId;
@@ -1799,7 +1755,7 @@ var init_telegram = __esm({
1799
1755
  });
1800
1756
 
1801
1757
  // src/core/doctor/checks/storage.ts
1802
- import * as fs19 from "fs";
1758
+ import * as fs18 from "fs";
1803
1759
  var storageCheck;
1804
1760
  var init_storage = __esm({
1805
1761
  "src/core/doctor/checks/storage.ts"() {
@@ -1809,28 +1765,28 @@ var init_storage = __esm({
1809
1765
  order: 4,
1810
1766
  async run(ctx) {
1811
1767
  const results = [];
1812
- if (!fs19.existsSync(ctx.dataDir)) {
1768
+ if (!fs18.existsSync(ctx.dataDir)) {
1813
1769
  results.push({
1814
1770
  status: "fail",
1815
1771
  message: "Data directory ~/.openacp does not exist",
1816
1772
  fixable: true,
1817
1773
  fixRisk: "safe",
1818
1774
  fix: async () => {
1819
- fs19.mkdirSync(ctx.dataDir, { recursive: true });
1775
+ fs18.mkdirSync(ctx.dataDir, { recursive: true });
1820
1776
  return { success: true, message: "created directory" };
1821
1777
  }
1822
1778
  });
1823
1779
  } else {
1824
1780
  try {
1825
- fs19.accessSync(ctx.dataDir, fs19.constants.W_OK);
1781
+ fs18.accessSync(ctx.dataDir, fs18.constants.W_OK);
1826
1782
  results.push({ status: "pass", message: "Data directory exists and writable" });
1827
1783
  } catch {
1828
1784
  results.push({ status: "fail", message: "Data directory not writable" });
1829
1785
  }
1830
1786
  }
1831
- if (fs19.existsSync(ctx.sessionsPath)) {
1787
+ if (fs18.existsSync(ctx.sessionsPath)) {
1832
1788
  try {
1833
- const content = fs19.readFileSync(ctx.sessionsPath, "utf-8");
1789
+ const content = fs18.readFileSync(ctx.sessionsPath, "utf-8");
1834
1790
  const data = JSON.parse(content);
1835
1791
  if (typeof data === "object" && data !== null && "sessions" in data) {
1836
1792
  results.push({ status: "pass", message: "Sessions file valid" });
@@ -1841,7 +1797,7 @@ var init_storage = __esm({
1841
1797
  fixable: true,
1842
1798
  fixRisk: "risky",
1843
1799
  fix: async () => {
1844
- fs19.writeFileSync(ctx.sessionsPath, JSON.stringify({ version: 1, sessions: {} }, null, 2));
1800
+ fs18.writeFileSync(ctx.sessionsPath, JSON.stringify({ version: 1, sessions: {} }, null, 2));
1845
1801
  return { success: true, message: "reset sessions file" };
1846
1802
  }
1847
1803
  });
@@ -1853,7 +1809,7 @@ var init_storage = __esm({
1853
1809
  fixable: true,
1854
1810
  fixRisk: "risky",
1855
1811
  fix: async () => {
1856
- fs19.writeFileSync(ctx.sessionsPath, JSON.stringify({ version: 1, sessions: {} }, null, 2));
1812
+ fs18.writeFileSync(ctx.sessionsPath, JSON.stringify({ version: 1, sessions: {} }, null, 2));
1857
1813
  return { success: true, message: "reset sessions file" };
1858
1814
  }
1859
1815
  });
@@ -1861,20 +1817,20 @@ var init_storage = __esm({
1861
1817
  } else {
1862
1818
  results.push({ status: "pass", message: "Sessions file not present yet (created on first session)" });
1863
1819
  }
1864
- if (!fs19.existsSync(ctx.logsDir)) {
1820
+ if (!fs18.existsSync(ctx.logsDir)) {
1865
1821
  results.push({
1866
1822
  status: "warn",
1867
1823
  message: "Log directory does not exist",
1868
1824
  fixable: true,
1869
1825
  fixRisk: "safe",
1870
1826
  fix: async () => {
1871
- fs19.mkdirSync(ctx.logsDir, { recursive: true });
1827
+ fs18.mkdirSync(ctx.logsDir, { recursive: true });
1872
1828
  return { success: true, message: "created log directory" };
1873
1829
  }
1874
1830
  });
1875
1831
  } else {
1876
1832
  try {
1877
- fs19.accessSync(ctx.logsDir, fs19.constants.W_OK);
1833
+ fs18.accessSync(ctx.logsDir, fs18.constants.W_OK);
1878
1834
  results.push({ status: "pass", message: "Log directory exists and writable" });
1879
1835
  } catch {
1880
1836
  results.push({ status: "fail", message: "Log directory not writable" });
@@ -1887,39 +1843,35 @@ var init_storage = __esm({
1887
1843
  });
1888
1844
 
1889
1845
  // src/core/doctor/checks/workspace.ts
1890
- import * as fs20 from "fs";
1846
+ import * as fs19 from "fs";
1847
+ import * as path18 from "path";
1891
1848
  var workspaceCheck;
1892
1849
  var init_workspace = __esm({
1893
1850
  "src/core/doctor/checks/workspace.ts"() {
1894
1851
  "use strict";
1895
- init_config();
1896
1852
  workspaceCheck = {
1897
1853
  name: "Workspace",
1898
1854
  order: 5,
1899
1855
  async run(ctx) {
1900
1856
  const results = [];
1901
- if (!ctx.config) {
1902
- results.push({ status: "fail", message: "Cannot check workspace \u2014 config not loaded" });
1903
- return results;
1904
- }
1905
- const baseDir = expandHome3(ctx.config.workspace.baseDir);
1906
- if (!fs20.existsSync(baseDir)) {
1857
+ const workspace = path18.dirname(ctx.dataDir);
1858
+ if (!fs19.existsSync(workspace)) {
1907
1859
  results.push({
1908
1860
  status: "warn",
1909
- message: `Workspace directory does not exist: ${baseDir}`,
1861
+ message: `Workspace directory does not exist: ${workspace}`,
1910
1862
  fixable: true,
1911
1863
  fixRisk: "safe",
1912
1864
  fix: async () => {
1913
- fs20.mkdirSync(baseDir, { recursive: true });
1865
+ fs19.mkdirSync(workspace, { recursive: true });
1914
1866
  return { success: true, message: "created directory" };
1915
1867
  }
1916
1868
  });
1917
1869
  } else {
1918
1870
  try {
1919
- fs20.accessSync(baseDir, fs20.constants.W_OK);
1920
- 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}` });
1921
1873
  } catch {
1922
- results.push({ status: "fail", message: `Workspace directory not writable: ${baseDir}` });
1874
+ results.push({ status: "fail", message: `Workspace directory not writable: ${workspace}` });
1923
1875
  }
1924
1876
  }
1925
1877
  return results;
@@ -1929,8 +1881,8 @@ var init_workspace = __esm({
1929
1881
  });
1930
1882
 
1931
1883
  // src/core/doctor/checks/plugins.ts
1932
- import * as fs21 from "fs";
1933
- import * as path20 from "path";
1884
+ import * as fs20 from "fs";
1885
+ import * as path19 from "path";
1934
1886
  var pluginsCheck;
1935
1887
  var init_plugins = __esm({
1936
1888
  "src/core/doctor/checks/plugins.ts"() {
@@ -1940,16 +1892,16 @@ var init_plugins = __esm({
1940
1892
  order: 6,
1941
1893
  async run(ctx) {
1942
1894
  const results = [];
1943
- if (!fs21.existsSync(ctx.pluginsDir)) {
1895
+ if (!fs20.existsSync(ctx.pluginsDir)) {
1944
1896
  results.push({
1945
1897
  status: "warn",
1946
1898
  message: "Plugins directory does not exist",
1947
1899
  fixable: true,
1948
1900
  fixRisk: "safe",
1949
1901
  fix: async () => {
1950
- fs21.mkdirSync(ctx.pluginsDir, { recursive: true });
1951
- fs21.writeFileSync(
1952
- path20.join(ctx.pluginsDir, "package.json"),
1902
+ fs20.mkdirSync(ctx.pluginsDir, { recursive: true });
1903
+ fs20.writeFileSync(
1904
+ path19.join(ctx.pluginsDir, "package.json"),
1953
1905
  JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
1954
1906
  );
1955
1907
  return { success: true, message: "initialized plugins directory" };
@@ -1958,15 +1910,15 @@ var init_plugins = __esm({
1958
1910
  return results;
1959
1911
  }
1960
1912
  results.push({ status: "pass", message: "Plugins directory exists" });
1961
- const pkgPath = path20.join(ctx.pluginsDir, "package.json");
1962
- if (!fs21.existsSync(pkgPath)) {
1913
+ const pkgPath = path19.join(ctx.pluginsDir, "package.json");
1914
+ if (!fs20.existsSync(pkgPath)) {
1963
1915
  results.push({
1964
1916
  status: "warn",
1965
1917
  message: "Plugins package.json missing",
1966
1918
  fixable: true,
1967
1919
  fixRisk: "safe",
1968
1920
  fix: async () => {
1969
- fs21.writeFileSync(
1921
+ fs20.writeFileSync(
1970
1922
  pkgPath,
1971
1923
  JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
1972
1924
  );
@@ -1976,7 +1928,7 @@ var init_plugins = __esm({
1976
1928
  return results;
1977
1929
  }
1978
1930
  try {
1979
- const pkg = JSON.parse(fs21.readFileSync(pkgPath, "utf-8"));
1931
+ const pkg = JSON.parse(fs20.readFileSync(pkgPath, "utf-8"));
1980
1932
  const deps = pkg.dependencies || {};
1981
1933
  const count = Object.keys(deps).length;
1982
1934
  results.push({ status: "pass", message: `Plugins package.json valid (${count} plugins)` });
@@ -1987,7 +1939,7 @@ var init_plugins = __esm({
1987
1939
  fixable: true,
1988
1940
  fixRisk: "risky",
1989
1941
  fix: async () => {
1990
- fs21.writeFileSync(
1942
+ fs20.writeFileSync(
1991
1943
  pkgPath,
1992
1944
  JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
1993
1945
  );
@@ -2002,7 +1954,7 @@ var init_plugins = __esm({
2002
1954
  });
2003
1955
 
2004
1956
  // src/core/doctor/checks/daemon.ts
2005
- import * as fs22 from "fs";
1957
+ import * as fs21 from "fs";
2006
1958
  import * as net from "net";
2007
1959
  function isProcessAlive(pid) {
2008
1960
  try {
@@ -2013,12 +1965,12 @@ function isProcessAlive(pid) {
2013
1965
  }
2014
1966
  }
2015
1967
  function checkPortInUse(port) {
2016
- return new Promise((resolve6) => {
1968
+ return new Promise((resolve7) => {
2017
1969
  const server = net.createServer();
2018
- server.once("error", () => resolve6(true));
1970
+ server.once("error", () => resolve7(true));
2019
1971
  server.once("listening", () => {
2020
1972
  server.close();
2021
- resolve6(false);
1973
+ resolve7(false);
2022
1974
  });
2023
1975
  server.listen(port, "127.0.0.1");
2024
1976
  });
@@ -2032,8 +1984,8 @@ var init_daemon = __esm({
2032
1984
  order: 7,
2033
1985
  async run(ctx) {
2034
1986
  const results = [];
2035
- if (fs22.existsSync(ctx.pidPath)) {
2036
- 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();
2037
1989
  const pid = parseInt(content, 10);
2038
1990
  if (isNaN(pid)) {
2039
1991
  results.push({
@@ -2042,7 +1994,7 @@ var init_daemon = __esm({
2042
1994
  fixable: true,
2043
1995
  fixRisk: "safe",
2044
1996
  fix: async () => {
2045
- fs22.unlinkSync(ctx.pidPath);
1997
+ fs21.unlinkSync(ctx.pidPath);
2046
1998
  return { success: true, message: "removed invalid PID file" };
2047
1999
  }
2048
2000
  });
@@ -2053,7 +2005,7 @@ var init_daemon = __esm({
2053
2005
  fixable: true,
2054
2006
  fixRisk: "safe",
2055
2007
  fix: async () => {
2056
- fs22.unlinkSync(ctx.pidPath);
2008
+ fs21.unlinkSync(ctx.pidPath);
2057
2009
  return { success: true, message: "removed stale PID file" };
2058
2010
  }
2059
2011
  });
@@ -2061,8 +2013,8 @@ var init_daemon = __esm({
2061
2013
  results.push({ status: "pass", message: `Daemon running (PID ${pid})` });
2062
2014
  }
2063
2015
  }
2064
- if (fs22.existsSync(ctx.portFilePath)) {
2065
- 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();
2066
2018
  const port = parseInt(content, 10);
2067
2019
  if (isNaN(port)) {
2068
2020
  results.push({
@@ -2071,7 +2023,7 @@ var init_daemon = __esm({
2071
2023
  fixable: true,
2072
2024
  fixRisk: "safe",
2073
2025
  fix: async () => {
2074
- fs22.unlinkSync(ctx.portFilePath);
2026
+ fs21.unlinkSync(ctx.portFilePath);
2075
2027
  return { success: true, message: "removed invalid port file" };
2076
2028
  }
2077
2029
  });
@@ -2083,8 +2035,8 @@ var init_daemon = __esm({
2083
2035
  const apiPort = 21420;
2084
2036
  const inUse = await checkPortInUse(apiPort);
2085
2037
  if (inUse) {
2086
- if (fs22.existsSync(ctx.pidPath)) {
2087
- 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);
2088
2040
  if (!isNaN(pid) && isProcessAlive(pid)) {
2089
2041
  results.push({ status: "pass", message: `API port ${apiPort} in use by OpenACP daemon` });
2090
2042
  } else {
@@ -2104,17 +2056,17 @@ var init_daemon = __esm({
2104
2056
  });
2105
2057
 
2106
2058
  // src/core/utils/install-binary.ts
2107
- import fs23 from "fs";
2108
- import path21 from "path";
2059
+ import fs22 from "fs";
2060
+ import path20 from "path";
2109
2061
  import https from "https";
2110
- import os9 from "os";
2062
+ import os5 from "os";
2111
2063
  import { execFileSync as execFileSync4 } from "child_process";
2112
2064
  function downloadFile(url, dest, maxRedirects = 10) {
2113
- return new Promise((resolve6, reject) => {
2114
- const file = fs23.createWriteStream(dest);
2065
+ return new Promise((resolve7, reject) => {
2066
+ const file = fs22.createWriteStream(dest);
2115
2067
  const cleanup = () => {
2116
2068
  try {
2117
- if (fs23.existsSync(dest)) fs23.unlinkSync(dest);
2069
+ if (fs22.existsSync(dest)) fs22.unlinkSync(dest);
2118
2070
  } catch {
2119
2071
  }
2120
2072
  };
@@ -2129,7 +2081,7 @@ function downloadFile(url, dest, maxRedirects = 10) {
2129
2081
  }
2130
2082
  file.close(() => {
2131
2083
  cleanup();
2132
- downloadFile(response.headers.location, dest, maxRedirects - 1).then(resolve6).catch(reject);
2084
+ downloadFile(response.headers.location, dest, maxRedirects - 1).then(resolve7).catch(reject);
2133
2085
  });
2134
2086
  return;
2135
2087
  }
@@ -2153,7 +2105,7 @@ function downloadFile(url, dest, maxRedirects = 10) {
2153
2105
  }
2154
2106
  });
2155
2107
  response.pipe(file);
2156
- file.on("finish", () => file.close(() => resolve6(dest)));
2108
+ file.on("finish", () => file.close(() => resolve7(dest)));
2157
2109
  file.on("error", (err) => {
2158
2110
  file.close(() => {
2159
2111
  cleanup();
@@ -2169,8 +2121,8 @@ function downloadFile(url, dest, maxRedirects = 10) {
2169
2121
  });
2170
2122
  }
2171
2123
  function getDownloadUrl(spec) {
2172
- const platform2 = os9.platform();
2173
- const arch = os9.arch();
2124
+ const platform2 = os5.platform();
2125
+ const arch = os5.arch();
2174
2126
  const mapping = spec.platforms[platform2];
2175
2127
  if (!mapping) throw new Error(`${spec.name}: unsupported platform ${platform2}`);
2176
2128
  const binary = mapping[arch];
@@ -2180,36 +2132,36 @@ function getDownloadUrl(spec) {
2180
2132
  async function ensureBinary(spec, binDir) {
2181
2133
  const resolvedBinDir = binDir ?? DEFAULT_BIN_DIR;
2182
2134
  const binName = IS_WINDOWS ? `${spec.name}.exe` : spec.name;
2183
- const binPath = path21.join(resolvedBinDir, binName);
2135
+ const binPath = path20.join(resolvedBinDir, binName);
2184
2136
  if (commandExists(spec.name)) {
2185
2137
  log17.debug({ name: spec.name }, "Found in PATH");
2186
2138
  return spec.name;
2187
2139
  }
2188
- if (fs23.existsSync(binPath)) {
2189
- if (!IS_WINDOWS) fs23.chmodSync(binPath, "755");
2140
+ if (fs22.existsSync(binPath)) {
2141
+ if (!IS_WINDOWS) fs22.chmodSync(binPath, "755");
2190
2142
  log17.debug({ name: spec.name, path: binPath }, "Found in ~/.openacp/bin");
2191
2143
  return binPath;
2192
2144
  }
2193
2145
  log17.info({ name: spec.name }, "Not found, downloading from GitHub...");
2194
- fs23.mkdirSync(resolvedBinDir, { recursive: true });
2146
+ fs22.mkdirSync(resolvedBinDir, { recursive: true });
2195
2147
  const url = getDownloadUrl(spec);
2196
2148
  const isArchive = spec.isArchive?.(url) ?? false;
2197
- const downloadDest = isArchive ? path21.join(resolvedBinDir, `${spec.name}.tgz`) : binPath;
2149
+ const downloadDest = isArchive ? path20.join(resolvedBinDir, `${spec.name}.tgz`) : binPath;
2198
2150
  await downloadFile(url, downloadDest);
2199
2151
  if (isArchive) {
2200
2152
  const listing = execFileSync4("tar", ["-tf", downloadDest], { stdio: "pipe" }).toString().trim().split("\n").filter(Boolean);
2201
2153
  validateTarContents(listing, resolvedBinDir);
2202
2154
  execFileSync4("tar", ["-xzf", downloadDest, "-C", resolvedBinDir], { stdio: "pipe" });
2203
2155
  try {
2204
- fs23.unlinkSync(downloadDest);
2156
+ fs22.unlinkSync(downloadDest);
2205
2157
  } catch {
2206
2158
  }
2207
2159
  }
2208
- if (!fs23.existsSync(binPath)) {
2160
+ if (!fs22.existsSync(binPath)) {
2209
2161
  throw new Error(`${spec.name}: binary not found at ${binPath} after download/extraction. The archive structure may have changed.`);
2210
2162
  }
2211
2163
  if (!IS_WINDOWS) {
2212
- fs23.chmodSync(binPath, "755");
2164
+ fs22.chmodSync(binPath, "755");
2213
2165
  }
2214
2166
  log17.info({ name: spec.name, path: binPath }, "Installed successfully");
2215
2167
  return binPath;
@@ -2222,8 +2174,8 @@ var init_install_binary = __esm({
2222
2174
  init_agent_dependencies();
2223
2175
  init_agent_installer();
2224
2176
  log17 = createChildLogger({ module: "binary-installer" });
2225
- DEFAULT_BIN_DIR = path21.join(os9.homedir(), ".openacp", "bin");
2226
- IS_WINDOWS = os9.platform() === "win32";
2177
+ DEFAULT_BIN_DIR = path20.join(os5.homedir(), ".openacp", "bin");
2178
+ IS_WINDOWS = os5.platform() === "win32";
2227
2179
  }
2228
2180
  });
2229
2181
 
@@ -2263,9 +2215,9 @@ var init_install_cloudflared = __esm({
2263
2215
  });
2264
2216
 
2265
2217
  // src/core/doctor/checks/tunnel.ts
2266
- import * as fs24 from "fs";
2267
- import * as path22 from "path";
2268
- import * as os10 from "os";
2218
+ import * as fs23 from "fs";
2219
+ import * as path21 from "path";
2220
+ import * as os6 from "os";
2269
2221
  import { execFileSync as execFileSync5 } from "child_process";
2270
2222
  var tunnelCheck;
2271
2223
  var init_tunnel = __esm({
@@ -2281,7 +2233,7 @@ var init_tunnel = __esm({
2281
2233
  return results;
2282
2234
  }
2283
2235
  const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
2284
- const sm = new SettingsManager2(path22.join(ctx.pluginsDir, "data"));
2236
+ const sm = new SettingsManager2(path21.join(ctx.pluginsDir, "data"));
2285
2237
  const tunnelSettings = await sm.loadSettings("@openacp/tunnel");
2286
2238
  const tunnelEnabled = tunnelSettings.enabled ?? false;
2287
2239
  const provider = tunnelSettings.provider ?? "cloudflare";
@@ -2292,10 +2244,10 @@ var init_tunnel = __esm({
2292
2244
  }
2293
2245
  results.push({ status: "pass", message: `Tunnel provider: ${provider}` });
2294
2246
  if (provider === "cloudflare") {
2295
- const binName = os10.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
2296
- 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);
2297
2249
  let found = false;
2298
- if (fs24.existsSync(binPath)) {
2250
+ if (fs23.existsSync(binPath)) {
2299
2251
  found = true;
2300
2252
  } else {
2301
2253
  try {
@@ -2336,14 +2288,13 @@ var init_tunnel = __esm({
2336
2288
  });
2337
2289
 
2338
2290
  // src/core/doctor/index.ts
2339
- import * as fs25 from "fs";
2340
- import * as path23 from "path";
2291
+ import * as fs24 from "fs";
2292
+ import * as path22 from "path";
2341
2293
  var ALL_CHECKS, CHECK_TIMEOUT_MS, DoctorEngine;
2342
2294
  var init_doctor = __esm({
2343
2295
  "src/core/doctor/index.ts"() {
2344
2296
  "use strict";
2345
2297
  init_config();
2346
- init_instance_context();
2347
2298
  init_config2();
2348
2299
  init_agents();
2349
2300
  init_telegram();
@@ -2368,7 +2319,7 @@ var init_doctor = __esm({
2368
2319
  dataDir;
2369
2320
  constructor(options) {
2370
2321
  this.dryRun = options?.dryRun ?? false;
2371
- this.dataDir = options?.dataDir ?? getGlobalRoot();
2322
+ this.dataDir = options.dataDir;
2372
2323
  }
2373
2324
  async runAll() {
2374
2325
  const ctx = await this.buildContext();
@@ -2419,27 +2370,27 @@ var init_doctor = __esm({
2419
2370
  }
2420
2371
  async buildContext() {
2421
2372
  const dataDir = this.dataDir;
2422
- 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");
2423
2374
  let config = null;
2424
2375
  let rawConfig = null;
2425
2376
  try {
2426
- const content = fs25.readFileSync(configPath, "utf-8");
2377
+ const content = fs24.readFileSync(configPath, "utf-8");
2427
2378
  rawConfig = JSON.parse(content);
2428
2379
  const cm = new ConfigManager(configPath);
2429
2380
  await cm.load();
2430
2381
  config = cm.get();
2431
2382
  } catch {
2432
2383
  }
2433
- const logsDir = config ? expandHome3(config.logging.logDir) : path23.join(dataDir, "logs");
2384
+ const logsDir = config ? expandHome2(config.logging.logDir) : path22.join(dataDir, "logs");
2434
2385
  return {
2435
2386
  config,
2436
2387
  rawConfig,
2437
2388
  configPath,
2438
2389
  dataDir,
2439
- sessionsPath: path23.join(dataDir, "sessions.json"),
2440
- pidPath: path23.join(dataDir, "openacp.pid"),
2441
- portFilePath: path23.join(dataDir, "api.port"),
2442
- 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"),
2443
2394
  logsDir
2444
2395
  };
2445
2396
  }
@@ -2447,6 +2398,123 @@ var init_doctor = __esm({
2447
2398
  }
2448
2399
  });
2449
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
+
2450
2518
  // src/core/plugin/plugin-installer.ts
2451
2519
  var plugin_installer_exports = {};
2452
2520
  __export(plugin_installer_exports, {
@@ -2455,16 +2523,15 @@ __export(plugin_installer_exports, {
2455
2523
  });
2456
2524
  import { execFile } from "child_process";
2457
2525
  import { promisify } from "util";
2458
- import * as fs27 from "fs/promises";
2459
- import * as os12 from "os";
2460
- import * as path25 from "path";
2526
+ import * as fs29 from "fs/promises";
2527
+ import * as path27 from "path";
2461
2528
  import { pathToFileURL } from "url";
2462
2529
  async function importFromDir(packageName, dir) {
2463
- const pkgDir = path25.join(dir, "node_modules", ...packageName.split("/"));
2464
- 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");
2465
2532
  let pkgJson;
2466
2533
  try {
2467
- pkgJson = JSON.parse(await fs27.readFile(pkgJsonPath, "utf-8"));
2534
+ pkgJson = JSON.parse(await fs29.readFile(pkgJsonPath, "utf-8"));
2468
2535
  } catch (err) {
2469
2536
  throw new Error(`Cannot read package.json for "${packageName}" at ${pkgJsonPath}: ${err.message}`);
2470
2537
  }
@@ -2477,9 +2544,9 @@ async function importFromDir(packageName, dir) {
2477
2544
  } else {
2478
2545
  entry = pkgJson.main ?? "index.js";
2479
2546
  }
2480
- const entryPath = path25.join(pkgDir, entry);
2547
+ const entryPath = path27.join(pkgDir, entry);
2481
2548
  try {
2482
- await fs27.access(entryPath);
2549
+ await fs29.access(entryPath);
2483
2550
  } catch {
2484
2551
  throw new Error(`Entry point "${entry}" not found for "${packageName}" at ${entryPath}`);
2485
2552
  }
@@ -2489,7 +2556,7 @@ async function installNpmPlugin(packageName, pluginsDir) {
2489
2556
  if (!VALID_NPM_NAME.test(packageName)) {
2490
2557
  throw new Error(`Invalid package name: "${packageName}". Must be a valid npm package name.`);
2491
2558
  }
2492
- const dir = pluginsDir ?? path25.join(os12.homedir(), ".openacp", "plugins");
2559
+ const dir = pluginsDir;
2493
2560
  try {
2494
2561
  return await importFromDir(packageName, dir);
2495
2562
  } catch {
@@ -2571,10 +2638,10 @@ var install_context_exports = {};
2571
2638
  __export(install_context_exports, {
2572
2639
  createInstallContext: () => createInstallContext
2573
2640
  });
2574
- import path26 from "path";
2641
+ import path28 from "path";
2575
2642
  function createInstallContext(opts) {
2576
2643
  const { pluginName, settingsManager, basePath, instanceRoot } = opts;
2577
- const dataDir = path26.join(basePath, pluginName, "data");
2644
+ const dataDir = path28.join(basePath, pluginName, "data");
2578
2645
  return {
2579
2646
  pluginName,
2580
2647
  terminal: createTerminalIO(),
@@ -2598,21 +2665,31 @@ __export(api_client_exports, {
2598
2665
  apiCall: () => apiCall,
2599
2666
  readApiPort: () => readApiPort,
2600
2667
  readApiSecret: () => readApiSecret,
2601
- removeStalePortFile: () => removeStalePortFile
2668
+ removeStalePortFile: () => removeStalePortFile,
2669
+ waitForPortFile: () => waitForPortFile
2602
2670
  });
2603
- import * as fs28 from "fs";
2604
- import * as path27 from "path";
2605
- 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";
2606
2674
  function defaultPortFile(root) {
2607
- return path27.join(root ?? DEFAULT_ROOT, "api.port");
2675
+ return path29.join(root, "api.port");
2608
2676
  }
2609
2677
  function defaultSecretFile(root) {
2610
- 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);
2611
2688
  }
2612
2689
  function readApiPort(portFilePath, instanceRoot) {
2613
2690
  const filePath = portFilePath ?? defaultPortFile(instanceRoot);
2614
2691
  try {
2615
- const content = fs28.readFileSync(filePath, "utf-8").trim();
2692
+ const content = fs30.readFileSync(filePath, "utf-8").trim();
2616
2693
  const port = parseInt(content, 10);
2617
2694
  return isNaN(port) ? null : port;
2618
2695
  } catch {
@@ -2622,7 +2699,7 @@ function readApiPort(portFilePath, instanceRoot) {
2622
2699
  function readApiSecret(secretFilePath, instanceRoot) {
2623
2700
  const filePath = secretFilePath ?? defaultSecretFile(instanceRoot);
2624
2701
  try {
2625
- const content = fs28.readFileSync(filePath, "utf-8").trim();
2702
+ const content = fs30.readFileSync(filePath, "utf-8").trim();
2626
2703
  return content || null;
2627
2704
  } catch {
2628
2705
  return null;
@@ -2631,7 +2708,7 @@ function readApiSecret(secretFilePath, instanceRoot) {
2631
2708
  function removeStalePortFile(portFilePath, instanceRoot) {
2632
2709
  const filePath = portFilePath ?? defaultPortFile(instanceRoot);
2633
2710
  try {
2634
- fs28.unlinkSync(filePath);
2711
+ fs30.unlinkSync(filePath);
2635
2712
  } catch {
2636
2713
  }
2637
2714
  }
@@ -2643,11 +2720,9 @@ async function apiCall(port, urlPath, options, instanceRoot) {
2643
2720
  }
2644
2721
  return fetch(`http://127.0.0.1:${port}${urlPath}`, { ...options, headers });
2645
2722
  }
2646
- var DEFAULT_ROOT;
2647
2723
  var init_api_client = __esm({
2648
2724
  "src/cli/api-client.ts"() {
2649
2725
  "use strict";
2650
- DEFAULT_ROOT = path27.join(os13.homedir(), ".openacp");
2651
2726
  }
2652
2727
  });
2653
2728
 
@@ -2681,8 +2756,8 @@ var init_notification = __esm({
2681
2756
  });
2682
2757
 
2683
2758
  // src/plugins/file-service/file-service.ts
2684
- import fs30 from "fs";
2685
- import path30 from "path";
2759
+ import fs32 from "fs";
2760
+ import path32 from "path";
2686
2761
  import { OggOpusDecoder } from "ogg-opus-decoder";
2687
2762
  import wav from "node-wav";
2688
2763
  function classifyMime(mimeType) {
@@ -2738,14 +2813,14 @@ var init_file_service = __esm({
2738
2813
  const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1e3;
2739
2814
  let removed = 0;
2740
2815
  try {
2741
- const entries = await fs30.promises.readdir(this.baseDir, { withFileTypes: true });
2816
+ const entries = await fs32.promises.readdir(this.baseDir, { withFileTypes: true });
2742
2817
  for (const entry of entries) {
2743
2818
  if (!entry.isDirectory()) continue;
2744
- const dirPath = path30.join(this.baseDir, entry.name);
2819
+ const dirPath = path32.join(this.baseDir, entry.name);
2745
2820
  try {
2746
- const stat = await fs30.promises.stat(dirPath);
2821
+ const stat = await fs32.promises.stat(dirPath);
2747
2822
  if (stat.mtimeMs < cutoff) {
2748
- await fs30.promises.rm(dirPath, { recursive: true, force: true });
2823
+ await fs32.promises.rm(dirPath, { recursive: true, force: true });
2749
2824
  removed++;
2750
2825
  }
2751
2826
  } catch {
@@ -2756,11 +2831,11 @@ var init_file_service = __esm({
2756
2831
  return removed;
2757
2832
  }
2758
2833
  async saveFile(sessionId, fileName, data, mimeType) {
2759
- const sessionDir = path30.join(this.baseDir, sessionId);
2760
- await fs30.promises.mkdir(sessionDir, { recursive: true });
2834
+ const sessionDir = path32.join(this.baseDir, sessionId);
2835
+ await fs32.promises.mkdir(sessionDir, { recursive: true });
2761
2836
  const safeName = `${Date.now()}-${fileName.replace(/[^a-zA-Z0-9._-]/g, "_")}`;
2762
- const filePath = path30.join(sessionDir, safeName);
2763
- await fs30.promises.writeFile(filePath, data);
2837
+ const filePath = path32.join(sessionDir, safeName);
2838
+ await fs32.promises.writeFile(filePath, data);
2764
2839
  return {
2765
2840
  type: classifyMime(mimeType),
2766
2841
  filePath,
@@ -2771,14 +2846,14 @@ var init_file_service = __esm({
2771
2846
  }
2772
2847
  async resolveFile(filePath) {
2773
2848
  try {
2774
- const stat = await fs30.promises.stat(filePath);
2849
+ const stat = await fs32.promises.stat(filePath);
2775
2850
  if (!stat.isFile()) return null;
2776
- const ext = path30.extname(filePath).toLowerCase();
2851
+ const ext = path32.extname(filePath).toLowerCase();
2777
2852
  const mimeType = EXT_TO_MIME[ext] || "application/octet-stream";
2778
2853
  return {
2779
2854
  type: classifyMime(mimeType),
2780
2855
  filePath,
2781
- fileName: path30.basename(filePath),
2856
+ fileName: path32.basename(filePath),
2782
2857
  mimeType,
2783
2858
  size: stat.size
2784
2859
  };
@@ -3013,7 +3088,7 @@ function createAuthPreHandler(getSecret, getJwtSecret, tokenStore) {
3013
3088
  const queryToken = request.query?.token;
3014
3089
  const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : queryToken;
3015
3090
  if (queryToken && !authHeader) {
3016
- log20.warn(
3091
+ log21.warn(
3017
3092
  { url: request.url.replace(/([?&]token=)[^&]+/, "$1[REDACTED]") },
3018
3093
  "Token passed via URL query param \u2014 use Authorization: Bearer header to avoid token leakage in tunnel/proxy logs"
3019
3094
  );
@@ -3067,7 +3142,7 @@ function requireRole(role) {
3067
3142
  }
3068
3143
  };
3069
3144
  }
3070
- var log20;
3145
+ var log21;
3071
3146
  var init_auth = __esm({
3072
3147
  "src/plugins/api-server/middleware/auth.ts"() {
3073
3148
  "use strict";
@@ -3075,7 +3150,7 @@ var init_auth = __esm({
3075
3150
  init_jwt();
3076
3151
  init_roles();
3077
3152
  init_log();
3078
- log20 = createChildLogger({ module: "api-auth" });
3153
+ log21 = createChildLogger({ module: "api-auth" });
3079
3154
  }
3080
3155
  });
3081
3156
 
@@ -3344,8 +3419,8 @@ data: ${JSON.stringify(data)}
3344
3419
  });
3345
3420
 
3346
3421
  // src/plugins/api-server/static-server.ts
3347
- import * as fs31 from "fs";
3348
- import * as path31 from "path";
3422
+ import * as fs33 from "fs";
3423
+ import * as path33 from "path";
3349
3424
  import { fileURLToPath } from "url";
3350
3425
  var MIME_TYPES, StaticServer;
3351
3426
  var init_static_server = __esm({
@@ -3369,16 +3444,16 @@ var init_static_server = __esm({
3369
3444
  this.uiDir = uiDir;
3370
3445
  if (!this.uiDir) {
3371
3446
  const __filename = fileURLToPath(import.meta.url);
3372
- const candidate = path31.resolve(path31.dirname(__filename), "../../ui/dist");
3373
- 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"))) {
3374
3449
  this.uiDir = candidate;
3375
3450
  }
3376
3451
  if (!this.uiDir) {
3377
- const publishCandidate = path31.resolve(
3378
- path31.dirname(__filename),
3452
+ const publishCandidate = path33.resolve(
3453
+ path33.dirname(__filename),
3379
3454
  "../ui"
3380
3455
  );
3381
- if (fs31.existsSync(path31.join(publishCandidate, "index.html"))) {
3456
+ if (fs33.existsSync(path33.join(publishCandidate, "index.html"))) {
3382
3457
  this.uiDir = publishCandidate;
3383
3458
  }
3384
3459
  }
@@ -3390,23 +3465,23 @@ var init_static_server = __esm({
3390
3465
  serve(req, res) {
3391
3466
  if (!this.uiDir) return false;
3392
3467
  const urlPath = (req.url || "/").split("?")[0];
3393
- const safePath = path31.normalize(urlPath);
3394
- const filePath = path31.join(this.uiDir, safePath);
3395
- 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)
3396
3471
  return false;
3397
3472
  let realFilePath;
3398
3473
  try {
3399
- realFilePath = fs31.realpathSync(filePath);
3474
+ realFilePath = fs33.realpathSync(filePath);
3400
3475
  } catch {
3401
3476
  realFilePath = null;
3402
3477
  }
3403
3478
  if (realFilePath !== null) {
3404
- const realUiDir = fs31.realpathSync(this.uiDir);
3405
- if (!realFilePath.startsWith(realUiDir + path31.sep) && realFilePath !== realUiDir)
3479
+ const realUiDir = fs33.realpathSync(this.uiDir);
3480
+ if (!realFilePath.startsWith(realUiDir + path33.sep) && realFilePath !== realUiDir)
3406
3481
  return false;
3407
3482
  }
3408
- if (realFilePath !== null && fs31.existsSync(realFilePath) && fs31.statSync(realFilePath).isFile()) {
3409
- const ext = path31.extname(filePath);
3483
+ if (realFilePath !== null && fs33.existsSync(realFilePath) && fs33.statSync(realFilePath).isFile()) {
3484
+ const ext = path33.extname(filePath);
3410
3485
  const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
3411
3486
  const isHashed = /\.[a-zA-Z0-9]{8,}\.(js|css)$/.test(filePath);
3412
3487
  const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "no-cache";
@@ -3414,16 +3489,16 @@ var init_static_server = __esm({
3414
3489
  "Content-Type": contentType,
3415
3490
  "Cache-Control": cacheControl
3416
3491
  });
3417
- fs31.createReadStream(realFilePath).pipe(res);
3492
+ fs33.createReadStream(realFilePath).pipe(res);
3418
3493
  return true;
3419
3494
  }
3420
- const indexPath = path31.join(this.uiDir, "index.html");
3421
- if (fs31.existsSync(indexPath)) {
3495
+ const indexPath = path33.join(this.uiDir, "index.html");
3496
+ if (fs33.existsSync(indexPath)) {
3422
3497
  res.writeHead(200, {
3423
3498
  "Content-Type": "text/html; charset=utf-8",
3424
3499
  "Cache-Control": "no-cache"
3425
3500
  });
3426
- fs31.createReadStream(indexPath).pipe(res);
3501
+ fs33.createReadStream(indexPath).pipe(res);
3427
3502
  return true;
3428
3503
  }
3429
3504
  return false;
@@ -3578,8 +3653,8 @@ var init_exports = __esm({
3578
3653
  });
3579
3654
 
3580
3655
  // src/plugins/context/context-cache.ts
3581
- import * as fs32 from "fs";
3582
- import * as path32 from "path";
3656
+ import * as fs34 from "fs";
3657
+ import * as path34 from "path";
3583
3658
  import * as crypto2 from "crypto";
3584
3659
  var DEFAULT_TTL_MS, ContextCache;
3585
3660
  var init_context_cache = __esm({
@@ -3590,37 +3665,35 @@ var init_context_cache = __esm({
3590
3665
  constructor(cacheDir, ttlMs = DEFAULT_TTL_MS) {
3591
3666
  this.cacheDir = cacheDir;
3592
3667
  this.ttlMs = ttlMs;
3593
- fs32.mkdirSync(cacheDir, { recursive: true });
3668
+ fs34.mkdirSync(cacheDir, { recursive: true });
3594
3669
  }
3595
3670
  keyHash(repoPath, queryKey) {
3596
3671
  return crypto2.createHash("sha256").update(`${repoPath}:${queryKey}`).digest("hex").slice(0, 16);
3597
3672
  }
3598
3673
  filePath(repoPath, queryKey) {
3599
- return path32.join(this.cacheDir, `${this.keyHash(repoPath, queryKey)}.json`);
3674
+ return path34.join(this.cacheDir, `${this.keyHash(repoPath, queryKey)}.json`);
3600
3675
  }
3601
3676
  get(repoPath, queryKey) {
3602
3677
  const fp = this.filePath(repoPath, queryKey);
3603
3678
  try {
3604
- const stat = fs32.statSync(fp);
3679
+ const stat = fs34.statSync(fp);
3605
3680
  if (Date.now() - stat.mtimeMs > this.ttlMs) {
3606
- fs32.unlinkSync(fp);
3681
+ fs34.unlinkSync(fp);
3607
3682
  return null;
3608
3683
  }
3609
- return JSON.parse(fs32.readFileSync(fp, "utf-8"));
3684
+ return JSON.parse(fs34.readFileSync(fp, "utf-8"));
3610
3685
  } catch {
3611
3686
  return null;
3612
3687
  }
3613
3688
  }
3614
3689
  set(repoPath, queryKey, result) {
3615
- fs32.writeFileSync(this.filePath(repoPath, queryKey), JSON.stringify(result));
3690
+ fs34.writeFileSync(this.filePath(repoPath, queryKey), JSON.stringify(result));
3616
3691
  }
3617
3692
  };
3618
3693
  }
3619
3694
  });
3620
3695
 
3621
3696
  // src/plugins/context/context-manager.ts
3622
- import * as os15 from "os";
3623
- import * as path33 from "path";
3624
3697
  var ContextManager;
3625
3698
  var init_context_manager = __esm({
3626
3699
  "src/plugins/context/context-manager.ts"() {
@@ -3632,7 +3705,7 @@ var init_context_manager = __esm({
3632
3705
  historyStore;
3633
3706
  sessionFlusher;
3634
3707
  constructor(cachePath) {
3635
- this.cache = new ContextCache(cachePath ?? path33.join(os15.homedir(), ".openacp", "cache", "entire"));
3708
+ this.cache = new ContextCache(cachePath);
3636
3709
  }
3637
3710
  setHistoryStore(store) {
3638
3711
  this.historyStore = store;
@@ -4549,8 +4622,8 @@ function formatToolSummary(name, rawInput, displaySummary) {
4549
4622
  }
4550
4623
  if (lowerName === "grep") {
4551
4624
  const pattern = args.pattern ?? "";
4552
- const path34 = args.path ?? "";
4553
- 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}`;
4554
4627
  }
4555
4628
  if (lowerName === "glob") {
4556
4629
  const pattern = args.pattern ?? "";
@@ -4586,8 +4659,8 @@ function formatToolTitle(name, rawInput, displayTitle) {
4586
4659
  }
4587
4660
  if (lowerName === "grep") {
4588
4661
  const pattern = args.pattern ?? "";
4589
- const path34 = args.path ?? "";
4590
- return pattern ? `"${pattern}"${path34 ? ` in ${path34}` : ""}` : name;
4662
+ const path35 = args.path ?? "";
4663
+ return pattern ? `"${pattern}"${path35 ? ` in ${path35}` : ""}` : name;
4591
4664
  }
4592
4665
  if (lowerName === "glob") {
4593
4666
  return String(args.pattern ?? name);
@@ -4975,10 +5048,10 @@ var init_send_queue = __esm({
4975
5048
  const type = opts?.type ?? "other";
4976
5049
  const key = opts?.key;
4977
5050
  const category = opts?.category;
4978
- let resolve6;
5051
+ let resolve7;
4979
5052
  let reject;
4980
5053
  const promise = new Promise((res, rej) => {
4981
- resolve6 = res;
5054
+ resolve7 = res;
4982
5055
  reject = rej;
4983
5056
  });
4984
5057
  promise.catch(() => {
@@ -4989,12 +5062,12 @@ var init_send_queue = __esm({
4989
5062
  );
4990
5063
  if (idx !== -1) {
4991
5064
  this.items[idx].resolve(void 0);
4992
- this.items[idx] = { fn, type, key, category, resolve: resolve6, reject, promise };
5065
+ this.items[idx] = { fn, type, key, category, resolve: resolve7, reject, promise };
4993
5066
  this.scheduleProcess();
4994
5067
  return promise;
4995
5068
  }
4996
5069
  }
4997
- this.items.push({ fn, type, key, category, resolve: resolve6, reject, promise });
5070
+ this.items.push({ fn, type, key, category, resolve: resolve7, reject, promise });
4998
5071
  this.scheduleProcess();
4999
5072
  return promise;
5000
5073
  }
@@ -5256,7 +5329,7 @@ var init_stream_accumulator = __esm({
5256
5329
  });
5257
5330
 
5258
5331
  // src/core/adapter-primitives/display-spec-builder.ts
5259
- function asRecord(value) {
5332
+ function asRecord3(value) {
5260
5333
  if (value !== null && typeof value === "object" && !Array.isArray(value)) {
5261
5334
  return value;
5262
5335
  }
@@ -5289,8 +5362,21 @@ function parseApplyPatchTargets(patchText) {
5289
5362
  function buildTitle(entry, kind) {
5290
5363
  if (entry.displayTitle) return entry.displayTitle;
5291
5364
  if (entry.displaySummary) return entry.displaySummary;
5292
- const input2 = asRecord(entry.rawInput);
5365
+ const input2 = asRecord3(entry.rawInput);
5293
5366
  const nameLower = entry.name.toLowerCase();
5367
+ if (isApplyPatchOtherTool(entry.kind, entry.name, entry.rawInput)) {
5368
+ const patchText = getStringField(input2, ["patchText", "patch_text"]);
5369
+ if (patchText) {
5370
+ const targets = parseApplyPatchTargets(patchText);
5371
+ if (targets.length === 1) return targets[0];
5372
+ if (targets.length > 1) {
5373
+ const shown = targets.slice(0, 2).join(", ");
5374
+ const rest = targets.length - 2;
5375
+ return rest > 0 ? `${shown} (+${rest} more)` : shown;
5376
+ }
5377
+ }
5378
+ return "apply_patch";
5379
+ }
5294
5380
  if (kind === "read") {
5295
5381
  const filePath = getStringField(input2, ["file_path", "filePath", "path"]);
5296
5382
  if (filePath) {
@@ -5339,36 +5425,6 @@ function buildTitle(entry, kind) {
5339
5425
  }
5340
5426
  return capitalize(entry.name);
5341
5427
  }
5342
- if (nameLower === "apply_patch") {
5343
- const patchText = getStringField(input2, ["patchText", "patch_text"]);
5344
- if (patchText) {
5345
- const targets = parseApplyPatchTargets(patchText);
5346
- if (targets.length === 1) return targets[0];
5347
- if (targets.length > 1) {
5348
- const shown = targets.slice(0, 2).join(", ");
5349
- const remaining = targets.length - 2;
5350
- return remaining > 0 ? `${shown} (+${remaining} more)` : shown;
5351
- }
5352
- }
5353
- return "apply_patch";
5354
- }
5355
- if (nameLower === "todowrite") {
5356
- const todos = Array.isArray(input2.todos) ? input2.todos : [];
5357
- if (todos.length > 0) {
5358
- const inProgress = todos.filter((t) => {
5359
- if (!t || typeof t !== "object") return false;
5360
- const status = t.status;
5361
- return status === "in_progress";
5362
- }).length;
5363
- const completed = todos.filter((t) => {
5364
- if (!t || typeof t !== "object") return false;
5365
- const status = t.status;
5366
- return status === "completed";
5367
- }).length;
5368
- return `Todo list (${completed}/${todos.length} done${inProgress > 0 ? `, ${inProgress} active` : ""})`;
5369
- }
5370
- return "Todo list";
5371
- }
5372
5428
  if (kind === "fetch" || kind === "web") {
5373
5429
  const url = typeof input2.url === "string" ? input2.url : null;
5374
5430
  if (url && url !== "undefined") return url.length > 60 ? url.slice(0, 57) + "..." : url;
@@ -5379,19 +5435,6 @@ function buildTitle(entry, kind) {
5379
5435
  if (nameLower === "skill" && typeof input2.skill === "string" && input2.skill) {
5380
5436
  return input2.skill;
5381
5437
  }
5382
- if (nameLower === "apply_patch") {
5383
- const patchText = getStringField(input2, ["patchText", "patch_text"]);
5384
- if (patchText) {
5385
- const targets = parseApplyPatchTargets(patchText);
5386
- if (targets.length === 1) return targets[0];
5387
- if (targets.length > 1) {
5388
- const shown = targets.slice(0, 2).join(", ");
5389
- const rest = targets.length - 2;
5390
- return rest > 0 ? `${shown} (+${rest} more)` : shown;
5391
- }
5392
- }
5393
- return "apply_patch";
5394
- }
5395
5438
  if (nameLower === "todowrite") {
5396
5439
  const todos = Array.isArray(input2.todos) ? input2.todos : [];
5397
5440
  if (todos.length > 0) {
@@ -5421,6 +5464,7 @@ var init_display_spec_builder = __esm({
5421
5464
  "src/core/adapter-primitives/display-spec-builder.ts"() {
5422
5465
  "use strict";
5423
5466
  init_format_types();
5467
+ init_apply_patch_detection();
5424
5468
  EXECUTE_KINDS = /* @__PURE__ */ new Set(["execute", "bash", "command", "terminal"]);
5425
5469
  INLINE_MAX_LINES = 15;
5426
5470
  INLINE_MAX_CHARS = 800;
@@ -5429,12 +5473,12 @@ var init_display_spec_builder = __esm({
5429
5473
  this.tunnelService = tunnelService;
5430
5474
  }
5431
5475
  buildToolSpec(entry, mode, sessionContext) {
5432
- const effectiveKind = entry.displayKind ?? entry.kind;
5476
+ const effectiveKind = entry.displayKind ?? (isApplyPatchOtherTool(entry.kind, entry.name, entry.rawInput) ? "edit" : entry.kind);
5433
5477
  const icon = KIND_ICONS[effectiveKind] ?? KIND_ICONS["other"] ?? "\u{1F6E0}\uFE0F";
5434
5478
  const title = buildTitle(entry, effectiveKind);
5435
5479
  const isHidden = entry.isNoise && mode !== "high";
5436
5480
  const includeMeta = mode !== "low";
5437
- const input2 = asRecord(entry.rawInput);
5481
+ const input2 = asRecord3(entry.rawInput);
5438
5482
  const rawDescription = typeof input2.description === "string" ? input2.description : null;
5439
5483
  const descLower = rawDescription?.toLowerCase();
5440
5484
  const description = includeMeta && rawDescription && rawDescription !== title && descLower !== effectiveKind && descLower !== entry.name.toLowerCase() ? rawDescription : null;
@@ -5819,14 +5863,14 @@ __export(version_exports, {
5819
5863
  runUpdate: () => runUpdate
5820
5864
  });
5821
5865
  import { fileURLToPath as fileURLToPath2 } from "url";
5822
- import { dirname as dirname10, join as join20, resolve as resolve5 } from "path";
5823
- 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";
5824
5868
  function findPackageJson() {
5825
- let dir = dirname10(fileURLToPath2(import.meta.url));
5869
+ let dir = dirname12(fileURLToPath2(import.meta.url));
5826
5870
  for (let i = 0; i < 5; i++) {
5827
- const candidate = join20(dir, "package.json");
5828
- if (existsSync17(candidate)) return candidate;
5829
- const parent = resolve5(dir, "..");
5871
+ const candidate = join16(dir, "package.json");
5872
+ if (existsSync16(candidate)) return candidate;
5873
+ const parent = resolve6(dir, "..");
5830
5874
  if (parent === dir) break;
5831
5875
  dir = parent;
5832
5876
  }
@@ -5836,7 +5880,7 @@ function getCurrentVersion() {
5836
5880
  try {
5837
5881
  const pkgPath = findPackageJson();
5838
5882
  if (!pkgPath) return "0.0.0-dev";
5839
- const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
5883
+ const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
5840
5884
  return pkg.version;
5841
5885
  } catch {
5842
5886
  return "0.0.0-dev";
@@ -5865,21 +5909,21 @@ function compareVersions(current, latest) {
5865
5909
  }
5866
5910
  async function runUpdate() {
5867
5911
  const { spawn: spawn4 } = await import("child_process");
5868
- return new Promise((resolve6) => {
5912
+ return new Promise((resolve7) => {
5869
5913
  const child = spawn4("npm", ["install", "-g", `${NPM_PACKAGE}@latest`], {
5870
5914
  stdio: "inherit",
5871
5915
  shell: true
5872
5916
  });
5873
5917
  const onSignal = () => {
5874
5918
  child.kill("SIGTERM");
5875
- resolve6(false);
5919
+ resolve7(false);
5876
5920
  };
5877
5921
  process.on("SIGINT", onSignal);
5878
5922
  process.on("SIGTERM", onSignal);
5879
5923
  child.on("close", (code) => {
5880
5924
  process.off("SIGINT", onSignal);
5881
5925
  process.off("SIGTERM", onSignal);
5882
- resolve6(code === 0);
5926
+ resolve7(code === 0);
5883
5927
  });
5884
5928
  });
5885
5929
  }
@@ -5942,7 +5986,7 @@ function setupDangerousModeCallbacks(bot, core) {
5942
5986
  }).catch(() => {
5943
5987
  });
5944
5988
  }
5945
- log22.info({ sessionId, wantOn }, "Bypass permissions toggled via button");
5989
+ log23.info({ sessionId, wantOn }, "Bypass permissions toggled via button");
5946
5990
  try {
5947
5991
  await ctx.editMessageText(buildSessionStatusText(session), {
5948
5992
  parse_mode: "HTML",
@@ -5965,7 +6009,7 @@ function setupDangerousModeCallbacks(bot, core) {
5965
6009
  const newDangerousMode = !(record.clientOverrides?.bypassPermissions ?? record.dangerousMode ?? false);
5966
6010
  core.sessionManager.patchRecord(sessionId, { clientOverrides: { bypassPermissions: newDangerousMode } }).catch(() => {
5967
6011
  });
5968
- log22.info(
6012
+ log23.info(
5969
6013
  { sessionId, dangerousMode: newDangerousMode },
5970
6014
  "Bypass permissions toggled via button (store-only, session not in memory)"
5971
6015
  );
@@ -6150,14 +6194,14 @@ async function handleRestart(ctx, core) {
6150
6194
  await new Promise((r) => setTimeout(r, 500));
6151
6195
  await core.requestRestart();
6152
6196
  }
6153
- var log22, OUTPUT_MODE_LABELS;
6197
+ var log23, OUTPUT_MODE_LABELS;
6154
6198
  var init_admin = __esm({
6155
6199
  "src/plugins/telegram/commands/admin.ts"() {
6156
6200
  "use strict";
6157
6201
  init_bypass_detection();
6158
6202
  init_formatting();
6159
6203
  init_log();
6160
- log22 = createChildLogger({ module: "telegram-cmd-admin" });
6204
+ log23 = createChildLogger({ module: "telegram-cmd-admin" });
6161
6205
  OUTPUT_MODE_LABELS = {
6162
6206
  low: "\u{1F507} Low",
6163
6207
  medium: "\u{1F4CA} Medium",
@@ -6172,7 +6216,7 @@ function botFromCtx(ctx) {
6172
6216
  return { api: ctx.api };
6173
6217
  }
6174
6218
  async function createSessionDirect(ctx, core, chatId, agentName, workspace, onControlMessage) {
6175
- 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)");
6176
6220
  let threadId;
6177
6221
  try {
6178
6222
  const topicName = `\u{1F504} New Session`;
@@ -6199,7 +6243,7 @@ async function createSessionDirect(ctx, core, chatId, agentName, workspace, onCo
6199
6243
  onControlMessage?.(session.id, controlMsg.message_id);
6200
6244
  return threadId ?? null;
6201
6245
  } catch (err) {
6202
- log23.error({ err }, "Session creation failed");
6246
+ log24.error({ err }, "Session creation failed");
6203
6247
  if (threadId) {
6204
6248
  try {
6205
6249
  await ctx.api.deleteForumTopic(chatId, threadId);
@@ -6306,10 +6350,8 @@ async function showAgentPicker(ctx, core, chatId) {
6306
6350
  async function showWorkspacePicker(ctx, core, chatId, agentKey, newMessage = false) {
6307
6351
  const records = core.sessionManager.listRecords();
6308
6352
  const recentWorkspaces = [...new Set(records.map((r) => r.workingDir).filter(Boolean))].slice(0, 5);
6309
- const config = core.configManager.get();
6310
- const baseDir = config.workspace.baseDir;
6311
- const resolvedBaseDir = core.configManager.resolveWorkspace(baseDir);
6312
- const hasBaseDir = recentWorkspaces.some((ws) => ws === baseDir || ws === resolvedBaseDir);
6353
+ const resolvedBaseDir = core.configManager.resolveWorkspace();
6354
+ const hasBaseDir = recentWorkspaces.some((ws) => ws === resolvedBaseDir);
6313
6355
  const workspaces = hasBaseDir ? recentWorkspaces : [resolvedBaseDir, ...recentWorkspaces].slice(0, 5);
6314
6356
  const kb = new InlineKeyboard2();
6315
6357
  for (const ws of workspaces) {
@@ -6434,7 +6476,7 @@ Agent: <code>${escapeHtml(agentKey)}</code>
6434
6476
  await _sendCustomPathPrompt(ctx, chatId, agentKey);
6435
6477
  });
6436
6478
  }
6437
- var log23, WS_CACHE_MAX, workspaceCache, nextWsId, _forceReplyMap;
6479
+ var log24, WS_CACHE_MAX, workspaceCache, nextWsId, _forceReplyMap;
6438
6480
  var init_new_session = __esm({
6439
6481
  "src/plugins/telegram/commands/new-session.ts"() {
6440
6482
  "use strict";
@@ -6442,7 +6484,7 @@ var init_new_session = __esm({
6442
6484
  init_topics();
6443
6485
  init_log();
6444
6486
  init_admin();
6445
- log23 = createChildLogger({ module: "telegram-cmd-new-session" });
6487
+ log24 = createChildLogger({ module: "telegram-cmd-new-session" });
6446
6488
  WS_CACHE_MAX = 50;
6447
6489
  workspaceCache = /* @__PURE__ */ new Map();
6448
6490
  nextWsId = 0;
@@ -6507,7 +6549,7 @@ ${lines.join("\n")}${truncated}`,
6507
6549
  { parse_mode: "HTML", reply_markup: keyboard }
6508
6550
  );
6509
6551
  } catch (err) {
6510
- log24.error({ err }, "handleTopics error");
6552
+ log25.error({ err }, "handleTopics error");
6511
6553
  await ctx.reply("\u274C Failed to list sessions.", { parse_mode: "HTML" }).catch(() => {
6512
6554
  });
6513
6555
  }
@@ -6528,13 +6570,13 @@ async function handleCleanup(ctx, core, chatId, statuses) {
6528
6570
  try {
6529
6571
  await ctx.api.deleteForumTopic(chatId, topicId);
6530
6572
  } catch (err) {
6531
- 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");
6532
6574
  }
6533
6575
  }
6534
6576
  await core.sessionManager.removeRecord(record.sessionId);
6535
6577
  deleted++;
6536
6578
  } catch (err) {
6537
- log24.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
6579
+ log25.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
6538
6580
  failed++;
6539
6581
  }
6540
6582
  }
@@ -6605,7 +6647,7 @@ async function handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicId
6605
6647
  try {
6606
6648
  await core.sessionManager.cancelSession(record.sessionId);
6607
6649
  } catch (err) {
6608
- 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");
6609
6651
  }
6610
6652
  }
6611
6653
  const topicId = record.platform?.topicId;
@@ -6613,13 +6655,13 @@ async function handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicId
6613
6655
  try {
6614
6656
  await ctx.api.deleteForumTopic(chatId, topicId);
6615
6657
  } catch (err) {
6616
- 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");
6617
6659
  }
6618
6660
  }
6619
6661
  await core.sessionManager.removeRecord(record.sessionId);
6620
6662
  deleted++;
6621
6663
  } catch (err) {
6622
- log24.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
6664
+ log25.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
6623
6665
  failed++;
6624
6666
  }
6625
6667
  }
@@ -6687,13 +6729,13 @@ async function handleArchiveConfirm(ctx, core, chatId) {
6687
6729
  }
6688
6730
  }
6689
6731
  }
6690
- var log24;
6732
+ var log25;
6691
6733
  var init_session = __esm({
6692
6734
  "src/plugins/telegram/commands/session.ts"() {
6693
6735
  "use strict";
6694
6736
  init_formatting();
6695
6737
  init_log();
6696
- log24 = createChildLogger({ module: "telegram-cmd-session" });
6738
+ log25 = createChildLogger({ module: "telegram-cmd-session" });
6697
6739
  }
6698
6740
  });
6699
6741
 
@@ -6705,14 +6747,14 @@ __export(integrate_exports, {
6705
6747
  listIntegrations: () => listIntegrations,
6706
6748
  uninstallIntegration: () => uninstallIntegration
6707
6749
  });
6708
- import { existsSync as existsSync18, mkdirSync as mkdirSync11, readFileSync as readFileSync16, writeFileSync as writeFileSync11, unlinkSync as unlinkSync7, chmodSync, rmdirSync } from "fs";
6709
- import { join as join21, dirname as dirname11 } from "path";
6710
- 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";
6711
6753
  function isHooksIntegrationSpec(spec) {
6712
6754
  return spec.strategy === "hooks";
6713
6755
  }
6714
6756
  function expandPath(p) {
6715
- return p.replace(/^~/, homedir10());
6757
+ return p.replace(/^~/, homedir4());
6716
6758
  }
6717
6759
  function generateInjectScript(_agentKey, spec) {
6718
6760
  const sidVar = spec.sessionIdVar ?? "SESSION_ID";
@@ -6867,8 +6909,8 @@ function generateOpencodePlugin(spec) {
6867
6909
  function mergeSettingsJson(settingsPath, hookEvent, hookScriptPath) {
6868
6910
  const fullPath = expandPath(settingsPath);
6869
6911
  let settings = {};
6870
- if (existsSync18(fullPath)) {
6871
- const raw = readFileSync16(fullPath, "utf-8");
6912
+ if (existsSync17(fullPath)) {
6913
+ const raw = readFileSync15(fullPath, "utf-8");
6872
6914
  writeFileSync11(`${fullPath}.bak`, raw);
6873
6915
  settings = JSON.parse(raw);
6874
6916
  }
@@ -6884,14 +6926,14 @@ function mergeSettingsJson(settingsPath, hookEvent, hookScriptPath) {
6884
6926
  hooks: [{ type: "command", command: hookScriptPath }]
6885
6927
  });
6886
6928
  }
6887
- mkdirSync11(dirname11(fullPath), { recursive: true });
6929
+ mkdirSync11(dirname13(fullPath), { recursive: true });
6888
6930
  writeFileSync11(fullPath, JSON.stringify(settings, null, 2) + "\n");
6889
6931
  }
6890
6932
  function mergeHooksJson(settingsPath, hookEvent, hookScriptPath) {
6891
6933
  const fullPath = expandPath(settingsPath);
6892
6934
  let config = { version: 1 };
6893
- if (existsSync18(fullPath)) {
6894
- const raw = readFileSync16(fullPath, "utf-8");
6935
+ if (existsSync17(fullPath)) {
6936
+ const raw = readFileSync15(fullPath, "utf-8");
6895
6937
  writeFileSync11(`${fullPath}.bak`, raw);
6896
6938
  config = JSON.parse(raw);
6897
6939
  }
@@ -6903,13 +6945,13 @@ function mergeHooksJson(settingsPath, hookEvent, hookScriptPath) {
6903
6945
  if (!alreadyInstalled) {
6904
6946
  eventHooks.push({ command: hookScriptPath });
6905
6947
  }
6906
- mkdirSync11(dirname11(fullPath), { recursive: true });
6948
+ mkdirSync11(dirname13(fullPath), { recursive: true });
6907
6949
  writeFileSync11(fullPath, JSON.stringify(config, null, 2) + "\n");
6908
6950
  }
6909
6951
  function removeFromSettingsJson(settingsPath, hookEvent) {
6910
6952
  const fullPath = expandPath(settingsPath);
6911
- if (!existsSync18(fullPath)) return;
6912
- const raw = readFileSync16(fullPath, "utf-8");
6953
+ if (!existsSync17(fullPath)) return;
6954
+ const raw = readFileSync15(fullPath, "utf-8");
6913
6955
  const settings = JSON.parse(raw);
6914
6956
  const hooks = settings.hooks;
6915
6957
  if (!hooks?.[hookEvent]) return;
@@ -6923,8 +6965,8 @@ function removeFromSettingsJson(settingsPath, hookEvent) {
6923
6965
  }
6924
6966
  function removeFromHooksJson(settingsPath, hookEvent) {
6925
6967
  const fullPath = expandPath(settingsPath);
6926
- if (!existsSync18(fullPath)) return;
6927
- const raw = readFileSync16(fullPath, "utf-8");
6968
+ if (!existsSync17(fullPath)) return;
6969
+ const raw = readFileSync15(fullPath, "utf-8");
6928
6970
  const config = JSON.parse(raw);
6929
6971
  const hooks = config.hooks;
6930
6972
  if (!hooks?.[hookEvent]) return;
@@ -6947,30 +6989,30 @@ async function installHooksIntegration(agentKey, spec) {
6947
6989
  }
6948
6990
  const hooksDir = expandPath(spec.hooksDirPath);
6949
6991
  mkdirSync11(hooksDir, { recursive: true });
6950
- const injectPath = join21(hooksDir, "openacp-inject-session.sh");
6992
+ const injectPath = join17(hooksDir, "openacp-inject-session.sh");
6951
6993
  writeFileSync11(injectPath, generateInjectScript(agentKey, spec));
6952
6994
  chmodSync(injectPath, 493);
6953
6995
  logs.push(`Created ${injectPath}`);
6954
- const handoffPath = join21(hooksDir, "openacp-handoff.sh");
6996
+ const handoffPath = join17(hooksDir, "openacp-handoff.sh");
6955
6997
  writeFileSync11(handoffPath, generateHandoffScript(agentKey));
6956
6998
  chmodSync(handoffPath, 493);
6957
6999
  logs.push(`Created ${handoffPath}`);
6958
7000
  if (spec.commandsPath && spec.handoffCommandName) {
6959
7001
  if (spec.commandFormat === "skill") {
6960
- const skillDir = expandPath(join21(spec.commandsPath, spec.handoffCommandName));
7002
+ const skillDir = expandPath(join17(spec.commandsPath, spec.handoffCommandName));
6961
7003
  mkdirSync11(skillDir, { recursive: true });
6962
- const skillPath = join21(skillDir, "SKILL.md");
7004
+ const skillPath = join17(skillDir, "SKILL.md");
6963
7005
  writeFileSync11(skillPath, generateHandoffCommand(agentKey, spec));
6964
7006
  logs.push(`Created ${skillPath}`);
6965
7007
  } else {
6966
7008
  const cmdsDir = expandPath(spec.commandsPath);
6967
7009
  mkdirSync11(cmdsDir, { recursive: true });
6968
- const cmdPath = join21(cmdsDir, `${spec.handoffCommandName}.md`);
7010
+ const cmdPath = join17(cmdsDir, `${spec.handoffCommandName}.md`);
6969
7011
  writeFileSync11(cmdPath, generateHandoffCommand(agentKey, spec));
6970
7012
  logs.push(`Created ${cmdPath}`);
6971
7013
  }
6972
7014
  }
6973
- const injectFullPath = join21(hooksDir, "openacp-inject-session.sh");
7015
+ const injectFullPath = join17(hooksDir, "openacp-inject-session.sh");
6974
7016
  if (spec.settingsFormat === "hooks_json") {
6975
7017
  mergeHooksJson(spec.settingsPath, spec.hookEvent, injectFullPath);
6976
7018
  } else {
@@ -6988,17 +7030,17 @@ async function uninstallHooksIntegration(agentKey, spec) {
6988
7030
  try {
6989
7031
  const hooksDir = expandPath(spec.hooksDirPath);
6990
7032
  for (const filename of ["openacp-inject-session.sh", "openacp-handoff.sh"]) {
6991
- const filePath = join21(hooksDir, filename);
6992
- if (existsSync18(filePath)) {
7033
+ const filePath = join17(hooksDir, filename);
7034
+ if (existsSync17(filePath)) {
6993
7035
  unlinkSync7(filePath);
6994
7036
  logs.push(`Removed ${filePath}`);
6995
7037
  }
6996
7038
  }
6997
7039
  if (spec.commandsPath && spec.handoffCommandName) {
6998
7040
  if (spec.commandFormat === "skill") {
6999
- const skillDir = expandPath(join21(spec.commandsPath, spec.handoffCommandName));
7000
- const skillPath = join21(skillDir, "SKILL.md");
7001
- if (existsSync18(skillPath)) {
7041
+ const skillDir = expandPath(join17(spec.commandsPath, spec.handoffCommandName));
7042
+ const skillPath = join17(skillDir, "SKILL.md");
7043
+ if (existsSync17(skillPath)) {
7002
7044
  unlinkSync7(skillPath);
7003
7045
  try {
7004
7046
  rmdirSync(skillDir);
@@ -7007,8 +7049,8 @@ async function uninstallHooksIntegration(agentKey, spec) {
7007
7049
  logs.push(`Removed ${skillPath}`);
7008
7050
  }
7009
7051
  } else {
7010
- const cmdPath = expandPath(join21(spec.commandsPath, `${spec.handoffCommandName}.md`));
7011
- if (existsSync18(cmdPath)) {
7052
+ const cmdPath = expandPath(join17(spec.commandsPath, `${spec.handoffCommandName}.md`));
7053
+ if (existsSync17(cmdPath)) {
7012
7054
  unlinkSync7(cmdPath);
7013
7055
  logs.push(`Removed ${cmdPath}`);
7014
7056
  }
@@ -7031,15 +7073,15 @@ async function installPluginIntegration(_agentKey, spec) {
7031
7073
  try {
7032
7074
  const commandsDir = expandPath(spec.commandsPath);
7033
7075
  mkdirSync11(commandsDir, { recursive: true });
7034
- const commandPath = join21(commandsDir, spec.handoffCommandFile);
7076
+ const commandPath = join17(commandsDir, spec.handoffCommandFile);
7035
7077
  const pluginsDir = expandPath(spec.pluginsPath);
7036
7078
  mkdirSync11(pluginsDir, { recursive: true });
7037
- const pluginPath = join21(pluginsDir, spec.pluginFileName);
7038
- if (existsSync18(commandPath) && existsSync18(pluginPath)) {
7079
+ const pluginPath = join17(pluginsDir, spec.pluginFileName);
7080
+ if (existsSync17(commandPath) && existsSync17(pluginPath)) {
7039
7081
  logs.push("Already installed, skipping.");
7040
7082
  return { success: true, logs };
7041
7083
  }
7042
- if (existsSync18(commandPath) || existsSync18(pluginPath)) {
7084
+ if (existsSync17(commandPath) || existsSync17(pluginPath)) {
7043
7085
  logs.push("Overwriting existing files.");
7044
7086
  }
7045
7087
  writeFileSync11(commandPath, generateOpencodeHandoffCommand(spec));
@@ -7055,15 +7097,15 @@ async function installPluginIntegration(_agentKey, spec) {
7055
7097
  async function uninstallPluginIntegration(_agentKey, spec) {
7056
7098
  const logs = [];
7057
7099
  try {
7058
- const commandPath = join21(expandPath(spec.commandsPath), spec.handoffCommandFile);
7100
+ const commandPath = join17(expandPath(spec.commandsPath), spec.handoffCommandFile);
7059
7101
  let removedCount = 0;
7060
- if (existsSync18(commandPath)) {
7102
+ if (existsSync17(commandPath)) {
7061
7103
  unlinkSync7(commandPath);
7062
7104
  logs.push(`Removed ${commandPath}`);
7063
7105
  removedCount += 1;
7064
7106
  }
7065
- const pluginPath = join21(expandPath(spec.pluginsPath), spec.pluginFileName);
7066
- if (existsSync18(pluginPath)) {
7107
+ const pluginPath = join17(expandPath(spec.pluginsPath), spec.pluginFileName);
7108
+ if (existsSync17(pluginPath)) {
7067
7109
  unlinkSync7(pluginPath);
7068
7110
  logs.push(`Removed ${pluginPath}`);
7069
7111
  removedCount += 1;
@@ -7097,11 +7139,11 @@ function buildHandoffItem(agentKey, spec) {
7097
7139
  isInstalled() {
7098
7140
  if (isHooksIntegrationSpec(spec)) {
7099
7141
  const hooksDir = expandPath(spec.hooksDirPath);
7100
- 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"));
7101
7143
  }
7102
- const commandPath = join21(expandPath(spec.commandsPath), spec.handoffCommandFile);
7103
- const pluginPath = join21(expandPath(spec.pluginsPath), spec.pluginFileName);
7104
- 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);
7105
7147
  },
7106
7148
  install: () => installIntegration(agentKey, spec),
7107
7149
  uninstall: () => uninstallIntegration(agentKey, spec)
@@ -7116,20 +7158,20 @@ function buildTunnelItem(spec) {
7116
7158
  if (!isHooksIntegrationSpec(spec) || !spec.commandsPath) return null;
7117
7159
  const hooksSpec = spec;
7118
7160
  function getTunnelPath() {
7119
- return join21(getSkillBasePath(hooksSpec), "openacp-tunnel", "SKILL.md");
7161
+ return join17(getSkillBasePath(hooksSpec), "openacp-tunnel", "SKILL.md");
7120
7162
  }
7121
7163
  return {
7122
7164
  id: "tunnel",
7123
7165
  name: "Tunnel",
7124
7166
  description: "Expose local ports to the internet via OpenACP tunnel",
7125
7167
  isInstalled() {
7126
- return existsSync18(getTunnelPath());
7168
+ return existsSync17(getTunnelPath());
7127
7169
  },
7128
7170
  async install() {
7129
7171
  const logs = [];
7130
7172
  try {
7131
7173
  const skillPath = getTunnelPath();
7132
- mkdirSync11(dirname11(skillPath), { recursive: true });
7174
+ mkdirSync11(dirname13(skillPath), { recursive: true });
7133
7175
  writeFileSync11(skillPath, generateTunnelCommand());
7134
7176
  logs.push(`Created ${skillPath}`);
7135
7177
  return { success: true, logs };
@@ -7142,10 +7184,10 @@ function buildTunnelItem(spec) {
7142
7184
  const logs = [];
7143
7185
  try {
7144
7186
  const skillPath = getTunnelPath();
7145
- if (existsSync18(skillPath)) {
7187
+ if (existsSync17(skillPath)) {
7146
7188
  unlinkSync7(skillPath);
7147
7189
  try {
7148
- rmdirSync(dirname11(skillPath));
7190
+ rmdirSync(dirname13(skillPath));
7149
7191
  } catch {
7150
7192
  }
7151
7193
  logs.push(`Removed ${skillPath}`);
@@ -7444,7 +7486,7 @@ var init_agents2 = __esm({
7444
7486
  // src/plugins/telegram/commands/resume.ts
7445
7487
  function setupResumeCallbacks(_bot, _core, _chatId, _onControlMessage) {
7446
7488
  }
7447
- var log25;
7489
+ var log26;
7448
7490
  var init_resume = __esm({
7449
7491
  "src/plugins/telegram/commands/resume.ts"() {
7450
7492
  "use strict";
@@ -7454,7 +7496,7 @@ var init_resume = __esm({
7454
7496
  init_topics();
7455
7497
  init_admin();
7456
7498
  init_log();
7457
- log25 = createChildLogger({ module: "telegram-cmd-resume" });
7499
+ log26 = createChildLogger({ module: "telegram-cmd-resume" });
7458
7500
  }
7459
7501
  });
7460
7502
 
@@ -7617,7 +7659,7 @@ function setupSettingsCallbacks(bot, core, getAssistantSession) {
7617
7659
  } catch {
7618
7660
  }
7619
7661
  } catch (err) {
7620
- log26.error({ err, fieldPath }, "Failed to toggle config");
7662
+ log27.error({ err, fieldPath }, "Failed to toggle config");
7621
7663
  try {
7622
7664
  await ctx.answerCallbackQuery({ text: "\u274C Failed to update" });
7623
7665
  } catch {
@@ -7698,7 +7740,7 @@ Tap to change:`, {
7698
7740
  } catch {
7699
7741
  }
7700
7742
  } catch (err) {
7701
- log26.error({ err, fieldPath }, "Failed to set config");
7743
+ log27.error({ err, fieldPath }, "Failed to set config");
7702
7744
  try {
7703
7745
  await ctx.answerCallbackQuery({ text: "\u274C Failed to update" });
7704
7746
  } catch {
@@ -7756,13 +7798,13 @@ Tap to change:`, {
7756
7798
  }
7757
7799
  });
7758
7800
  }
7759
- var log26;
7801
+ var log27;
7760
7802
  var init_settings = __esm({
7761
7803
  "src/plugins/telegram/commands/settings.ts"() {
7762
7804
  "use strict";
7763
7805
  init_config_registry();
7764
7806
  init_log();
7765
- log26 = createChildLogger({ module: "telegram-settings" });
7807
+ log27 = createChildLogger({ module: "telegram-settings" });
7766
7808
  }
7767
7809
  });
7768
7810
 
@@ -7809,7 +7851,7 @@ async function handleDoctor(ctx) {
7809
7851
  reply_markup: keyboard
7810
7852
  });
7811
7853
  } catch (err) {
7812
- log27.error({ err }, "Doctor command failed");
7854
+ log28.error({ err }, "Doctor command failed");
7813
7855
  await ctx.api.editMessageText(
7814
7856
  ctx.chat.id,
7815
7857
  statusMsg.message_id,
@@ -7858,7 +7900,7 @@ function setupDoctorCallbacks(bot) {
7858
7900
  }
7859
7901
  }
7860
7902
  } catch (err) {
7861
- log27.error({ err, index }, "Doctor fix callback failed");
7903
+ log28.error({ err, index }, "Doctor fix callback failed");
7862
7904
  }
7863
7905
  });
7864
7906
  bot.callbackQuery("m:doctor", async (ctx) => {
@@ -7869,13 +7911,13 @@ function setupDoctorCallbacks(bot) {
7869
7911
  await handleDoctor(ctx);
7870
7912
  });
7871
7913
  }
7872
- var log27, pendingFixesStore;
7914
+ var log28, pendingFixesStore;
7873
7915
  var init_doctor2 = __esm({
7874
7916
  "src/plugins/telegram/commands/doctor.ts"() {
7875
7917
  "use strict";
7876
7918
  init_doctor();
7877
7919
  init_log();
7878
- log27 = createChildLogger({ module: "telegram-cmd-doctor" });
7920
+ log28 = createChildLogger({ module: "telegram-cmd-doctor" });
7879
7921
  pendingFixesStore = /* @__PURE__ */ new Map();
7880
7922
  }
7881
7923
  });
@@ -7934,13 +7976,13 @@ function setupTunnelCallbacks(bot, core) {
7934
7976
  }
7935
7977
  });
7936
7978
  }
7937
- var log28;
7979
+ var log29;
7938
7980
  var init_tunnel2 = __esm({
7939
7981
  "src/plugins/telegram/commands/tunnel.ts"() {
7940
7982
  "use strict";
7941
7983
  init_formatting();
7942
7984
  init_log();
7943
- log28 = createChildLogger({ module: "telegram-cmd-tunnel" });
7985
+ log29 = createChildLogger({ module: "telegram-cmd-tunnel" });
7944
7986
  }
7945
7987
  });
7946
7988
 
@@ -7954,10 +7996,10 @@ async function executeSwitchAgent(ctx, core, sessionId, agentName) {
7954
7996
  `Switched to <b>${escapeHtml(agentName)}</b> (${status})`,
7955
7997
  { parse_mode: "HTML" }
7956
7998
  );
7957
- log29.info({ sessionId, agentName, resumed }, "Agent switched via /switch");
7999
+ log30.info({ sessionId, agentName, resumed }, "Agent switched via /switch");
7958
8000
  } catch (err) {
7959
8001
  await ctx.reply(`Failed to switch agent: ${escapeHtml(String(err.message || err))}`);
7960
- log29.warn({ sessionId, agentName, err: err.message }, "Agent switch failed");
8002
+ log30.warn({ sessionId, agentName, err: err.message }, "Agent switch failed");
7961
8003
  }
7962
8004
  }
7963
8005
  function setupSwitchCallbacks(bot, core) {
@@ -8002,13 +8044,13 @@ Switch to <b>${escapeHtml(agentName)}</b> anyway?`,
8002
8044
  await executeSwitchAgent(ctx, core, session.id, data);
8003
8045
  });
8004
8046
  }
8005
- var log29;
8047
+ var log30;
8006
8048
  var init_switch = __esm({
8007
8049
  "src/plugins/telegram/commands/switch.ts"() {
8008
8050
  "use strict";
8009
8051
  init_formatting();
8010
8052
  init_log();
8011
- log29 = createChildLogger({ module: "telegram-cmd-switch" });
8053
+ log30 = createChildLogger({ module: "telegram-cmd-switch" });
8012
8054
  }
8013
8055
  });
8014
8056
 
@@ -8206,7 +8248,7 @@ function setupAllCallbacks(bot, core, chatId, systemTopicIds, getAssistantSessio
8206
8248
  core,
8207
8249
  chatId,
8208
8250
  agentKey,
8209
- core.configManager.get().workspace.baseDir
8251
+ core.configManager.resolveWorkspace()
8210
8252
  );
8211
8253
  });
8212
8254
  setupNewSessionCallbacks(bot, core, chatId);
@@ -8221,7 +8263,7 @@ function setupAllCallbacks(bot, core, chatId, systemTopicIds, getAssistantSessio
8221
8263
  if (!menuRegistry) return;
8222
8264
  const item = menuRegistry.getItem(itemId);
8223
8265
  if (!item) {
8224
- log30.warn({ itemId }, "Menu item not found in registry");
8266
+ log31.warn({ itemId }, "Menu item not found in registry");
8225
8267
  return;
8226
8268
  }
8227
8269
  const topicId = ctx.callbackQuery.message?.message_thread_id;
@@ -8307,7 +8349,7 @@ ${lines}`, { parse_mode: "HTML" }).catch(() => {
8307
8349
  }
8308
8350
  });
8309
8351
  }
8310
- var log30, STATIC_COMMANDS;
8352
+ var log31, STATIC_COMMANDS;
8311
8353
  var init_commands = __esm({
8312
8354
  "src/plugins/telegram/commands/index.ts"() {
8313
8355
  "use strict";
@@ -8332,7 +8374,7 @@ var init_commands = __esm({
8332
8374
  init_settings();
8333
8375
  init_doctor2();
8334
8376
  init_resume();
8335
- log30 = createChildLogger({ module: "telegram-menu-callbacks" });
8377
+ log31 = createChildLogger({ module: "telegram-menu-callbacks" });
8336
8378
  STATIC_COMMANDS = [
8337
8379
  { command: "new", description: "Create new session" },
8338
8380
  { command: "newchat", description: "New chat, same agent & workspace" },
@@ -8367,14 +8409,14 @@ var init_commands = __esm({
8367
8409
  // src/plugins/telegram/permissions.ts
8368
8410
  import { InlineKeyboard as InlineKeyboard11 } from "grammy";
8369
8411
  import { nanoid as nanoid4 } from "nanoid";
8370
- var log31, PermissionHandler;
8412
+ var log32, PermissionHandler;
8371
8413
  var init_permissions = __esm({
8372
8414
  "src/plugins/telegram/permissions.ts"() {
8373
8415
  "use strict";
8374
8416
  init_formatting();
8375
8417
  init_topics();
8376
8418
  init_log();
8377
- log31 = createChildLogger({ module: "telegram-permissions" });
8419
+ log32 = createChildLogger({ module: "telegram-permissions" });
8378
8420
  PermissionHandler = class {
8379
8421
  constructor(bot, chatId, getSession, sendNotification) {
8380
8422
  this.bot = bot;
@@ -8434,7 +8476,7 @@ ${escapeHtml(request.description)}`,
8434
8476
  }
8435
8477
  const session = this.getSession(pending.sessionId);
8436
8478
  const isAllow = pending.options.find((o) => o.id === optionId)?.isAllow ?? false;
8437
- log31.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
8479
+ log32.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
8438
8480
  if (session?.permissionGate.requestId === pending.requestId) {
8439
8481
  session.permissionGate.resolve(optionId);
8440
8482
  }
@@ -8454,7 +8496,7 @@ ${escapeHtml(request.description)}`,
8454
8496
  });
8455
8497
 
8456
8498
  // src/plugins/telegram/activity.ts
8457
- var log32, THINKING_REFRESH_MS, THINKING_MAX_MS, ThinkingIndicator, ToolCard, ActivityTracker2;
8499
+ var log33, THINKING_REFRESH_MS, THINKING_MAX_MS, ThinkingIndicator, ToolCard, ActivityTracker2;
8458
8500
  var init_activity = __esm({
8459
8501
  "src/plugins/telegram/activity.ts"() {
8460
8502
  "use strict";
@@ -8464,7 +8506,7 @@ var init_activity = __esm({
8464
8506
  init_stream_accumulator();
8465
8507
  init_stream_accumulator();
8466
8508
  init_display_spec_builder();
8467
- log32 = createChildLogger({ module: "telegram:activity" });
8509
+ log33 = createChildLogger({ module: "telegram:activity" });
8468
8510
  THINKING_REFRESH_MS = 15e3;
8469
8511
  THINKING_MAX_MS = 3 * 60 * 1e3;
8470
8512
  ThinkingIndicator = class {
@@ -8505,7 +8547,7 @@ var init_activity = __esm({
8505
8547
  }
8506
8548
  }
8507
8549
  } catch (err) {
8508
- log32.warn({ err }, "ThinkingIndicator.show() failed");
8550
+ log33.warn({ err }, "ThinkingIndicator.show() failed");
8509
8551
  } finally {
8510
8552
  this.sending = false;
8511
8553
  }
@@ -8673,7 +8715,7 @@ var init_activity = __esm({
8673
8715
  this.tracer?.log("telegram", { action: "telegram:delete:overflow", sessionId: this.sessionId, msgId: staleId });
8674
8716
  }
8675
8717
  } catch (err) {
8676
- log32.warn({ err }, "[ToolCard] send/edit failed");
8718
+ log33.warn({ err }, "[ToolCard] send/edit failed");
8677
8719
  }
8678
8720
  }
8679
8721
  };
@@ -8777,7 +8819,7 @@ var init_activity = __esm({
8777
8819
  const entry = this.toolStateMap.merge(id, status, rawInput, content, viewerLinks, diffStats);
8778
8820
  if (!existed || !entry) return;
8779
8821
  if (viewerLinks || entry.viewerLinks) {
8780
- 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");
8781
8823
  }
8782
8824
  const spec = this.specBuilder.buildToolSpec(entry, this._outputMode, this.sessionContext);
8783
8825
  this.toolCard.updateFromSpec(spec);
@@ -9096,13 +9138,13 @@ var init_draft_manager = __esm({
9096
9138
  });
9097
9139
 
9098
9140
  // src/plugins/telegram/skill-command-manager.ts
9099
- var log33, SkillCommandManager;
9141
+ var log34, SkillCommandManager;
9100
9142
  var init_skill_command_manager = __esm({
9101
9143
  "src/plugins/telegram/skill-command-manager.ts"() {
9102
9144
  "use strict";
9103
9145
  init_commands();
9104
9146
  init_log();
9105
- log33 = createChildLogger({ module: "skill-commands" });
9147
+ log34 = createChildLogger({ module: "skill-commands" });
9106
9148
  SkillCommandManager = class {
9107
9149
  // sessionId → pinned msgId
9108
9150
  constructor(bot, chatId, sendQueue, sessionManager) {
@@ -9168,7 +9210,7 @@ var init_skill_command_manager = __esm({
9168
9210
  disable_notification: true
9169
9211
  });
9170
9212
  } catch (err) {
9171
- log33.error({ err, sessionId }, "Failed to send skill commands");
9213
+ log34.error({ err, sessionId }, "Failed to send skill commands");
9172
9214
  }
9173
9215
  }
9174
9216
  async cleanup(sessionId) {
@@ -9347,7 +9389,7 @@ async function validateBotAdmin(token, chatId) {
9347
9389
  };
9348
9390
  }
9349
9391
  const { status } = data.result;
9350
- log34.info(
9392
+ log35.info(
9351
9393
  { status, can_manage_topics: data.result.can_manage_topics, raw_result: data.result },
9352
9394
  "validateBotAdmin: getChatMember raw result"
9353
9395
  );
@@ -9367,7 +9409,7 @@ async function validateBotAdmin(token, chatId) {
9367
9409
  }
9368
9410
  async function checkTopicsPrerequisites(token, chatId) {
9369
9411
  const issues = [];
9370
- log34.info({ chatId }, "checkTopicsPrerequisites: starting checks");
9412
+ log35.info({ chatId }, "checkTopicsPrerequisites: starting checks");
9371
9413
  try {
9372
9414
  const res = await fetch(`https://api.telegram.org/bot${token}/getChat`, {
9373
9415
  method: "POST",
@@ -9375,7 +9417,7 @@ async function checkTopicsPrerequisites(token, chatId) {
9375
9417
  body: JSON.stringify({ chat_id: chatId })
9376
9418
  });
9377
9419
  const data = await res.json();
9378
- log34.info(
9420
+ log35.info(
9379
9421
  { chatId, apiOk: data.ok, is_forum: data.result?.is_forum, type: data.result?.type, title: data.result?.title },
9380
9422
  "checkTopicsPrerequisites: getChat result"
9381
9423
  );
@@ -9385,11 +9427,11 @@ async function checkTopicsPrerequisites(token, chatId) {
9385
9427
  );
9386
9428
  }
9387
9429
  } catch (err) {
9388
- log34.warn({ err, chatId }, "checkTopicsPrerequisites: getChat failed (network error)");
9430
+ log35.warn({ err, chatId }, "checkTopicsPrerequisites: getChat failed (network error)");
9389
9431
  issues.push("\u274C Could not check if Topics are enabled (network error).");
9390
9432
  }
9391
9433
  const adminResult = await validateBotAdmin(token, chatId);
9392
- log34.info(
9434
+ log35.info(
9393
9435
  { chatId, adminOk: adminResult.ok, canManageTopics: adminResult.ok ? adminResult.canManageTopics : void 0, error: !adminResult.ok ? adminResult.error : void 0 },
9394
9436
  "checkTopicsPrerequisites: validateBotAdmin result"
9395
9437
  );
@@ -9403,16 +9445,16 @@ async function checkTopicsPrerequisites(token, chatId) {
9403
9445
  '\u274C Bot cannot manage topics.\n\u2192 In Admin settings, enable the "Manage Topics" permission'
9404
9446
  );
9405
9447
  }
9406
- 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");
9407
9449
  if (issues.length > 0) return { ok: false, issues };
9408
9450
  return { ok: true };
9409
9451
  }
9410
- var log34;
9452
+ var log35;
9411
9453
  var init_validators = __esm({
9412
9454
  "src/plugins/telegram/validators.ts"() {
9413
9455
  "use strict";
9414
9456
  init_log();
9415
- log34 = createChildLogger({ module: "telegram-validators" });
9457
+ log35 = createChildLogger({ module: "telegram-validators" });
9416
9458
  }
9417
9459
  });
9418
9460
 
@@ -9431,7 +9473,7 @@ function patchedFetch(input2, init) {
9431
9473
  }
9432
9474
  return fetch(input2, init);
9433
9475
  }
9434
- var log35, TelegramAdapter;
9476
+ var log36, TelegramAdapter;
9435
9477
  var init_adapter = __esm({
9436
9478
  "src/plugins/telegram/adapter.ts"() {
9437
9479
  "use strict";
@@ -9451,7 +9493,7 @@ var init_adapter = __esm({
9451
9493
  init_messaging_adapter();
9452
9494
  init_renderer2();
9453
9495
  init_output_mode_resolver();
9454
- log35 = createChildLogger({ module: "telegram" });
9496
+ log36 = createChildLogger({ module: "telegram" });
9455
9497
  TelegramAdapter = class extends MessagingAdapter {
9456
9498
  name = "telegram";
9457
9499
  renderer = new TelegramRenderer();
@@ -9578,7 +9620,7 @@ var init_adapter = __esm({
9578
9620
  );
9579
9621
  this.bot.catch((err) => {
9580
9622
  const rootCause = err.error instanceof Error ? err.error : err;
9581
- log35.error({ err: rootCause }, "Telegram bot error");
9623
+ log36.error({ err: rootCause }, "Telegram bot error");
9582
9624
  });
9583
9625
  this.bot.api.config.use(async (prev, method, payload, signal) => {
9584
9626
  const maxRetries = 3;
@@ -9596,7 +9638,7 @@ var init_adapter = __esm({
9596
9638
  if (rateLimitedMethods.includes(method)) {
9597
9639
  this.sendQueue.onRateLimited();
9598
9640
  }
9599
- log35.warn(
9641
+ log36.warn(
9600
9642
  { method, retryAfter, attempt: attempt + 1 },
9601
9643
  "Rate limited by Telegram, retrying"
9602
9644
  );
@@ -9784,9 +9826,9 @@ ${p}` : p;
9784
9826
  this.setupRoutes();
9785
9827
  this.bot.start({
9786
9828
  allowed_updates: ["message", "callback_query"],
9787
- onStart: () => log35.info({ chatId: this.telegramConfig.chatId }, "Telegram bot started")
9829
+ onStart: () => log36.info({ chatId: this.telegramConfig.chatId }, "Telegram bot started")
9788
9830
  });
9789
- log35.info(
9831
+ log36.info(
9790
9832
  {
9791
9833
  chatId: this.telegramConfig.chatId,
9792
9834
  notificationTopicId: this.telegramConfig.notificationTopicId,
@@ -9800,12 +9842,12 @@ ${p}` : p;
9800
9842
  this.telegramConfig.chatId
9801
9843
  );
9802
9844
  if (prereqResult.ok) {
9803
- log35.info("Telegram adapter: prerequisites OK, initializing topic-dependent features");
9845
+ log36.info("Telegram adapter: prerequisites OK, initializing topic-dependent features");
9804
9846
  await this.initTopicDependentFeatures();
9805
9847
  } else {
9806
- 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");
9807
9849
  for (const issue of prereqResult.issues) {
9808
- log35.warn({ issue }, "Telegram prerequisite not met");
9850
+ log36.warn({ issue }, "Telegram prerequisite not met");
9809
9851
  }
9810
9852
  this.startPrerequisiteWatcher(prereqResult.issues);
9811
9853
  }
@@ -9821,7 +9863,7 @@ ${p}` : p;
9821
9863
  } catch (err) {
9822
9864
  if (attempt === maxRetries) throw err;
9823
9865
  const delay = baseDelayMs * Math.pow(2, attempt - 1);
9824
- log35.warn(
9866
+ log36.warn(
9825
9867
  { err, attempt, maxRetries, delayMs: delay, operation: label },
9826
9868
  `${label} failed, retrying in ${delay}ms`
9827
9869
  );
@@ -9841,12 +9883,12 @@ ${p}` : p;
9841
9883
  }),
9842
9884
  "setMyCommands"
9843
9885
  ).catch((err) => {
9844
- 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)");
9845
9887
  });
9846
9888
  }
9847
9889
  async initTopicDependentFeatures() {
9848
9890
  if (this._topicsInitialized) return;
9849
- log35.info(
9891
+ log36.info(
9850
9892
  { notificationTopicId: this.telegramConfig.notificationTopicId, assistantTopicId: this.telegramConfig.assistantTopicId },
9851
9893
  "initTopicDependentFeatures: starting (existing IDs in config)"
9852
9894
  );
@@ -9871,7 +9913,7 @@ ${p}` : p;
9871
9913
  this.assistantTopicId = topics.assistantTopicId;
9872
9914
  this._systemTopicIds.notificationTopicId = topics.notificationTopicId;
9873
9915
  this._systemTopicIds.assistantTopicId = topics.assistantTopicId;
9874
- log35.info(
9916
+ log36.info(
9875
9917
  { notificationTopicId: this.notificationTopicId, assistantTopicId: this.assistantTopicId },
9876
9918
  "initTopicDependentFeatures: topics ready"
9877
9919
  );
@@ -9902,7 +9944,7 @@ ${p}` : p;
9902
9944
  ).then((msg) => {
9903
9945
  if (msg) this.storeControlMsgId(sessionId, msg.message_id);
9904
9946
  }).catch((err) => {
9905
- 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");
9906
9948
  });
9907
9949
  };
9908
9950
  this.core.eventBus.on(BusEvent.SESSION_THREAD_READY, this._threadReadyHandler);
@@ -9911,7 +9953,7 @@ ${p}` : p;
9911
9953
  });
9912
9954
  };
9913
9955
  this.core.eventBus.on("session:configChanged", this._configChangedHandler);
9914
- log35.info({ assistantTopicId: this.assistantTopicId }, "initTopicDependentFeatures: sending welcome message");
9956
+ log36.info({ assistantTopicId: this.assistantTopicId }, "initTopicDependentFeatures: sending welcome message");
9915
9957
  try {
9916
9958
  const config = this.core.configManager.get();
9917
9959
  const agents = this.core.agentManager.getAvailableAgents();
@@ -9933,17 +9975,17 @@ ${p}` : p;
9933
9975
  this.core.lifecycleManager?.serviceRegistry?.get("menu-registry")
9934
9976
  )
9935
9977
  });
9936
- log35.info("initTopicDependentFeatures: welcome message sent");
9978
+ log36.info("initTopicDependentFeatures: welcome message sent");
9937
9979
  } catch (err) {
9938
- log35.warn({ err }, "Failed to send welcome message");
9980
+ log36.warn({ err }, "Failed to send welcome message");
9939
9981
  }
9940
9982
  try {
9941
9983
  await this.core.assistantManager.getOrSpawn("telegram", String(this.assistantTopicId));
9942
9984
  } catch (err) {
9943
- log35.error({ err }, "Failed to spawn assistant");
9985
+ log36.error({ err }, "Failed to spawn assistant");
9944
9986
  }
9945
9987
  this._topicsInitialized = true;
9946
- log35.info("Telegram adapter fully initialized");
9988
+ log36.info("Telegram adapter fully initialized");
9947
9989
  }
9948
9990
  startPrerequisiteWatcher(issues) {
9949
9991
  const setupMessage = `\u26A0\uFE0F <b>OpenACP needs setup before it can start.</b>
@@ -9954,7 +9996,7 @@ OpenACP will automatically retry until this is resolved.`;
9954
9996
  this.bot.api.sendMessage(this.telegramConfig.chatId, setupMessage, {
9955
9997
  parse_mode: "HTML"
9956
9998
  }).catch((err) => {
9957
- log35.warn({ err }, "Failed to send setup guidance to General topic");
9999
+ log36.warn({ err }, "Failed to send setup guidance to General topic");
9958
10000
  });
9959
10001
  const schedule = [5e3, 1e4, 3e4];
9960
10002
  let attempt = 1;
@@ -9967,7 +10009,7 @@ OpenACP will automatically retry until this is resolved.`;
9967
10009
  );
9968
10010
  if (result.ok) {
9969
10011
  this._prerequisiteWatcher = null;
9970
- log35.info("Prerequisites met \u2014 completing Telegram adapter initialization");
10012
+ log36.info("Prerequisites met \u2014 completing Telegram adapter initialization");
9971
10013
  try {
9972
10014
  await this.initTopicDependentFeatures();
9973
10015
  await this.bot.api.sendMessage(
@@ -9976,11 +10018,11 @@ OpenACP will automatically retry until this is resolved.`;
9976
10018
  { parse_mode: "HTML" }
9977
10019
  );
9978
10020
  } catch (err) {
9979
- log35.error({ err }, "Failed to complete initialization after prerequisites met");
10021
+ log36.error({ err }, "Failed to complete initialization after prerequisites met");
9980
10022
  }
9981
10023
  return;
9982
10024
  }
9983
- log35.debug({ issues: result.issues }, "Prerequisites not yet met, retrying");
10025
+ log36.debug({ issues: result.issues }, "Prerequisites not yet met, retrying");
9984
10026
  const delay = schedule[Math.min(attempt, schedule.length - 1)];
9985
10027
  attempt++;
9986
10028
  this._prerequisiteWatcher = setTimeout(retry, delay);
@@ -10006,7 +10048,7 @@ OpenACP will automatically retry until this is resolved.`;
10006
10048
  }
10007
10049
  this.sendQueue.clear();
10008
10050
  await this.bot.stop();
10009
- log35.info("Telegram bot stopped");
10051
+ log36.info("Telegram bot stopped");
10010
10052
  }
10011
10053
  // --- CommandRegistry response rendering ---
10012
10054
  async renderCommandResponse(response, chatId, topicId) {
@@ -10130,7 +10172,7 @@ ${lines.join("\n")}`;
10130
10172
  threadId: String(threadId),
10131
10173
  userId: String(ctx.from.id),
10132
10174
  text: forwardText
10133
- }).catch((err) => log35.error({ err }, "handleMessage error"));
10175
+ }).catch((err) => log36.error({ err }, "handleMessage error"));
10134
10176
  });
10135
10177
  this.bot.on("message:photo", async (ctx) => {
10136
10178
  const threadId = ctx.message.message_thread_id;
@@ -10219,7 +10261,7 @@ ${lines.join("\n")}`;
10219
10261
  if (session.archiving) return;
10220
10262
  const threadId = Number(session.threadId);
10221
10263
  if (!threadId || isNaN(threadId)) {
10222
- log35.warn(
10264
+ log36.warn(
10223
10265
  { sessionId, threadId: session.threadId },
10224
10266
  "Session has no valid threadId, skipping message"
10225
10267
  );
@@ -10235,7 +10277,7 @@ ${lines.join("\n")}`;
10235
10277
  this._sessionThreadIds.delete(sessionId);
10236
10278
  }
10237
10279
  }).catch((err) => {
10238
- log35.warn({ err, sessionId }, "Dispatch queue error");
10280
+ log36.warn({ err, sessionId }, "Dispatch queue error");
10239
10281
  });
10240
10282
  this._dispatchQueues.set(sessionId, next);
10241
10283
  await next;
@@ -10357,7 +10399,7 @@ ${lines.join("\n")}`;
10357
10399
  );
10358
10400
  usageMsgId = result?.message_id;
10359
10401
  } catch (err) {
10360
- log35.warn({ err, sessionId }, "Failed to send usage message");
10402
+ log36.warn({ err, sessionId }, "Failed to send usage message");
10361
10403
  }
10362
10404
  if (this.notificationTopicId && sessionId !== this.core.assistantManager?.get("telegram")?.id) {
10363
10405
  const sess = this.core.sessionManager.getSession(sessionId);
@@ -10385,7 +10427,7 @@ Task completed.
10385
10427
  if (!content.attachment) return;
10386
10428
  const { attachment } = content;
10387
10429
  if (attachment.size > 50 * 1024 * 1024) {
10388
- log35.warn(
10430
+ log36.warn(
10389
10431
  {
10390
10432
  sessionId,
10391
10433
  fileName: attachment.fileName,
@@ -10429,7 +10471,7 @@ Task completed.
10429
10471
  );
10430
10472
  }
10431
10473
  } catch (err) {
10432
- log35.error(
10474
+ log36.error(
10433
10475
  { err, sessionId, fileName: attachment.fileName },
10434
10476
  "Failed to send attachment"
10435
10477
  );
@@ -10528,7 +10570,7 @@ Task completed.
10528
10570
  }
10529
10571
  async sendPermissionRequest(sessionId, request) {
10530
10572
  this.getTracer(sessionId)?.log("telegram", { action: "permission:send", sessionId, requestId: request.id, description: request.description });
10531
- log35.info({ sessionId, requestId: request.id }, "Permission request sent");
10573
+ log36.info({ sessionId, requestId: request.id }, "Permission request sent");
10532
10574
  const session = this.core.sessionManager.getSession(sessionId);
10533
10575
  if (!session) return;
10534
10576
  await this.sendQueue.enqueue(
@@ -10538,7 +10580,7 @@ Task completed.
10538
10580
  async sendNotification(notification) {
10539
10581
  this.getTracer(notification.sessionId)?.log("telegram", { action: "notification:send", sessionId: notification.sessionId, type: notification.type });
10540
10582
  if (notification.sessionId === this.core.assistantManager?.get("telegram")?.id) return;
10541
- log35.info(
10583
+ log36.info(
10542
10584
  { sessionId: notification.sessionId, type: notification.type },
10543
10585
  "Notification sent"
10544
10586
  );
@@ -10577,7 +10619,7 @@ Task completed.
10577
10619
  }
10578
10620
  async createSessionThread(sessionId, name) {
10579
10621
  this.getTracer(sessionId)?.log("telegram", { action: "thread:create", sessionId, name });
10580
- log35.info({ sessionId, name }, "Session topic created");
10622
+ log36.info({ sessionId, name }, "Session topic created");
10581
10623
  return String(
10582
10624
  await createSessionTopic(this.bot, this.telegramConfig.chatId, name)
10583
10625
  );
@@ -10588,7 +10630,7 @@ Task completed.
10588
10630
  if (!session) return;
10589
10631
  const threadId = Number(session.threadId);
10590
10632
  if (!threadId) {
10591
- 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");
10592
10634
  return;
10593
10635
  }
10594
10636
  await renameSessionTopic(
@@ -10607,7 +10649,7 @@ Task completed.
10607
10649
  try {
10608
10650
  await this.bot.api.deleteForumTopic(this.telegramConfig.chatId, topicId);
10609
10651
  } catch (err) {
10610
- log35.warn(
10652
+ log36.warn(
10611
10653
  { err, sessionId, topicId },
10612
10654
  "Failed to delete forum topic (may already be deleted)"
10613
10655
  );
@@ -10651,7 +10693,7 @@ Task completed.
10651
10693
  const buffer = Buffer.from(await response.arrayBuffer());
10652
10694
  return { buffer, filePath: file.file_path };
10653
10695
  } catch (err) {
10654
- log35.error({ err }, "Failed to download file from Telegram");
10696
+ log36.error({ err }, "Failed to download file from Telegram");
10655
10697
  return null;
10656
10698
  }
10657
10699
  }
@@ -10672,7 +10714,7 @@ Task completed.
10672
10714
  try {
10673
10715
  buffer = await this.fileService.convertOggToWav(buffer);
10674
10716
  } catch (err) {
10675
- log35.warn({ err }, "OGG\u2192WAV conversion failed, saving original OGG");
10717
+ log36.warn({ err }, "OGG\u2192WAV conversion failed, saving original OGG");
10676
10718
  fileName = "voice.ogg";
10677
10719
  mimeType = "audio/ogg";
10678
10720
  originalFilePath = void 0;
@@ -10704,7 +10746,7 @@ Task completed.
10704
10746
  userId: String(userId),
10705
10747
  text: text3,
10706
10748
  attachments: [att]
10707
- }).catch((err) => log35.error({ err }, "handleMessage error"));
10749
+ }).catch((err) => log36.error({ err }, "handleMessage error"));
10708
10750
  }
10709
10751
  async cleanupSkillCommands(sessionId) {
10710
10752
  this._pendingSkillCommands.delete(sessionId);
@@ -10778,15 +10820,15 @@ var ChannelAdapter = class {
10778
10820
  function nodeToWebWritable(nodeStream) {
10779
10821
  return new WritableStream({
10780
10822
  write(chunk) {
10781
- return new Promise((resolve6, reject) => {
10823
+ return new Promise((resolve7, reject) => {
10782
10824
  const ok2 = nodeStream.write(chunk);
10783
10825
  if (ok2) {
10784
- resolve6();
10826
+ resolve7();
10785
10827
  return;
10786
10828
  }
10787
10829
  const onDrain = () => {
10788
10830
  nodeStream.removeListener("error", onError);
10789
- resolve6();
10831
+ resolve7();
10790
10832
  };
10791
10833
  const onError = (err) => {
10792
10834
  nodeStream.removeListener("drain", onDrain);
@@ -10840,13 +10882,13 @@ init_config();
10840
10882
  // src/core/agents/agent-instance.ts
10841
10883
  import { spawn as spawn2, execFileSync } from "child_process";
10842
10884
  import { Transform } from "stream";
10843
- import fs8 from "fs";
10844
- import path7 from "path";
10885
+ import fs7 from "fs";
10886
+ import path6 from "path";
10845
10887
  import { ClientSideConnection, ndJsonStream } from "@agentclientprotocol/sdk";
10846
10888
 
10847
10889
  // src/core/security/path-guard.ts
10848
- import fs5 from "fs";
10849
- import path5 from "path";
10890
+ import fs4 from "fs";
10891
+ import path4 from "path";
10850
10892
  import ignore from "ignore";
10851
10893
  var DEFAULT_DENY_PATTERNS = [
10852
10894
  ".env",
@@ -10866,15 +10908,15 @@ var PathGuard = class {
10866
10908
  ig;
10867
10909
  constructor(options) {
10868
10910
  try {
10869
- this.cwd = fs5.realpathSync(path5.resolve(options.cwd));
10911
+ this.cwd = fs4.realpathSync(path4.resolve(options.cwd));
10870
10912
  } catch {
10871
- this.cwd = path5.resolve(options.cwd);
10913
+ this.cwd = path4.resolve(options.cwd);
10872
10914
  }
10873
10915
  this.allowedPaths = options.allowedPaths.map((p) => {
10874
10916
  try {
10875
- return fs5.realpathSync(path5.resolve(p));
10917
+ return fs4.realpathSync(path4.resolve(p));
10876
10918
  } catch {
10877
- return path5.resolve(p);
10919
+ return path4.resolve(p);
10878
10920
  }
10879
10921
  });
10880
10922
  this.ig = ignore();
@@ -10884,19 +10926,19 @@ var PathGuard = class {
10884
10926
  }
10885
10927
  }
10886
10928
  validatePath(targetPath, operation) {
10887
- const resolved = path5.resolve(targetPath);
10929
+ const resolved = path4.resolve(targetPath);
10888
10930
  let realPath;
10889
10931
  try {
10890
- realPath = fs5.realpathSync(resolved);
10932
+ realPath = fs4.realpathSync(resolved);
10891
10933
  } catch {
10892
10934
  realPath = resolved;
10893
10935
  }
10894
- if (operation === "write" && path5.basename(realPath) === ".openacpignore") {
10936
+ if (operation === "write" && path4.basename(realPath) === ".openacpignore") {
10895
10937
  return { allowed: false, reason: "Cannot write to .openacpignore" };
10896
10938
  }
10897
- const isWithinCwd = realPath === this.cwd || realPath.startsWith(this.cwd + path5.sep);
10939
+ const isWithinCwd = realPath === this.cwd || realPath.startsWith(this.cwd + path4.sep);
10898
10940
  const isWithinAllowed = this.allowedPaths.some(
10899
- (ap) => realPath === ap || realPath.startsWith(ap + path5.sep)
10941
+ (ap) => realPath === ap || realPath.startsWith(ap + path4.sep)
10900
10942
  );
10901
10943
  if (!isWithinCwd && !isWithinAllowed) {
10902
10944
  return {
@@ -10905,7 +10947,7 @@ var PathGuard = class {
10905
10947
  };
10906
10948
  }
10907
10949
  if (isWithinCwd && !isWithinAllowed) {
10908
- const relativePath = path5.relative(this.cwd, realPath);
10950
+ const relativePath = path4.relative(this.cwd, realPath);
10909
10951
  if (relativePath === ".openacpignore") {
10910
10952
  return { allowed: true, reason: "" };
10911
10953
  }
@@ -10920,15 +10962,15 @@ var PathGuard = class {
10920
10962
  }
10921
10963
  addAllowedPath(p) {
10922
10964
  try {
10923
- this.allowedPaths.push(fs5.realpathSync(path5.resolve(p)));
10965
+ this.allowedPaths.push(fs4.realpathSync(path4.resolve(p)));
10924
10966
  } catch {
10925
- this.allowedPaths.push(path5.resolve(p));
10967
+ this.allowedPaths.push(path4.resolve(p));
10926
10968
  }
10927
10969
  }
10928
10970
  static loadIgnoreFile(cwd) {
10929
- const ignorePath = path5.join(cwd, ".openacpignore");
10971
+ const ignorePath = path4.join(cwd, ".openacpignore");
10930
10972
  try {
10931
- const content = fs5.readFileSync(ignorePath, "utf-8");
10973
+ const content = fs4.readFileSync(ignorePath, "utf-8");
10932
10974
  return content.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
10933
10975
  } catch {
10934
10976
  return [];
@@ -11222,12 +11264,12 @@ var TerminalManager = class {
11222
11264
  signal: state.exitStatus.signal
11223
11265
  };
11224
11266
  }
11225
- return new Promise((resolve6) => {
11267
+ return new Promise((resolve7) => {
11226
11268
  state.process.on("exit", (code, signal) => {
11227
- resolve6({ exitCode: code, signal });
11269
+ resolve7({ exitCode: code, signal });
11228
11270
  });
11229
11271
  if (state.exitStatus !== null) {
11230
- resolve6({
11272
+ resolve7({
11231
11273
  exitCode: state.exitStatus.exitCode,
11232
11274
  signal: state.exitStatus.signal
11233
11275
  });
@@ -11269,24 +11311,24 @@ var McpManager = class {
11269
11311
  };
11270
11312
 
11271
11313
  // src/core/utils/debug-tracer.ts
11272
- import fs7 from "fs";
11273
- import path6 from "path";
11314
+ import fs6 from "fs";
11315
+ import path5 from "path";
11274
11316
  var DEBUG_ENABLED = process.env.OPENACP_DEBUG === "true" || process.env.OPENACP_DEBUG === "1";
11275
11317
  var DebugTracer = class {
11276
11318
  constructor(sessionId, workingDirectory) {
11277
11319
  this.sessionId = sessionId;
11278
11320
  this.workingDirectory = workingDirectory;
11279
- this.logDir = path6.join(workingDirectory, ".log");
11321
+ this.logDir = path5.join(workingDirectory, ".log");
11280
11322
  }
11281
11323
  dirCreated = false;
11282
11324
  logDir;
11283
11325
  log(layer, data) {
11284
11326
  try {
11285
11327
  if (!this.dirCreated) {
11286
- fs7.mkdirSync(this.logDir, { recursive: true });
11328
+ fs6.mkdirSync(this.logDir, { recursive: true });
11287
11329
  this.dirCreated = true;
11288
11330
  }
11289
- const filePath = path6.join(this.logDir, `${this.sessionId}_${layer}.jsonl`);
11331
+ const filePath = path5.join(this.logDir, `${this.sessionId}_${layer}.jsonl`);
11290
11332
  const seen = /* @__PURE__ */ new WeakSet();
11291
11333
  const line = JSON.stringify({ ts: Date.now(), ...data }, (_key, value) => {
11292
11334
  if (typeof value === "object" && value !== null) {
@@ -11295,7 +11337,7 @@ var DebugTracer = class {
11295
11337
  }
11296
11338
  return value;
11297
11339
  }) + "\n";
11298
- fs7.appendFileSync(filePath, line);
11340
+ fs6.appendFileSync(filePath, line);
11299
11341
  } catch {
11300
11342
  }
11301
11343
  }
@@ -11314,11 +11356,11 @@ init_events();
11314
11356
  var log4 = createChildLogger({ module: "agent-instance" });
11315
11357
  function findPackageRoot(startDir) {
11316
11358
  let dir = startDir;
11317
- while (dir !== path7.dirname(dir)) {
11318
- if (fs8.existsSync(path7.join(dir, "package.json"))) {
11359
+ while (dir !== path6.dirname(dir)) {
11360
+ if (fs7.existsSync(path6.join(dir, "package.json"))) {
11319
11361
  return dir;
11320
11362
  }
11321
- dir = path7.dirname(dir);
11363
+ dir = path6.dirname(dir);
11322
11364
  }
11323
11365
  return startDir;
11324
11366
  }
@@ -11330,26 +11372,26 @@ function resolveAgentCommand(cmd) {
11330
11372
  }
11331
11373
  for (const root of searchRoots) {
11332
11374
  const packageDirs = [
11333
- path7.resolve(root, "node_modules", "@zed-industries", cmd, "dist", "index.js"),
11334
- 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")
11335
11377
  ];
11336
11378
  for (const jsPath of packageDirs) {
11337
- if (fs8.existsSync(jsPath)) {
11379
+ if (fs7.existsSync(jsPath)) {
11338
11380
  return { command: process.execPath, args: [jsPath] };
11339
11381
  }
11340
11382
  }
11341
11383
  }
11342
11384
  for (const root of searchRoots) {
11343
- const localBin = path7.resolve(root, "node_modules", ".bin", cmd);
11344
- if (fs8.existsSync(localBin)) {
11345
- 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");
11346
11388
  if (content.startsWith("#!/usr/bin/env node")) {
11347
11389
  return { command: process.execPath, args: [localBin] };
11348
11390
  }
11349
11391
  const match = content.match(/"([^"]+\.js)"/);
11350
11392
  if (match) {
11351
- const target = path7.resolve(path7.dirname(localBin), match[1]);
11352
- if (fs8.existsSync(target)) {
11393
+ const target = path6.resolve(path6.dirname(localBin), match[1]);
11394
+ if (fs7.existsSync(target)) {
11353
11395
  return { command: process.execPath, args: [target] };
11354
11396
  }
11355
11397
  }
@@ -11359,7 +11401,7 @@ function resolveAgentCommand(cmd) {
11359
11401
  const fullPath = execFileSync("which", [cmd], { encoding: "utf-8" }).trim();
11360
11402
  if (fullPath) {
11361
11403
  try {
11362
- const content = fs8.readFileSync(fullPath, "utf-8");
11404
+ const content = fs7.readFileSync(fullPath, "utf-8");
11363
11405
  if (content.startsWith("#!/usr/bin/env node")) {
11364
11406
  return { command: process.execPath, args: [fullPath] };
11365
11407
  }
@@ -11378,16 +11420,16 @@ function resolveAgentCommand(cmd) {
11378
11420
  candidates.push(dir);
11379
11421
  }
11380
11422
  };
11381
- addCandidate(path7.dirname(process.execPath));
11423
+ addCandidate(path6.dirname(process.execPath));
11382
11424
  try {
11383
- addCandidate(path7.dirname(fs8.realpathSync(process.execPath)));
11425
+ addCandidate(path6.dirname(fs7.realpathSync(process.execPath)));
11384
11426
  } catch {
11385
11427
  }
11386
11428
  addCandidate("/opt/homebrew/bin");
11387
11429
  addCandidate("/usr/local/bin");
11388
11430
  for (const dir of candidates) {
11389
- const candidate = path7.join(dir, cmd);
11390
- if (fs8.existsSync(candidate)) {
11431
+ const candidate = path6.join(dir, cmd);
11432
+ if (fs7.existsSync(candidate)) {
11391
11433
  log4.info({ cmd, resolved: candidate }, "Resolved package runner from fallback search");
11392
11434
  return { command: candidate, args: [] };
11393
11435
  }
@@ -11450,7 +11492,7 @@ var AgentInstance = class _AgentInstance extends TypedEmitter {
11450
11492
  env: filterEnv(process.env, agentDef.env)
11451
11493
  }
11452
11494
  );
11453
- await new Promise((resolve6, reject) => {
11495
+ await new Promise((resolve7, reject) => {
11454
11496
  instance.child.on("error", (err) => {
11455
11497
  reject(
11456
11498
  new Error(
@@ -11458,7 +11500,7 @@ var AgentInstance = class _AgentInstance extends TypedEmitter {
11458
11500
  )
11459
11501
  );
11460
11502
  });
11461
- instance.child.on("spawn", () => resolve6());
11503
+ instance.child.on("spawn", () => resolve7());
11462
11504
  });
11463
11505
  instance.stderrCapture = new StderrCapture(50);
11464
11506
  instance.child.stderr.on("data", (chunk) => {
@@ -11554,10 +11596,11 @@ ${stderr}`
11554
11596
  allowedPaths
11555
11597
  );
11556
11598
  const resolvedMcp = _AgentInstance.mcpManager.resolve(mcpServers);
11557
- const response = await instance.connection.newSession({
11558
- cwd: workingDirectory,
11559
- mcpServers: resolvedMcp
11560
- });
11599
+ const response = await withAgentTimeout(
11600
+ instance.connection.newSession({ cwd: workingDirectory, mcpServers: resolvedMcp }),
11601
+ agentDef.name,
11602
+ "newSession"
11603
+ );
11561
11604
  log4.info(response, "newSession response");
11562
11605
  instance.sessionId = response.sessionId;
11563
11606
  instance.initialSessionResponse = response;
@@ -11585,11 +11628,11 @@ ${stderr}`
11585
11628
  const resolvedMcp = _AgentInstance.mcpManager.resolve(mcpServers);
11586
11629
  try {
11587
11630
  if (instance.agentCapabilities?.loadSession) {
11588
- const response = await instance.connection.loadSession({
11589
- sessionId: agentSessionId,
11590
- cwd: workingDirectory,
11591
- mcpServers: resolvedMcp
11592
- });
11631
+ const response = await withAgentTimeout(
11632
+ instance.connection.loadSession({ sessionId: agentSessionId, cwd: workingDirectory, mcpServers: resolvedMcp }),
11633
+ agentDef.name,
11634
+ "loadSession"
11635
+ );
11593
11636
  instance.sessionId = agentSessionId;
11594
11637
  instance.initialSessionResponse = response;
11595
11638
  instance.debugTracer = createDebugTracer(agentSessionId, workingDirectory);
@@ -11602,10 +11645,11 @@ ${stderr}`
11602
11645
  "Agent load complete"
11603
11646
  );
11604
11647
  } else {
11605
- const response = await instance.connection.unstable_resumeSession({
11606
- sessionId: agentSessionId,
11607
- cwd: workingDirectory
11608
- });
11648
+ const response = await withAgentTimeout(
11649
+ instance.connection.unstable_resumeSession({ sessionId: agentSessionId, cwd: workingDirectory }),
11650
+ agentDef.name,
11651
+ "resumeSession"
11652
+ );
11609
11653
  instance.sessionId = response.sessionId;
11610
11654
  instance.initialSessionResponse = response;
11611
11655
  instance.debugTracer = createDebugTracer(response.sessionId, workingDirectory);
@@ -11623,10 +11667,11 @@ ${stderr}`
11623
11667
  { err, agentSessionId },
11624
11668
  "Resume failed, falling back to new session"
11625
11669
  );
11626
- const response = await instance.connection.newSession({
11627
- cwd: workingDirectory,
11628
- mcpServers: resolvedMcp
11629
- });
11670
+ const response = await withAgentTimeout(
11671
+ instance.connection.newSession({ cwd: workingDirectory, mcpServers: resolvedMcp }),
11672
+ agentDef.name,
11673
+ "newSession (fallback)"
11674
+ );
11630
11675
  instance.sessionId = response.sessionId;
11631
11676
  instance.initialSessionResponse = response;
11632
11677
  instance.debugTracer = createDebugTracer(response.sessionId, workingDirectory);
@@ -11818,8 +11863,8 @@ ${stderr}`
11818
11863
  writePath = result.path;
11819
11864
  writeContent = result.content;
11820
11865
  }
11821
- await fs8.promises.mkdir(path7.dirname(writePath), { recursive: true });
11822
- 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");
11823
11868
  return {};
11824
11869
  },
11825
11870
  // ── Terminal operations (delegated to TerminalManager) ─────────────
@@ -11927,7 +11972,7 @@ ${skipNote}`;
11927
11972
  [Attachment access denied: ${attCheck.reason}]`;
11928
11973
  continue;
11929
11974
  }
11930
- const data = await fs8.promises.readFile(att.filePath);
11975
+ const data = await fs7.promises.readFile(att.filePath);
11931
11976
  contentBlocks.push({ type: "image", data: data.toString("base64"), mimeType: att.mimeType });
11932
11977
  } else if (att.type === "audio" && capabilities.audio) {
11933
11978
  const attCheck = this.pathGuard.validatePath(att.filePath, "read");
@@ -11937,7 +11982,7 @@ ${skipNote}`;
11937
11982
  [Attachment access denied: ${attCheck.reason}]`;
11938
11983
  continue;
11939
11984
  }
11940
- const data = await fs8.promises.readFile(att.filePath);
11985
+ const data = await fs7.promises.readFile(att.filePath);
11941
11986
  contentBlocks.push({ type: "audio", data: data.toString("base64"), mimeType: att.mimeType });
11942
11987
  } else {
11943
11988
  if (att.type === "image" || att.type === "audio") {
@@ -11964,15 +12009,15 @@ ${skipNote}`;
11964
12009
  this._destroying = true;
11965
12010
  this.terminalManager.destroyAll();
11966
12011
  if (this.child.exitCode !== null) return;
11967
- await new Promise((resolve6) => {
12012
+ await new Promise((resolve7) => {
11968
12013
  this.child.on("exit", () => {
11969
12014
  clearTimeout(forceKillTimer);
11970
- resolve6();
12015
+ resolve7();
11971
12016
  });
11972
12017
  this.child.kill("SIGTERM");
11973
12018
  const forceKillTimer = setTimeout(() => {
11974
12019
  if (this.child.exitCode === null) this.child.kill("SIGKILL");
11975
- resolve6();
12020
+ resolve7();
11976
12021
  }, 1e4);
11977
12022
  if (typeof forceKillTimer === "object" && forceKillTimer !== null && "unref" in forceKillTimer) {
11978
12023
  forceKillTimer.unref();
@@ -11980,6 +12025,26 @@ ${skipNote}`;
11980
12025
  });
11981
12026
  }
11982
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
+ }
11983
12048
 
11984
12049
  // src/core/agents/agent-manager.ts
11985
12050
  var AgentManager = class {
@@ -12026,8 +12091,8 @@ var PromptQueue = class {
12026
12091
  processorSettled = null;
12027
12092
  async enqueue(text3, attachments, routing, turnId) {
12028
12093
  if (this.processing) {
12029
- return new Promise((resolve6) => {
12030
- this.queue.push({ text: text3, attachments, routing, turnId, resolve: resolve6 });
12094
+ return new Promise((resolve7) => {
12095
+ this.queue.push({ text: text3, attachments, routing, turnId, resolve: resolve7 });
12031
12096
  });
12032
12097
  }
12033
12098
  await this.process(text3, attachments, routing, turnId);
@@ -12101,8 +12166,8 @@ var PermissionGate = class {
12101
12166
  this.request = request;
12102
12167
  this.settled = false;
12103
12168
  this.clearTimeout();
12104
- return new Promise((resolve6, reject) => {
12105
- this.resolveFn = resolve6;
12169
+ return new Promise((resolve7, reject) => {
12170
+ this.resolveFn = resolve7;
12106
12171
  this.rejectFn = reject;
12107
12172
  this.timeoutTimer = setTimeout(() => {
12108
12173
  this.reject("Permission request timed out (no response received)");
@@ -12151,7 +12216,7 @@ var PermissionGate = class {
12151
12216
 
12152
12217
  // src/core/sessions/session.ts
12153
12218
  init_log();
12154
- import * as fs9 from "fs";
12219
+ import * as fs8 from "fs";
12155
12220
 
12156
12221
  // src/core/sessions/turn-context.ts
12157
12222
  import { nanoid } from "nanoid";
@@ -12477,7 +12542,7 @@ ${text3}`;
12477
12542
  try {
12478
12543
  const audioPath = att.originalFilePath || att.filePath;
12479
12544
  const audioMime = att.originalFilePath ? "audio/ogg" : att.mimeType;
12480
- const audioBuffer = await fs9.promises.readFile(audioPath);
12545
+ const audioBuffer = await fs8.promises.readFile(audioPath);
12481
12546
  const result = await this.speechService.transcribe(audioBuffer, audioMime);
12482
12547
  this.log.info({ provider: "stt", duration: result.duration }, "Voice transcribed");
12483
12548
  this.emit(SessionEv.AGENT_EVENT, {
@@ -12708,10 +12773,14 @@ ${result.text}` : result.text;
12708
12773
  };
12709
12774
 
12710
12775
  // src/core/message-transformer.ts
12711
- import * as path8 from "path";
12776
+ import * as path7 from "path";
12712
12777
 
12713
12778
  // src/core/utils/extract-file-info.ts
12714
- function extractFileInfo(name, kind, content, rawInput, meta) {
12779
+ init_apply_patch_detection();
12780
+ function extractFileInfo(name, kind, content, rawInput, meta, rawOutput) {
12781
+ if (isApplyPatchOtherTool(kind, name, rawInput)) {
12782
+ return parseApplyPatchRawOutput(rawOutput);
12783
+ }
12715
12784
  if (kind && !["read", "edit", "write"].includes(kind)) return null;
12716
12785
  let info = null;
12717
12786
  if (meta) {
@@ -12766,6 +12835,37 @@ function extractFileInfo(name, kind, content, rawInput, meta) {
12766
12835
  if (!info.filePath || !info.content) return null;
12767
12836
  return info;
12768
12837
  }
12838
+ function parseApplyPatchRawOutput(rawOutput) {
12839
+ if (!rawOutput || typeof rawOutput !== "object" || Array.isArray(rawOutput)) return null;
12840
+ const output = rawOutput;
12841
+ const metadata = output.metadata;
12842
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) return null;
12843
+ const files = metadata.files;
12844
+ if (!Array.isArray(files)) return null;
12845
+ const sortedFiles = [...files].sort((a, b) => getApplyPatchFileScore(b) - getApplyPatchFileScore(a));
12846
+ for (const file of sortedFiles) {
12847
+ if (!file || typeof file !== "object" || Array.isArray(file)) continue;
12848
+ const f = file;
12849
+ const filePath = typeof f.filePath === "string" ? f.filePath : typeof f.relativePath === "string" ? f.relativePath : null;
12850
+ if (!filePath) continue;
12851
+ const after = typeof f.after === "string" ? f.after : null;
12852
+ if (!after) continue;
12853
+ const before = typeof f.before === "string" ? f.before : void 0;
12854
+ return {
12855
+ filePath,
12856
+ content: after,
12857
+ oldContent: before
12858
+ };
12859
+ }
12860
+ return null;
12861
+ }
12862
+ function getApplyPatchFileScore(file) {
12863
+ if (!file || typeof file !== "object" || Array.isArray(file)) return 0;
12864
+ const f = file;
12865
+ const additions = typeof f.additions === "number" && Number.isFinite(f.additions) && f.additions >= 0 ? f.additions : 0;
12866
+ const deletions = typeof f.deletions === "number" && Number.isFinite(f.deletions) && f.deletions >= 0 ? f.deletions : 0;
12867
+ return additions + deletions;
12868
+ }
12769
12869
  function resolveToolResponse(meta) {
12770
12870
  const claudeCode = meta.claudeCode;
12771
12871
  if (claudeCode?.toolResponse && typeof claudeCode.toolResponse === "object") {
@@ -12833,6 +12933,7 @@ function parseContent(content) {
12833
12933
  }
12834
12934
 
12835
12935
  // src/core/message-transformer.ts
12936
+ init_apply_patch_detection();
12836
12937
  init_log();
12837
12938
  var log5 = createChildLogger({ module: "message-transformer" });
12838
12939
  var BINARY_VIEWER_EXTENSIONS = /* @__PURE__ */ new Set([
@@ -12888,6 +12989,63 @@ function computeLineDiff(oldStr, newStr) {
12888
12989
  removed: Math.max(0, oldLines.length - prefixLen - suffixLen)
12889
12990
  };
12890
12991
  }
12992
+ function asRecord2(value) {
12993
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
12994
+ }
12995
+ function asNonNegativeNumber(value) {
12996
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
12997
+ }
12998
+ function extractDiffStatsFromRawOutput(rawOutput) {
12999
+ const output = asRecord2(rawOutput);
13000
+ const metadata = asRecord2(output?.metadata);
13001
+ if (!metadata) return null;
13002
+ const files = Array.isArray(metadata.files) ? metadata.files : null;
13003
+ if (files && files.length > 0) {
13004
+ let added2 = 0;
13005
+ let removed2 = 0;
13006
+ let hasAny = false;
13007
+ for (const file of files) {
13008
+ const fileMeta = asRecord2(file);
13009
+ if (!fileMeta) continue;
13010
+ const fileAdded = asNonNegativeNumber(fileMeta.additions);
13011
+ const fileRemoved = asNonNegativeNumber(fileMeta.deletions);
13012
+ if (fileAdded === null && fileRemoved === null) continue;
13013
+ added2 += fileAdded ?? 0;
13014
+ removed2 += fileRemoved ?? 0;
13015
+ hasAny = true;
13016
+ }
13017
+ if (hasAny && (added2 > 0 || removed2 > 0)) return { added: added2, removed: removed2 };
13018
+ }
13019
+ const added = asNonNegativeNumber(metadata.additions);
13020
+ const removed = asNonNegativeNumber(metadata.deletions);
13021
+ if (added === null && removed === null) return null;
13022
+ if ((added ?? 0) === 0 && (removed ?? 0) === 0) return null;
13023
+ return {
13024
+ added: added ?? 0,
13025
+ removed: removed ?? 0
13026
+ };
13027
+ }
13028
+ function extractDiffStatsFromToolPayload(name, kind, rawInput, rawOutput) {
13029
+ if (kind === "edit" || kind === "write") {
13030
+ const ri = asRecord2(rawInput);
13031
+ if (!ri) return null;
13032
+ const oldStr = typeof ri.old_string === "string" ? ri.old_string : typeof ri.oldText === "string" ? ri.oldText : null;
13033
+ const newStr = typeof ri.new_string === "string" ? ri.new_string : typeof ri.newText === "string" ? ri.newText : typeof ri.content === "string" ? ri.content : null;
13034
+ if (oldStr !== null && newStr !== null) {
13035
+ const stats = computeLineDiff(oldStr, newStr);
13036
+ return stats.added > 0 || stats.removed > 0 ? stats : null;
13037
+ }
13038
+ if (oldStr === null && newStr !== null && kind === "write") {
13039
+ const added = newStr.split("\n").length;
13040
+ return added > 0 ? { added, removed: 0 } : null;
13041
+ }
13042
+ return null;
13043
+ }
13044
+ if (isApplyPatchOtherTool(kind, name, rawInput)) {
13045
+ return extractDiffStatsFromRawOutput(rawOutput);
13046
+ }
13047
+ return null;
13048
+ }
12891
13049
  var MessageTransformer = class {
12892
13050
  tunnelService;
12893
13051
  /** Cache rawInput from tool_call so it's available in tool_update (which often lacks it) */
@@ -13037,22 +13195,11 @@ var MessageTransformer = class {
13037
13195
  return input2 !== null && input2 !== void 0 && typeof input2 === "object" && !Array.isArray(input2) && Object.keys(input2).length > 0;
13038
13196
  }
13039
13197
  enrichWithViewerLinks(event, metadata, sessionContext) {
13198
+ const name = "name" in event ? event.name || "" : "";
13040
13199
  const kind = "kind" in event ? event.kind : void 0;
13041
- if (!metadata.diffStats && (kind === "edit" || kind === "write")) {
13042
- const ri = event.rawInput;
13043
- if (ri) {
13044
- const oldStr = typeof ri.old_string === "string" ? ri.old_string : typeof ri.oldText === "string" ? ri.oldText : null;
13045
- const newStr = typeof ri.new_string === "string" ? ri.new_string : typeof ri.newText === "string" ? ri.newText : typeof ri.content === "string" ? ri.content : null;
13046
- if (oldStr !== null && newStr !== null) {
13047
- const stats = computeLineDiff(oldStr, newStr);
13048
- if (stats.added > 0 || stats.removed > 0) {
13049
- metadata.diffStats = stats;
13050
- }
13051
- } else if (oldStr === null && newStr !== null && kind === "write") {
13052
- const added = newStr.split("\n").length;
13053
- if (added > 0) metadata.diffStats = { added, removed: 0 };
13054
- }
13055
- }
13200
+ if (!metadata.diffStats) {
13201
+ const stats = extractDiffStatsFromToolPayload(name, kind, event.rawInput, event.rawOutput);
13202
+ if (stats) metadata.diffStats = stats;
13056
13203
  }
13057
13204
  if (!this.tunnelService || !sessionContext) {
13058
13205
  log5.debug(
@@ -13061,7 +13208,6 @@ var MessageTransformer = class {
13061
13208
  );
13062
13209
  return;
13063
13210
  }
13064
- const name = "name" in event ? event.name || "" : "";
13065
13211
  log5.debug(
13066
13212
  { name, kind, status: event.status, hasContent: !!event.content, hasRawInput: !!event.rawInput },
13067
13213
  "enrichWithViewerLinks: inspecting event"
@@ -13071,7 +13217,8 @@ var MessageTransformer = class {
13071
13217
  kind,
13072
13218
  event.content,
13073
13219
  event.rawInput,
13074
- event.meta
13220
+ event.meta,
13221
+ event.rawOutput
13075
13222
  );
13076
13223
  if (!fileInfo) {
13077
13224
  log5.debug(
@@ -13080,7 +13227,7 @@ var MessageTransformer = class {
13080
13227
  );
13081
13228
  return;
13082
13229
  }
13083
- const fileExt = path8.extname(fileInfo.filePath).toLowerCase();
13230
+ const fileExt = path7.extname(fileInfo.filePath).toLowerCase();
13084
13231
  if (BINARY_VIEWER_EXTENSIONS.has(fileExt)) {
13085
13232
  log5.debug({ kind, filePath: fileInfo.filePath }, "enrichWithViewerLinks: skipping binary file");
13086
13233
  return;
@@ -13589,12 +13736,12 @@ var SessionBridge = class {
13589
13736
  break;
13590
13737
  case "image_content": {
13591
13738
  if (this.deps.fileService) {
13592
- const fs33 = this.deps.fileService;
13739
+ const fs35 = this.deps.fileService;
13593
13740
  const sid = this.session.id;
13594
13741
  const { data, mimeType } = event;
13595
13742
  const buffer = Buffer.from(data, "base64");
13596
- const ext = fs33.extensionFromMime(mimeType);
13597
- 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) => {
13598
13745
  this.sendMessage(sid, {
13599
13746
  type: "attachment",
13600
13747
  text: "",
@@ -13606,12 +13753,12 @@ var SessionBridge = class {
13606
13753
  }
13607
13754
  case "audio_content": {
13608
13755
  if (this.deps.fileService) {
13609
- const fs33 = this.deps.fileService;
13756
+ const fs35 = this.deps.fileService;
13610
13757
  const sid = this.session.id;
13611
13758
  const { data, mimeType } = event;
13612
13759
  const buffer = Buffer.from(data, "base64");
13613
- const ext = fs33.extensionFromMime(mimeType);
13614
- 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) => {
13615
13762
  this.sendMessage(sid, {
13616
13763
  type: "attachment",
13617
13764
  text: "",
@@ -14163,14 +14310,13 @@ var SessionFactory = class {
14163
14310
  };
14164
14311
 
14165
14312
  // src/core/core.ts
14166
- import path16 from "path";
14167
- import os8 from "os";
14313
+ import path14 from "path";
14168
14314
  import { nanoid as nanoid3 } from "nanoid";
14169
14315
 
14170
14316
  // src/core/sessions/session-store.ts
14171
14317
  init_log();
14172
- import fs10 from "fs";
14173
- import path9 from "path";
14318
+ import fs9 from "fs";
14319
+ import path8 from "path";
14174
14320
  var log8 = createChildLogger({ module: "session-store" });
14175
14321
  var DEBOUNCE_MS = 2e3;
14176
14322
  var JsonFileSessionStore = class {
@@ -14252,9 +14398,9 @@ var JsonFileSessionStore = class {
14252
14398
  version: 1,
14253
14399
  sessions: Object.fromEntries(this.records)
14254
14400
  };
14255
- const dir = path9.dirname(this.filePath);
14256
- if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
14257
- 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));
14258
14404
  }
14259
14405
  destroy() {
14260
14406
  if (this.debounceTimer) clearTimeout(this.debounceTimer);
@@ -14267,10 +14413,10 @@ var JsonFileSessionStore = class {
14267
14413
  }
14268
14414
  }
14269
14415
  load() {
14270
- if (!fs10.existsSync(this.filePath)) return;
14416
+ if (!fs9.existsSync(this.filePath)) return;
14271
14417
  try {
14272
14418
  const raw = JSON.parse(
14273
- fs10.readFileSync(this.filePath, "utf-8")
14419
+ fs9.readFileSync(this.filePath, "utf-8")
14274
14420
  );
14275
14421
  if (raw.version !== 1) {
14276
14422
  log8.warn(
@@ -14286,7 +14432,7 @@ var JsonFileSessionStore = class {
14286
14432
  } catch (err) {
14287
14433
  log8.error({ err }, "Failed to load session store, backing up corrupt file");
14288
14434
  try {
14289
- fs10.renameSync(this.filePath, `${this.filePath}.bak`);
14435
+ fs9.renameSync(this.filePath, `${this.filePath}.bak`);
14290
14436
  } catch {
14291
14437
  }
14292
14438
  }
@@ -14520,92 +14666,13 @@ var AgentSwitchHandler = class {
14520
14666
  }
14521
14667
  };
14522
14668
 
14523
- // src/core/agents/agent-catalog.ts
14524
- import * as fs14 from "fs";
14525
- import * as path13 from "path";
14526
- import * as os6 from "os";
14527
-
14528
- // src/core/agents/agent-store.ts
14529
- init_log();
14530
- import * as fs12 from "fs";
14531
- import * as path11 from "path";
14532
- import * as os4 from "os";
14533
- import { z as z2 } from "zod";
14534
- var log10 = createChildLogger({ module: "agent-store" });
14535
- var InstalledAgentSchema = z2.object({
14536
- registryId: z2.string().nullable(),
14537
- name: z2.string(),
14538
- version: z2.string(),
14539
- distribution: z2.enum(["npx", "uvx", "binary", "custom"]),
14540
- command: z2.string(),
14541
- args: z2.array(z2.string()).default([]),
14542
- env: z2.record(z2.string(), z2.string()).default({}),
14543
- workingDirectory: z2.string().optional(),
14544
- installedAt: z2.string(),
14545
- binaryPath: z2.string().nullable().default(null)
14546
- });
14547
- var AgentStoreSchema = z2.object({
14548
- version: z2.number().default(1),
14549
- installed: z2.record(z2.string(), InstalledAgentSchema).default({})
14550
- });
14551
- var AgentStore = class {
14552
- data = { version: 1, installed: {} };
14553
- filePath;
14554
- constructor(filePath) {
14555
- this.filePath = filePath ?? path11.join(os4.homedir(), ".openacp", "agents.json");
14556
- }
14557
- load() {
14558
- if (!fs12.existsSync(this.filePath)) {
14559
- this.data = { version: 1, installed: {} };
14560
- return;
14561
- }
14562
- try {
14563
- const raw = JSON.parse(fs12.readFileSync(this.filePath, "utf-8"));
14564
- const result = AgentStoreSchema.safeParse(raw);
14565
- if (result.success) {
14566
- this.data = result.data;
14567
- } else {
14568
- log10.warn({ errors: result.error.issues }, "Invalid agents.json, starting fresh");
14569
- this.data = { version: 1, installed: {} };
14570
- }
14571
- } catch (err) {
14572
- log10.warn({ err }, "Failed to read agents.json, starting fresh");
14573
- this.data = { version: 1, installed: {} };
14574
- }
14575
- }
14576
- exists() {
14577
- return fs12.existsSync(this.filePath);
14578
- }
14579
- getInstalled() {
14580
- return this.data.installed;
14581
- }
14582
- getAgent(key) {
14583
- return this.data.installed[key];
14584
- }
14585
- addAgent(key, agent) {
14586
- this.data.installed[key] = agent;
14587
- this.save();
14588
- }
14589
- removeAgent(key) {
14590
- delete this.data.installed[key];
14591
- this.save();
14592
- }
14593
- hasAgent(key) {
14594
- return key in this.data.installed;
14595
- }
14596
- save() {
14597
- fs12.mkdirSync(path11.dirname(this.filePath), { recursive: true });
14598
- const tmpPath = this.filePath + ".tmp";
14599
- fs12.writeFileSync(tmpPath, JSON.stringify(this.data, null, 2), { mode: 384 });
14600
- fs12.renameSync(tmpPath, this.filePath);
14601
- }
14602
- };
14603
-
14604
14669
  // src/core/agents/agent-catalog.ts
14605
14670
  init_agent_installer();
14606
14671
  init_agent_dependencies();
14607
14672
  init_log();
14608
- 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" });
14609
14676
  var REGISTRY_URL = "https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json";
14610
14677
  var DEFAULT_TTL_HOURS = 24;
14611
14678
  var AgentCatalog = class {
@@ -14614,8 +14681,8 @@ var AgentCatalog = class {
14614
14681
  cachePath;
14615
14682
  agentsDir;
14616
14683
  constructor(store, cachePath, agentsDir) {
14617
- this.store = store ?? new AgentStore();
14618
- this.cachePath = cachePath ?? path13.join(os6.homedir(), ".openacp", "registry-cache.json");
14684
+ this.store = store;
14685
+ this.cachePath = cachePath;
14619
14686
  this.agentsDir = agentsDir;
14620
14687
  }
14621
14688
  load() {
@@ -14626,7 +14693,7 @@ var AgentCatalog = class {
14626
14693
  // --- Registry ---
14627
14694
  async fetchRegistry() {
14628
14695
  try {
14629
- log12.info("Fetching agent registry from CDN...");
14696
+ log11.info("Fetching agent registry from CDN...");
14630
14697
  const response = await fetch(REGISTRY_URL);
14631
14698
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
14632
14699
  const data = await response.json();
@@ -14636,11 +14703,11 @@ var AgentCatalog = class {
14636
14703
  ttlHours: DEFAULT_TTL_HOURS,
14637
14704
  data
14638
14705
  };
14639
- fs14.mkdirSync(path13.dirname(this.cachePath), { recursive: true });
14640
- fs14.writeFileSync(this.cachePath, JSON.stringify(cache, null, 2), { mode: 384 });
14641
- 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");
14642
14709
  } catch (err) {
14643
- log12.warn({ err }, "Failed to fetch registry, using cached data");
14710
+ log11.warn({ err }, "Failed to fetch registry, using cached data");
14644
14711
  }
14645
14712
  }
14646
14713
  async refreshRegistryIfStale() {
@@ -14671,7 +14738,7 @@ var AgentCatalog = class {
14671
14738
  }
14672
14739
  // --- Discovery ---
14673
14740
  getAvailable() {
14674
- const installed = this.store.getInstalled();
14741
+ const installed = this.getInstalledEntries();
14675
14742
  const items = [];
14676
14743
  const seenKeys = /* @__PURE__ */ new Set();
14677
14744
  for (const [key, agent] of Object.entries(installed)) {
@@ -14745,11 +14812,11 @@ var AgentCatalog = class {
14745
14812
  this.store.addAgent(key, data);
14746
14813
  }
14747
14814
  async uninstall(key) {
14748
- if (!this.store.hasAgent(key)) {
14749
- return { ok: false, error: `"${key}" is not installed.` };
14815
+ if (this.store.hasAgent(key)) {
14816
+ await uninstallAgent(key, this.store);
14817
+ return { ok: true };
14750
14818
  }
14751
- await uninstallAgent(key, this.store);
14752
- return { ok: true };
14819
+ return { ok: false, error: `"${key}" is not installed.` };
14753
14820
  }
14754
14821
  // --- Resolution (for AgentManager) ---
14755
14822
  resolve(key) {
@@ -14792,7 +14859,15 @@ var AgentCatalog = class {
14792
14859
  const dist = resolveDistribution(regAgent);
14793
14860
  if (dist) {
14794
14861
  agent.distribution = dist.type;
14795
- 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
+ }
14796
14871
  }
14797
14872
  }
14798
14873
  if (updated) {
@@ -14801,13 +14876,13 @@ var AgentCatalog = class {
14801
14876
  }
14802
14877
  }
14803
14878
  if (changed) {
14804
- log12.info("Enriched installed agents with registry data");
14879
+ log11.info("Enriched installed agents with registry data");
14805
14880
  }
14806
14881
  }
14807
14882
  isCacheStale() {
14808
- if (!fs14.existsSync(this.cachePath)) return true;
14883
+ if (!fs12.existsSync(this.cachePath)) return true;
14809
14884
  try {
14810
- const raw = JSON.parse(fs14.readFileSync(this.cachePath, "utf-8"));
14885
+ const raw = JSON.parse(fs12.readFileSync(this.cachePath, "utf-8"));
14811
14886
  const fetchedAt = new Date(raw.fetchedAt).getTime();
14812
14887
  const ttlMs = (raw.ttlHours ?? DEFAULT_TTL_HOURS) * 60 * 60 * 1e3;
14813
14888
  return Date.now() - fetchedAt > ttlMs;
@@ -14816,36 +14891,127 @@ var AgentCatalog = class {
14816
14891
  }
14817
14892
  }
14818
14893
  loadRegistryFromCacheOrSnapshot() {
14819
- if (fs14.existsSync(this.cachePath)) {
14894
+ if (fs12.existsSync(this.cachePath)) {
14820
14895
  try {
14821
- const raw = JSON.parse(fs14.readFileSync(this.cachePath, "utf-8"));
14896
+ const raw = JSON.parse(fs12.readFileSync(this.cachePath, "utf-8"));
14822
14897
  if (raw.data?.agents) {
14823
14898
  this.registryAgents = raw.data.agents;
14824
- log12.debug({ count: this.registryAgents.length }, "Loaded registry from cache");
14899
+ log11.debug({ count: this.registryAgents.length }, "Loaded registry from cache");
14825
14900
  return;
14826
14901
  }
14827
14902
  } catch {
14828
- log12.warn("Failed to load registry cache");
14903
+ log11.warn("Failed to load registry cache");
14829
14904
  }
14830
14905
  }
14831
14906
  try {
14832
14907
  const candidates = [
14833
- path13.join(import.meta.dirname, "data", "registry-snapshot.json"),
14834
- path13.join(import.meta.dirname, "..", "data", "registry-snapshot.json"),
14835
- 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")
14836
14911
  ];
14837
14912
  for (const candidate of candidates) {
14838
- if (fs14.existsSync(candidate)) {
14839
- const raw = JSON.parse(fs14.readFileSync(candidate, "utf-8"));
14913
+ if (fs12.existsSync(candidate)) {
14914
+ const raw = JSON.parse(fs12.readFileSync(candidate, "utf-8"));
14840
14915
  this.registryAgents = raw.agents ?? [];
14841
- log12.debug({ count: this.registryAgents.length }, "Loaded registry from bundled snapshot");
14916
+ log11.debug({ count: this.registryAgents.length }, "Loaded registry from bundled snapshot");
14842
14917
  return;
14843
14918
  }
14844
14919
  }
14845
- log12.warn("No registry data available (no cache, no snapshot)");
14920
+ log11.warn("No registry data available (no cache, no snapshot)");
14846
14921
  } catch {
14847
- 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;
14848
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: {} };
14988
+ }
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);
14849
15015
  }
14850
15016
  };
14851
15017
 
@@ -14855,7 +15021,7 @@ var EventBus = class extends TypedEmitter {
14855
15021
 
14856
15022
  // src/core/plugin/plugin-loader.ts
14857
15023
  import { createHash } from "crypto";
14858
- import { readFileSync as readFileSync5 } from "fs";
15024
+ import { readFileSync as readFileSync4 } from "fs";
14859
15025
  function resolveLoadOrder(plugins) {
14860
15026
  const overrideTargets = /* @__PURE__ */ new Set();
14861
15027
  for (const p of plugins) {
@@ -15096,32 +15262,28 @@ var ErrorTracker = class {
15096
15262
  }
15097
15263
  };
15098
15264
 
15099
- // src/core/plugin/plugin-context.ts
15100
- import path15 from "path";
15101
- import os7 from "os";
15102
-
15103
15265
  // src/core/plugin/plugin-storage.ts
15104
- import fs15 from "fs";
15105
- import path14 from "path";
15266
+ import fs14 from "fs";
15267
+ import path13 from "path";
15106
15268
  var PluginStorageImpl = class {
15107
15269
  kvPath;
15108
15270
  dataDir;
15109
15271
  writeChain = Promise.resolve();
15110
15272
  constructor(baseDir) {
15111
- this.dataDir = path14.join(baseDir, "data");
15112
- this.kvPath = path14.join(baseDir, "kv.json");
15113
- 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 });
15114
15276
  }
15115
15277
  readKv() {
15116
15278
  try {
15117
- const raw = fs15.readFileSync(this.kvPath, "utf-8");
15279
+ const raw = fs14.readFileSync(this.kvPath, "utf-8");
15118
15280
  return JSON.parse(raw);
15119
15281
  } catch {
15120
15282
  return {};
15121
15283
  }
15122
15284
  }
15123
15285
  writeKv(data) {
15124
- fs15.writeFileSync(this.kvPath, JSON.stringify(data), "utf-8");
15286
+ fs14.writeFileSync(this.kvPath, JSON.stringify(data), "utf-8");
15125
15287
  }
15126
15288
  async get(key) {
15127
15289
  const data = this.readKv();
@@ -15147,7 +15309,7 @@ var PluginStorageImpl = class {
15147
15309
  return Object.keys(this.readKv());
15148
15310
  }
15149
15311
  getDataDir() {
15150
- fs15.mkdirSync(this.dataDir, { recursive: true });
15312
+ fs14.mkdirSync(this.dataDir, { recursive: true });
15151
15313
  return this.dataDir;
15152
15314
  }
15153
15315
  };
@@ -15171,7 +15333,7 @@ function createPluginContext(opts) {
15171
15333
  config,
15172
15334
  core
15173
15335
  } = opts;
15174
- const instanceRoot = opts.instanceRoot ?? path15.join(os7.homedir(), ".openacp");
15336
+ const instanceRoot = opts.instanceRoot;
15175
15337
  const registeredListeners = [];
15176
15338
  const registeredCommands = [];
15177
15339
  const registeredMenuItemIds = [];
@@ -15194,7 +15356,7 @@ function createPluginContext(opts) {
15194
15356
  }
15195
15357
  };
15196
15358
  const baseLog = opts.log ?? noopLog;
15197
- const log36 = typeof baseLog.child === "function" ? baseLog.child({ plugin: pluginName }) : baseLog;
15359
+ const log37 = typeof baseLog.child === "function" ? baseLog.child({ plugin: pluginName }) : baseLog;
15198
15360
  const storageImpl = new PluginStorageImpl(storagePath);
15199
15361
  const storage = {
15200
15362
  async get(key) {
@@ -15221,7 +15383,7 @@ function createPluginContext(opts) {
15221
15383
  const ctx = {
15222
15384
  pluginName,
15223
15385
  pluginConfig,
15224
- log: log36,
15386
+ log: log37,
15225
15387
  storage,
15226
15388
  on(event, handler) {
15227
15389
  requirePermission(permissions, "events:read", "on()");
@@ -15256,7 +15418,7 @@ function createPluginContext(opts) {
15256
15418
  const registry = serviceRegistry.get("command-registry");
15257
15419
  if (registry && typeof registry.register === "function") {
15258
15420
  registry.register(def, pluginName);
15259
- log36.debug(`Command '/${def.name}' registered`);
15421
+ log37.debug(`Command '/${def.name}' registered`);
15260
15422
  }
15261
15423
  },
15262
15424
  async sendMessage(_sessionId, _content) {
@@ -15299,7 +15461,7 @@ function createPluginContext(opts) {
15299
15461
  const registry = serviceRegistry.get("field-registry");
15300
15462
  if (registry && typeof registry.register === "function") {
15301
15463
  registry.register(pluginName, fields);
15302
- log36.debug(`Registered ${fields.length} editable field(s) for ${pluginName}`);
15464
+ log37.debug(`Registered ${fields.length} editable field(s) for ${pluginName}`);
15303
15465
  }
15304
15466
  },
15305
15467
  get sessions() {
@@ -15355,13 +15517,13 @@ init_events();
15355
15517
  var SETUP_TIMEOUT_MS = 3e4;
15356
15518
  var TEARDOWN_TIMEOUT_MS = 1e4;
15357
15519
  function withTimeout(promise, ms, label) {
15358
- return new Promise((resolve6, reject) => {
15520
+ return new Promise((resolve7, reject) => {
15359
15521
  const timer = setTimeout(() => reject(new Error(`Timeout: ${label} exceeded ${ms}ms`)), ms);
15360
15522
  if (typeof timer === "object" && timer !== null && "unref" in timer) {
15361
15523
  ;
15362
15524
  timer.unref();
15363
15525
  }
15364
- promise.then(resolve6, reject).finally(() => clearTimeout(timer));
15526
+ promise.then(resolve7, reject).finally(() => clearTimeout(timer));
15365
15527
  });
15366
15528
  }
15367
15529
  function resolvePluginConfig(pluginName, configManager) {
@@ -15682,7 +15844,7 @@ Examples:
15682
15844
  ${baseCmd} api status
15683
15845
  ${baseCmd} api new claude-code ~/my-project --channel <current_channel>
15684
15846
  ${baseCmd} api cancel <id>
15685
- ${baseCmd} config set workspace.baseDir ~/code
15847
+ ${baseCmd} config set logging.level debug
15686
15848
  ${baseCmd} agents install gemini
15687
15849
  \`\`\`
15688
15850
 
@@ -15855,10 +16017,10 @@ function createConfigSection(core) {
15855
16017
  title: "Configuration",
15856
16018
  priority: 30,
15857
16019
  buildContext: () => {
15858
- const config = core.configManager.get();
16020
+ const workspace = core.configManager.resolveWorkspace();
15859
16021
  const speechSvc = core.lifecycleManager?.serviceRegistry.get("speech");
15860
16022
  const sttActive = speechSvc ? speechSvc.isSTTAvailable() : false;
15861
- return `Workspace base: ${config.workspace.baseDir}
16023
+ return `Workspace base: ${workspace}
15862
16024
  STT: ${sttActive ? "configured \u2705" : "Not configured"}`;
15863
16025
  },
15864
16026
  commands: [
@@ -16043,13 +16205,13 @@ var OpenACPCore = class {
16043
16205
  this.instanceContext = ctx;
16044
16206
  const config = configManager.get();
16045
16207
  this.agentCatalog = new AgentCatalog(
16046
- ctx ? new AgentStore(ctx.paths.agents) : void 0,
16047
- ctx?.paths.registryCache,
16048
- ctx?.paths.agentsDir
16208
+ new AgentStore(ctx.paths.agents),
16209
+ ctx.paths.registryCache,
16210
+ ctx.paths.agentsDir
16049
16211
  );
16050
16212
  this.agentCatalog.load();
16051
16213
  this.agentManager = new AgentManager(this.agentCatalog);
16052
- const storePath = ctx?.paths.sessions ?? path16.join(os8.homedir(), ".openacp", "sessions.json");
16214
+ const storePath = ctx.paths.sessions;
16053
16215
  this.sessionStore = new JsonFileSessionStore(
16054
16216
  storePath,
16055
16217
  config.sessionStore.ttlDays
@@ -16063,7 +16225,7 @@ var OpenACPCore = class {
16063
16225
  this.sessionManager,
16064
16226
  () => this.speechService,
16065
16227
  this.eventBus,
16066
- ctx?.root
16228
+ ctx.root
16067
16229
  );
16068
16230
  this.lifecycleManager = new LifecycleManager({
16069
16231
  serviceRegistry: new ServiceRegistry(),
@@ -16073,8 +16235,8 @@ var OpenACPCore = class {
16073
16235
  sessions: this.sessionManager,
16074
16236
  config: this.configManager,
16075
16237
  core: this,
16076
- storagePath: ctx?.paths.pluginsData ?? path16.join(os8.homedir(), ".openacp", "plugins", "data"),
16077
- instanceRoot: ctx?.root,
16238
+ storagePath: ctx.paths.pluginsData,
16239
+ instanceRoot: ctx.root,
16078
16240
  log: createChildLogger({ module: "plugin" })
16079
16241
  });
16080
16242
  this.sessionFactory.middlewareChain = this.lifecycleManager.middlewareChain;
@@ -16138,9 +16300,7 @@ var OpenACPCore = class {
16138
16300
  }
16139
16301
  );
16140
16302
  registerCoreMenuItems(this.menuRegistry);
16141
- if (ctx?.root) {
16142
- this.assistantRegistry.setInstanceRoot(path16.dirname(ctx.root));
16143
- }
16303
+ this.assistantRegistry.setInstanceRoot(path14.dirname(ctx.root));
16144
16304
  this.assistantRegistry.register(createSessionsSection(this));
16145
16305
  this.assistantRegistry.register(createAgentsSection(this));
16146
16306
  this.assistantRegistry.register(createConfigSection(this));
@@ -16439,8 +16599,8 @@ ${text3}`;
16439
16599
  message: `Agent '${agentName}' not found`
16440
16600
  };
16441
16601
  }
16442
- const { existsSync: existsSync19 } = await import("fs");
16443
- if (!existsSync19(cwd)) {
16602
+ const { existsSync: existsSync18 } = await import("fs");
16603
+ if (!existsSync18(cwd)) {
16444
16604
  return {
16445
16605
  ok: false,
16446
16606
  error: "invalid_cwd",
@@ -16786,19 +16946,30 @@ init_doctor();
16786
16946
  init_config_registry();
16787
16947
 
16788
16948
  // src/core/config/config-editor.ts
16789
- import * as path28 from "path";
16949
+ import * as path30 from "path";
16790
16950
  import * as clack2 from "@clack/prompts";
16791
16951
 
16792
16952
  // src/cli/autostart.ts
16793
16953
  init_log();
16794
16954
  import { execFileSync as execFileSync6 } from "child_process";
16795
- import * as fs26 from "fs";
16796
- import * as path24 from "path";
16797
- import * as os11 from "os";
16955
+ import * as fs25 from "fs";
16956
+ import * as path23 from "path";
16957
+ import * as os7 from "os";
16798
16958
  var log18 = createChildLogger({ module: "autostart" });
16799
- var LAUNCHD_LABEL = "com.openacp.daemon";
16800
- var LAUNCHD_PLIST_PATH = path24.join(os11.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
16801
- 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
+ }
16802
16973
  function isAutoStartSupported() {
16803
16974
  return process.platform === "darwin" || process.platform === "linux";
16804
16975
  }
@@ -16809,20 +16980,26 @@ function escapeSystemdValue(str) {
16809
16980
  const escaped = str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\$/g, "$$$$").replace(/%/g, "%%");
16810
16981
  return `"${escaped}"`;
16811
16982
  }
16812
- function generateLaunchdPlist(nodePath, cliPath, logDir2) {
16813
- 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");
16814
16986
  return `<?xml version="1.0" encoding="UTF-8"?>
16815
16987
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
16816
16988
  <plist version="1.0">
16817
16989
  <dict>
16818
16990
  <key>Label</key>
16819
- <string>${LAUNCHD_LABEL}</string>
16991
+ <string>${label}</string>
16820
16992
  <key>ProgramArguments</key>
16821
16993
  <array>
16822
16994
  <string>${escapeXml(nodePath)}</string>
16823
16995
  <string>${escapeXml(cliPath)}</string>
16824
16996
  <string>--daemon-child</string>
16825
16997
  </array>
16998
+ <key>EnvironmentVariables</key>
16999
+ <dict>
17000
+ <key>OPENACP_INSTANCE_ROOT</key>
17001
+ <string>${escapeXml(instanceRoot)}</string>
17002
+ </dict>
16826
17003
  <key>RunAtLoad</key>
16827
17004
  <true/>
16828
17005
  <key>KeepAlive</key>
@@ -16838,43 +17015,85 @@ function generateLaunchdPlist(nodePath, cliPath, logDir2) {
16838
17015
  </plist>
16839
17016
  `;
16840
17017
  }
16841
- function generateSystemdUnit(nodePath, cliPath) {
17018
+ function generateSystemdUnit(nodePath, cliPath, instanceRoot, instanceId) {
17019
+ const serviceName = getSystemdServiceName(instanceId);
16842
17020
  return `[Unit]
16843
- Description=OpenACP Daemon
17021
+ Description=OpenACP Daemon (${instanceId})
16844
17022
 
16845
17023
  [Service]
16846
17024
  ExecStart=${escapeSystemdValue(nodePath)} ${escapeSystemdValue(cliPath)} --daemon-child
17025
+ Environment=OPENACP_INSTANCE_ROOT=${escapeSystemdValue(instanceRoot)}
16847
17026
  Restart=on-failure
16848
17027
 
16849
17028
  [Install]
16850
17029
  WantedBy=default.target
17030
+ # Service name: ${serviceName}
16851
17031
  `;
16852
17032
  }
16853
- 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) {
16854
17063
  if (!isAutoStartSupported()) {
16855
17064
  return { success: false, error: "Auto-start not supported on this platform" };
16856
17065
  }
16857
17066
  const nodePath = process.execPath;
16858
- const cliPath = path24.resolve(process.argv[1]);
16859
- 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;
16860
17069
  try {
17070
+ migrateLegacy();
16861
17071
  if (process.platform === "darwin") {
16862
- const plist = generateLaunchdPlist(nodePath, cliPath, resolvedLogDir);
16863
- const dir = path24.dirname(LAUNCHD_PLIST_PATH);
16864
- fs26.mkdirSync(dir, { recursive: true });
16865
- fs26.writeFileSync(LAUNCHD_PLIST_PATH, plist);
16866
- execFileSync6("launchctl", ["load", LAUNCHD_PLIST_PATH], { stdio: "pipe" });
16867
- 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");
16868
17085
  return { success: true };
16869
17086
  }
16870
17087
  if (process.platform === "linux") {
16871
- const unit = generateSystemdUnit(nodePath, cliPath);
16872
- const dir = path24.dirname(SYSTEMD_SERVICE_PATH);
16873
- fs26.mkdirSync(dir, { recursive: true });
16874
- 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);
16875
17094
  execFileSync6("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
16876
- execFileSync6("systemctl", ["--user", "enable", "openacp"], { stdio: "pipe" });
16877
- log18.info("systemd user service installed");
17095
+ execFileSync6("systemctl", ["--user", "enable", serviceName], { stdio: "pipe" });
17096
+ log18.info({ instanceId }, "systemd user service installed");
16878
17097
  return { success: true };
16879
17098
  }
16880
17099
  return { success: false, error: "Unsupported platform" };
@@ -16884,31 +17103,35 @@ function installAutoStart(logDir2) {
16884
17103
  return { success: false, error: msg };
16885
17104
  }
16886
17105
  }
16887
- function uninstallAutoStart() {
17106
+ function uninstallAutoStart(instanceId) {
16888
17107
  if (!isAutoStartSupported()) {
16889
17108
  return { success: false, error: "Auto-start not supported on this platform" };
16890
17109
  }
16891
17110
  try {
16892
17111
  if (process.platform === "darwin") {
16893
- if (fs26.existsSync(LAUNCHD_PLIST_PATH)) {
17112
+ const plistPath = getLaunchdPlistPath(instanceId);
17113
+ if (fs25.existsSync(plistPath)) {
17114
+ const uid = process.getuid();
16894
17115
  try {
16895
- execFileSync6("launchctl", ["unload", LAUNCHD_PLIST_PATH], { stdio: "pipe" });
17116
+ execFileSync6("launchctl", ["bootout", `gui/${uid}`, plistPath], { stdio: "pipe" });
16896
17117
  } catch {
16897
17118
  }
16898
- fs26.unlinkSync(LAUNCHD_PLIST_PATH);
16899
- log18.info("LaunchAgent removed");
17119
+ fs25.unlinkSync(plistPath);
17120
+ log18.info({ instanceId }, "LaunchAgent removed");
16900
17121
  }
16901
17122
  return { success: true };
16902
17123
  }
16903
17124
  if (process.platform === "linux") {
16904
- if (fs26.existsSync(SYSTEMD_SERVICE_PATH)) {
17125
+ const servicePath = getSystemdServicePath(instanceId);
17126
+ const serviceName = getSystemdServiceName(instanceId);
17127
+ if (fs25.existsSync(servicePath)) {
16905
17128
  try {
16906
- execFileSync6("systemctl", ["--user", "disable", "openacp"], { stdio: "pipe" });
17129
+ execFileSync6("systemctl", ["--user", "disable", serviceName], { stdio: "pipe" });
16907
17130
  } catch {
16908
17131
  }
16909
- fs26.unlinkSync(SYSTEMD_SERVICE_PATH);
17132
+ fs25.unlinkSync(servicePath);
16910
17133
  execFileSync6("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
16911
- log18.info("systemd user service removed");
17134
+ log18.info({ instanceId }, "systemd user service removed");
16912
17135
  }
16913
17136
  return { success: true };
16914
17137
  }
@@ -16919,16 +17142,41 @@ function uninstallAutoStart() {
16919
17142
  return { success: false, error: msg };
16920
17143
  }
16921
17144
  }
16922
- function isAutoStartInstalled() {
17145
+ function isAutoStartInstalled(instanceId) {
16923
17146
  if (process.platform === "darwin") {
16924
- return fs26.existsSync(LAUNCHD_PLIST_PATH);
17147
+ return fs25.existsSync(getLaunchdPlistPath(instanceId));
16925
17148
  }
16926
17149
  if (process.platform === "linux") {
16927
- return fs26.existsSync(SYSTEMD_SERVICE_PATH);
17150
+ return fs25.existsSync(getSystemdServicePath(instanceId));
16928
17151
  }
16929
17152
  return false;
16930
17153
  }
16931
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
+
16932
17180
  // src/core/config/config-editor.ts
16933
17181
  init_config();
16934
17182
  async function select3(opts) {
@@ -17054,29 +17302,28 @@ async function ensureDiscordPlugin() {
17054
17302
  }
17055
17303
  }
17056
17304
  }
17057
- async function editDiscord(_config, _updates) {
17305
+ async function editDiscord(_config, _updates, settingsManager, instanceRoot) {
17058
17306
  const pluginModule = await ensureDiscordPlugin();
17059
17307
  if (!pluginModule) return;
17060
17308
  const plugin = pluginModule.default;
17061
17309
  if (plugin?.configure) {
17062
- 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
+ }
17063
17314
  const { createInstallContext: createInstallContext2 } = await Promise.resolve().then(() => (init_install_context(), install_context_exports));
17064
- const { getGlobalRoot: getGlobalRoot2 } = await Promise.resolve().then(() => (init_instance_context(), instance_context_exports));
17065
- const root = getGlobalRoot2();
17066
- const basePath = path28.join(root, "plugins", "data");
17067
- const settingsManager = new SettingsManager2(basePath);
17068
17315
  const ctx = createInstallContext2({
17069
17316
  pluginName: plugin.name,
17070
17317
  settingsManager,
17071
- basePath,
17072
- instanceRoot: root
17318
+ basePath: settingsManager.getBasePath(),
17319
+ instanceRoot
17073
17320
  });
17074
17321
  await plugin.configure(ctx);
17075
17322
  } else {
17076
17323
  console.log(warn("This plugin does not have a configure() method yet."));
17077
17324
  }
17078
17325
  }
17079
- async function editChannels(config, updates, settingsManager) {
17326
+ async function editChannels(config, updates, settingsManager, instanceRoot) {
17080
17327
  let tgConfigured = false;
17081
17328
  let dcConfigured = false;
17082
17329
  if (settingsManager) {
@@ -17100,7 +17347,7 @@ async function editChannels(config, updates, settingsManager) {
17100
17347
  });
17101
17348
  if (choice === "back") break;
17102
17349
  if (choice === "telegram") await editTelegram(config, updates, settingsManager);
17103
- if (choice === "discord") await editDiscord(config, updates);
17350
+ if (choice === "discord") await editDiscord(config, updates, settingsManager, instanceRoot);
17104
17351
  }
17105
17352
  }
17106
17353
  async function editAgent(config, updates) {
@@ -17133,19 +17380,6 @@ async function editAgent(config, updates) {
17133
17380
  }
17134
17381
  }
17135
17382
  }
17136
- async function editWorkspace(config, updates) {
17137
- const currentDir = config.workspace?.baseDir ?? "~/openacp-workspace";
17138
- console.log(header("Workspace"));
17139
- console.log(` Base directory : ${currentDir}`);
17140
- console.log("");
17141
- const newDir = await input({
17142
- message: "Workspace base directory:",
17143
- default: currentDir,
17144
- validate: (val) => val.trim().length > 0 || "Path cannot be empty"
17145
- });
17146
- updates.workspace = { baseDir: newDir.trim() };
17147
- console.log(ok(`Workspace set to ${newDir.trim()}`));
17148
- }
17149
17383
  async function editSecurity(config, updates, settingsManager) {
17150
17384
  const ps = settingsManager ? await settingsManager.loadSettings("@openacp/security") : {};
17151
17385
  const sec = {
@@ -17244,10 +17478,11 @@ async function editLogging(config, updates) {
17244
17478
  }
17245
17479
  }
17246
17480
  }
17247
- async function editRunMode(config, updates) {
17481
+ async function editRunMode(config, updates, instanceRoot) {
17248
17482
  const currentMode = config.runMode ?? "foreground";
17249
17483
  const currentAutoStart = config.autoStart ?? false;
17250
- const autoStartInstalled = isAutoStartInstalled();
17484
+ const instanceId = instanceRoot ? resolveInstanceId(instanceRoot) : "default";
17485
+ const autoStartInstalled = isAutoStartInstalled(instanceId);
17251
17486
  const autoStartSupported = isAutoStartSupported();
17252
17487
  console.log(header("Run Mode"));
17253
17488
  console.log(` Current mode : ${c.bold}${currentMode}${c.reset}`);
@@ -17280,7 +17515,7 @@ async function editRunMode(config, updates) {
17280
17515
  if (choice === "daemon") {
17281
17516
  updates.runMode = "daemon";
17282
17517
  const logDir2 = config.logging?.logDir ?? "~/.openacp/logs";
17283
- const result = installAutoStart(expandHome3(logDir2));
17518
+ const result = installAutoStart(expandHome2(logDir2), instanceRoot, instanceId);
17284
17519
  if (result.success) {
17285
17520
  updates.autoStart = true;
17286
17521
  console.log(ok("Switched to daemon mode with auto-start"));
@@ -17291,7 +17526,7 @@ async function editRunMode(config, updates) {
17291
17526
  if (choice === "foreground") {
17292
17527
  updates.runMode = "foreground";
17293
17528
  updates.autoStart = false;
17294
- uninstallAutoStart();
17529
+ uninstallAutoStart(instanceId);
17295
17530
  console.log(ok("Switched to foreground mode"));
17296
17531
  }
17297
17532
  if (choice === "toggleAutoStart") {
@@ -17300,7 +17535,7 @@ async function editRunMode(config, updates) {
17300
17535
  return currentAutoStart;
17301
17536
  })();
17302
17537
  if (autoStartCurrent) {
17303
- const result = uninstallAutoStart();
17538
+ const result = uninstallAutoStart(instanceId);
17304
17539
  updates.autoStart = false;
17305
17540
  if (result.success) {
17306
17541
  console.log(ok("Auto-start disabled"));
@@ -17309,7 +17544,7 @@ async function editRunMode(config, updates) {
17309
17544
  }
17310
17545
  } else {
17311
17546
  const logDir2 = config.logging?.logDir ?? "~/.openacp/logs";
17312
- const result = installAutoStart(expandHome3(logDir2));
17547
+ const result = installAutoStart(expandHome2(logDir2), instanceRoot, instanceId);
17313
17548
  updates.autoStart = result.success;
17314
17549
  if (result.success) {
17315
17550
  console.log(ok("Auto-start enabled"));
@@ -17511,6 +17746,7 @@ async function runConfigEditor(configManager, mode = "file", apiPort, settingsMa
17511
17746
  await configManager.load();
17512
17747
  const config = configManager.get();
17513
17748
  const updates = {};
17749
+ const instanceRoot = path30.dirname(configManager.getConfigPath());
17514
17750
  console.log(`
17515
17751
  ${c.cyan}${c.bold}OpenACP Config Editor${c.reset}`);
17516
17752
  console.log(dim(`Config: ${configManager.getConfigPath()}`));
@@ -17523,7 +17759,6 @@ ${c.cyan}${c.bold}OpenACP Config Editor${c.reset}`);
17523
17759
  choices: [
17524
17760
  { name: "Channels", value: "channels" },
17525
17761
  { name: "Agent", value: "agent" },
17526
- { name: "Workspace", value: "workspace" },
17527
17762
  { name: "Security", value: "security" },
17528
17763
  { name: "Logging", value: "logging" },
17529
17764
  { name: "Run Mode", value: "runMode" },
@@ -17542,12 +17777,11 @@ ${c.cyan}${c.bold}OpenACP Config Editor${c.reset}`);
17542
17777
  break;
17543
17778
  }
17544
17779
  const sectionUpdates = {};
17545
- if (choice === "channels") await editChannels(config, sectionUpdates, settingsManager);
17780
+ if (choice === "channels") await editChannels(config, sectionUpdates, settingsManager, instanceRoot);
17546
17781
  else if (choice === "agent") await editAgent(config, sectionUpdates);
17547
- else if (choice === "workspace") await editWorkspace(config, sectionUpdates);
17548
17782
  else if (choice === "security") await editSecurity(config, sectionUpdates, settingsManager);
17549
17783
  else if (choice === "logging") await editLogging(config, sectionUpdates);
17550
- else if (choice === "runMode") await editRunMode(config, sectionUpdates);
17784
+ else if (choice === "runMode") await editRunMode(config, sectionUpdates, instanceRoot);
17551
17785
  else if (choice === "api") await editApi(config, sectionUpdates, settingsManager);
17552
17786
  else if (choice === "tunnel") await editTunnel(config, sectionUpdates, settingsManager);
17553
17787
  if (mode === "api" && Object.keys(sectionUpdates).length > 0) {
@@ -17569,17 +17803,17 @@ ${c.cyan}${c.bold}OpenACP Config Editor${c.reset}`);
17569
17803
  async function sendConfigViaApi(port, updates) {
17570
17804
  const { apiCall: call } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
17571
17805
  const paths = flattenToPaths(updates);
17572
- for (const { path: path34, value } of paths) {
17806
+ for (const { path: path35, value } of paths) {
17573
17807
  const res = await call(port, "/api/config", {
17574
17808
  method: "PATCH",
17575
17809
  headers: { "Content-Type": "application/json" },
17576
- body: JSON.stringify({ path: path34, value })
17810
+ body: JSON.stringify({ path: path35, value })
17577
17811
  });
17578
17812
  const data = await res.json();
17579
17813
  if (!res.ok) {
17580
- console.log(warn(`Failed to update ${path34}: ${data.error}`));
17814
+ console.log(warn(`Failed to update ${path35}: ${data.error}`));
17581
17815
  } else if (data.needsRestart) {
17582
- console.log(warn(`${path34} updated \u2014 restart required`));
17816
+ console.log(warn(`${path35} updated \u2014 restart required`));
17583
17817
  }
17584
17818
  }
17585
17819
  }
@@ -17599,30 +17833,25 @@ function flattenToPaths(obj, prefix = "") {
17599
17833
  // src/cli/daemon.ts
17600
17834
  init_config();
17601
17835
  import { spawn as spawn3 } from "child_process";
17602
- import * as fs29 from "fs";
17603
- import * as path29 from "path";
17604
- import * as os14 from "os";
17605
- var DEFAULT_ROOT2 = path29.join(os14.homedir(), ".openacp");
17836
+ import * as fs31 from "fs";
17837
+ import * as path31 from "path";
17606
17838
  function getPidPath(root) {
17607
- const base = root ?? DEFAULT_ROOT2;
17608
- return path29.join(base, "openacp.pid");
17839
+ return path31.join(root, "openacp.pid");
17609
17840
  }
17610
17841
  function getLogDir(root) {
17611
- const base = root ?? DEFAULT_ROOT2;
17612
- return path29.join(base, "logs");
17842
+ return path31.join(root, "logs");
17613
17843
  }
17614
17844
  function getRunningMarker(root) {
17615
- const base = root ?? DEFAULT_ROOT2;
17616
- return path29.join(base, "running");
17845
+ return path31.join(root, "running");
17617
17846
  }
17618
17847
  function writePidFile(pidPath, pid) {
17619
- const dir = path29.dirname(pidPath);
17620
- fs29.mkdirSync(dir, { recursive: true });
17621
- fs29.writeFileSync(pidPath, String(pid));
17848
+ const dir = path31.dirname(pidPath);
17849
+ fs31.mkdirSync(dir, { recursive: true });
17850
+ fs31.writeFileSync(pidPath, String(pid));
17622
17851
  }
17623
17852
  function readPidFile(pidPath) {
17624
17853
  try {
17625
- const content = fs29.readFileSync(pidPath, "utf-8").trim();
17854
+ const content = fs31.readFileSync(pidPath, "utf-8").trim();
17626
17855
  const pid = parseInt(content, 10);
17627
17856
  return isNaN(pid) ? null : pid;
17628
17857
  } catch {
@@ -17631,7 +17860,7 @@ function readPidFile(pidPath) {
17631
17860
  }
17632
17861
  function removePidFile(pidPath) {
17633
17862
  try {
17634
- fs29.unlinkSync(pidPath);
17863
+ fs31.unlinkSync(pidPath);
17635
17864
  } catch {
17636
17865
  }
17637
17866
  }
@@ -17646,7 +17875,7 @@ function isProcessRunning(pidPath) {
17646
17875
  return false;
17647
17876
  }
17648
17877
  }
17649
- function getStatus(pidPath = getPidPath()) {
17878
+ function getStatus(pidPath) {
17650
17879
  const pid = readPidFile(pidPath);
17651
17880
  if (pid === null) return { running: false };
17652
17881
  try {
@@ -17657,19 +17886,19 @@ function getStatus(pidPath = getPidPath()) {
17657
17886
  return { running: false };
17658
17887
  }
17659
17888
  }
17660
- function startDaemon(pidPath = getPidPath(), logDir2, instanceRoot) {
17889
+ function startDaemon(pidPath, logDir2, instanceRoot) {
17661
17890
  markRunning(instanceRoot);
17662
17891
  if (isProcessRunning(pidPath)) {
17663
17892
  const pid = readPidFile(pidPath);
17664
17893
  return { error: `Already running (PID ${pid})` };
17665
17894
  }
17666
- const resolvedLogDir = logDir2 ? expandHome3(logDir2) : getLogDir(instanceRoot);
17667
- fs29.mkdirSync(resolvedLogDir, { recursive: true });
17668
- const logFile = path29.join(resolvedLogDir, "openacp.log");
17669
- 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]);
17670
17899
  const nodePath = process.execPath;
17671
- const out = fs29.openSync(logFile, "a");
17672
- const err = fs29.openSync(logFile, "a");
17900
+ const out = fs31.openSync(logFile, "a");
17901
+ const err = fs31.openSync(logFile, "a");
17673
17902
  const child = spawn3(nodePath, [cliPath, "--daemon-child"], {
17674
17903
  detached: true,
17675
17904
  stdio: ["ignore", out, err],
@@ -17678,8 +17907,8 @@ function startDaemon(pidPath = getPidPath(), logDir2, instanceRoot) {
17678
17907
  ...instanceRoot ? { OPENACP_INSTANCE_ROOT: instanceRoot } : {}
17679
17908
  }
17680
17909
  });
17681
- fs29.closeSync(out);
17682
- fs29.closeSync(err);
17910
+ fs31.closeSync(out);
17911
+ fs31.closeSync(err);
17683
17912
  if (!child.pid) {
17684
17913
  return { error: "Failed to spawn daemon process" };
17685
17914
  }
@@ -17687,8 +17916,8 @@ function startDaemon(pidPath = getPidPath(), logDir2, instanceRoot) {
17687
17916
  child.unref();
17688
17917
  return { pid: child.pid };
17689
17918
  }
17690
- function sleep(ms) {
17691
- return new Promise((resolve6) => setTimeout(resolve6, ms));
17919
+ function sleep2(ms) {
17920
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
17692
17921
  }
17693
17922
  function isProcessAlive2(pid) {
17694
17923
  try {
@@ -17700,7 +17929,7 @@ function isProcessAlive2(pid) {
17700
17929
  return "dead";
17701
17930
  }
17702
17931
  }
17703
- async function stopDaemon(pidPath = getPidPath(), instanceRoot) {
17932
+ async function stopDaemon(pidPath, instanceRoot) {
17704
17933
  const pid = readPidFile(pidPath);
17705
17934
  if (pid === null) return { stopped: false, error: "Not running (no PID file)" };
17706
17935
  const status = isProcessAlive2(pid);
@@ -17722,7 +17951,7 @@ async function stopDaemon(pidPath = getPidPath(), instanceRoot) {
17722
17951
  const TIMEOUT = 5e3;
17723
17952
  const start = Date.now();
17724
17953
  while (Date.now() - start < TIMEOUT) {
17725
- await sleep(POLL_INTERVAL);
17954
+ await sleep2(POLL_INTERVAL);
17726
17955
  const s = isProcessAlive2(pid);
17727
17956
  if (s === "dead" || s === "eperm") {
17728
17957
  removePidFile(pidPath);
@@ -17739,7 +17968,7 @@ async function stopDaemon(pidPath = getPidPath(), instanceRoot) {
17739
17968
  }
17740
17969
  const killStart = Date.now();
17741
17970
  while (Date.now() - killStart < 1e3) {
17742
- await sleep(POLL_INTERVAL);
17971
+ await sleep2(POLL_INTERVAL);
17743
17972
  const s = isProcessAlive2(pid);
17744
17973
  if (s === "dead" || s === "eperm") {
17745
17974
  removePidFile(pidPath);
@@ -17750,12 +17979,12 @@ async function stopDaemon(pidPath = getPidPath(), instanceRoot) {
17750
17979
  }
17751
17980
  function markRunning(root) {
17752
17981
  const marker = getRunningMarker(root);
17753
- fs29.mkdirSync(path29.dirname(marker), { recursive: true });
17754
- fs29.writeFileSync(marker, "");
17982
+ fs31.mkdirSync(path31.dirname(marker), { recursive: true });
17983
+ fs31.writeFileSync(marker, "");
17755
17984
  }
17756
17985
  function clearRunning(root) {
17757
17986
  try {
17758
- fs29.unlinkSync(getRunningMarker(root));
17987
+ fs31.unlinkSync(getRunningMarker(root));
17759
17988
  } catch {
17760
17989
  }
17761
17990
  }
@@ -17771,7 +18000,7 @@ init_static_server();
17771
18000
 
17772
18001
  // src/plugins/telegram/topic-manager.ts
17773
18002
  init_log();
17774
- var log21 = createChildLogger({ module: "topic-manager" });
18003
+ var log22 = createChildLogger({ module: "topic-manager" });
17775
18004
  var TopicManager = class {
17776
18005
  constructor(sessionManager, adapter, systemTopicIds) {
17777
18006
  this.sessionManager = sessionManager;
@@ -17810,7 +18039,7 @@ var TopicManager = class {
17810
18039
  try {
17811
18040
  await this.adapter.deleteSessionThread?.(sessionId);
17812
18041
  } catch (err) {
17813
- 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");
17814
18043
  }
17815
18044
  }
17816
18045
  await this.sessionManager.removeRecord(sessionId);
@@ -17833,7 +18062,7 @@ var TopicManager = class {
17833
18062
  try {
17834
18063
  await this.adapter.deleteSessionThread?.(record.sessionId);
17835
18064
  } catch (err) {
17836
- 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");
17837
18066
  }
17838
18067
  }
17839
18068
  await this.sessionManager.removeRecord(record.sessionId);
@@ -18455,7 +18684,7 @@ Config file: \`~/.openacp/config.json\`
18455
18684
  - Agent list is fetched from the ACP Registry CDN and cached locally (24h)
18456
18685
 
18457
18686
  ### Workspace
18458
- - **workspace.baseDir** \u2014 Base directory for project folders (default: \`~/openacp-workspace\`)
18687
+ - Workspace directory is the parent of \`.openacp/\` (where you ran \`openacp\` setup)
18459
18688
 
18460
18689
  ### Security
18461
18690
  - **security.allowedUserIds** \u2014 Restrict who can use the bot (empty = everyone)
@@ -18611,7 +18840,7 @@ export {
18611
18840
  createChildLogger,
18612
18841
  createSessionLogger,
18613
18842
  createTurnContext,
18614
- expandHome3 as expandHome,
18843
+ expandHome2 as expandHome,
18615
18844
  extractContentText,
18616
18845
  formatTokens,
18617
18846
  formatToolSummary,