@openacp/cli 2026.402.5 → 2026.403.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1634,21 +1634,23 @@ var init_security_guard = __esm({
1634
1634
  "src/plugins/security/security-guard.ts"() {
1635
1635
  "use strict";
1636
1636
  SecurityGuard = class {
1637
- constructor(configManager, sessionManager) {
1638
- this.configManager = configManager;
1637
+ constructor(getSecurityConfig, sessionManager) {
1638
+ this.getSecurityConfig = getSecurityConfig;
1639
1639
  this.sessionManager = sessionManager;
1640
1640
  }
1641
- checkAccess(message) {
1642
- const config = this.configManager.get();
1643
- if (config.security.allowedUserIds.length > 0) {
1641
+ async checkAccess(message) {
1642
+ const config = await this.getSecurityConfig();
1643
+ const allowedIds = config.allowedUserIds ?? [];
1644
+ const maxSessions = config.maxConcurrentSessions ?? 20;
1645
+ if (allowedIds.length > 0) {
1644
1646
  const userId = String(message.userId);
1645
- if (!config.security.allowedUserIds.includes(userId)) {
1647
+ if (!allowedIds.includes(userId)) {
1646
1648
  return { allowed: false, reason: "Unauthorized user" };
1647
1649
  }
1648
1650
  }
1649
1651
  const active = this.sessionManager.listSessions().filter((s) => s.status === "active" || s.status === "initializing");
1650
- if (active.length >= config.security.maxConcurrentSessions) {
1651
- return { allowed: false, reason: `Session limit reached (${config.security.maxConcurrentSessions})` };
1652
+ if (active.length >= maxSessions) {
1653
+ return { allowed: false, reason: `Session limit reached (${maxSessions})` };
1652
1654
  }
1653
1655
  return { allowed: true };
1654
1656
  }
@@ -1668,7 +1670,7 @@ function createSecurityPlugin() {
1668
1670
  description: "User access control and session limits",
1669
1671
  essential: false,
1670
1672
  permissions: ["services:register", "middleware:register", "kernel:access", "commands:register"],
1671
- inheritableKeys: ["allowedUsers", "maxSessionsPerUser", "rateLimits"],
1673
+ inheritableKeys: ["allowedUserIds", "maxConcurrentSessions", "sessionTimeoutMinutes"],
1672
1674
  async install(ctx) {
1673
1675
  const { settings, legacyConfig, terminal } = ctx;
1674
1676
  if (legacyConfig) {
@@ -1745,10 +1747,28 @@ function createSecurityPlugin() {
1745
1747
  },
1746
1748
  async setup(ctx) {
1747
1749
  const core = ctx.core;
1748
- const guard = new SecurityGuard(core.configManager, core.sessionManager);
1750
+ const settingsManager = core.lifecycleManager?.settingsManager;
1751
+ const pluginName = ctx.pluginName;
1752
+ const getSecurityConfig = async () => {
1753
+ if (settingsManager) {
1754
+ const settings = await settingsManager.loadSettings(pluginName);
1755
+ if (Object.keys(settings).length > 0) {
1756
+ return {
1757
+ allowedUserIds: settings.allowedUserIds ?? [],
1758
+ maxConcurrentSessions: settings.maxConcurrentSessions ?? 20
1759
+ };
1760
+ }
1761
+ }
1762
+ const cfg = ctx.pluginConfig;
1763
+ return {
1764
+ allowedUserIds: cfg.allowedUserIds ?? [],
1765
+ maxConcurrentSessions: cfg.maxConcurrentSessions ?? 20
1766
+ };
1767
+ };
1768
+ const guard = new SecurityGuard(getSecurityConfig, core.sessionManager);
1749
1769
  ctx.registerMiddleware("message:incoming", {
1750
1770
  handler: async (payload, next) => {
1751
- const access2 = guard.checkAccess(payload);
1771
+ const access2 = await guard.checkAccess(payload);
1752
1772
  if (!access2.allowed) {
1753
1773
  ctx.log.info(`Access denied: ${access2.reason}`);
1754
1774
  return null;
@@ -3869,7 +3889,7 @@ var init_speech = __esm({
3869
3889
  essential: false,
3870
3890
  optionalPluginDependencies: { "@openacp/file-service": "^1.0.0" },
3871
3891
  permissions: ["services:register", "commands:register", "kernel:access"],
3872
- inheritableKeys: ["tts"],
3892
+ inheritableKeys: ["ttsProvider", "ttsVoice"],
3873
3893
  async install(ctx) {
3874
3894
  const { terminal, settings, legacyConfig } = ctx;
3875
3895
  const pluginsDir = ctx.instanceRoot ? path13.join(ctx.instanceRoot, "plugins") : void 0;
@@ -6826,6 +6846,289 @@ var init_token_store = __esm({
6826
6846
  }
6827
6847
  });
6828
6848
 
6849
+ // src/core/instance/instance-context.ts
6850
+ var instance_context_exports = {};
6851
+ __export(instance_context_exports, {
6852
+ createInstanceContext: () => createInstanceContext,
6853
+ generateSlug: () => generateSlug,
6854
+ getGlobalRoot: () => getGlobalRoot,
6855
+ resolveInstanceRoot: () => resolveInstanceRoot
6856
+ });
6857
+ import path21 from "path";
6858
+ import fs17 from "fs";
6859
+ import os10 from "os";
6860
+ function createInstanceContext(opts) {
6861
+ const { id, root, isGlobal } = opts;
6862
+ return {
6863
+ id,
6864
+ root,
6865
+ isGlobal,
6866
+ paths: {
6867
+ config: path21.join(root, "config.json"),
6868
+ sessions: path21.join(root, "sessions.json"),
6869
+ agents: path21.join(root, "agents.json"),
6870
+ registryCache: path21.join(root, "registry-cache.json"),
6871
+ plugins: path21.join(root, "plugins"),
6872
+ pluginsData: path21.join(root, "plugins", "data"),
6873
+ pluginRegistry: path21.join(root, "plugins.json"),
6874
+ logs: path21.join(root, "logs"),
6875
+ pid: path21.join(root, "openacp.pid"),
6876
+ running: path21.join(root, "running"),
6877
+ apiPort: path21.join(root, "api.port"),
6878
+ apiSecret: path21.join(root, "api-secret"),
6879
+ bin: path21.join(root, "bin"),
6880
+ cache: path21.join(root, "cache"),
6881
+ tunnels: path21.join(root, "tunnels.json"),
6882
+ agentsDir: path21.join(root, "agents")
6883
+ }
6884
+ };
6885
+ }
6886
+ function generateSlug(name) {
6887
+ const slug = name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
6888
+ return slug || "openacp";
6889
+ }
6890
+ function expandHome2(p2) {
6891
+ if (p2.startsWith("~")) return path21.join(os10.homedir(), p2.slice(1));
6892
+ return p2;
6893
+ }
6894
+ function resolveInstanceRoot(opts) {
6895
+ const cwd = opts.cwd ?? process.cwd();
6896
+ if (opts.dir) return path21.join(expandHome2(opts.dir), ".openacp");
6897
+ if (opts.local) return path21.join(cwd, ".openacp");
6898
+ if (opts.global) return path21.join(os10.homedir(), ".openacp");
6899
+ const localRoot = path21.join(cwd, ".openacp");
6900
+ if (fs17.existsSync(localRoot)) return localRoot;
6901
+ if (process.env.OPENACP_INSTANCE_ROOT) return process.env.OPENACP_INSTANCE_ROOT;
6902
+ return null;
6903
+ }
6904
+ function getGlobalRoot() {
6905
+ return path21.join(os10.homedir(), ".openacp");
6906
+ }
6907
+ var init_instance_context = __esm({
6908
+ "src/core/instance/instance-context.ts"() {
6909
+ "use strict";
6910
+ }
6911
+ });
6912
+
6913
+ // src/core/config/config-registry.ts
6914
+ var config_registry_exports = {};
6915
+ __export(config_registry_exports, {
6916
+ CONFIG_REGISTRY: () => CONFIG_REGISTRY,
6917
+ ConfigValidationError: () => ConfigValidationError,
6918
+ getConfigValue: () => getConfigValue,
6919
+ getFieldDef: () => getFieldDef,
6920
+ getFieldValueAsync: () => getFieldValueAsync,
6921
+ getSafeFields: () => getSafeFields,
6922
+ isHotReloadable: () => isHotReloadable,
6923
+ resolveOptions: () => resolveOptions,
6924
+ setFieldValueAsync: () => setFieldValueAsync
6925
+ });
6926
+ import * as fs18 from "fs";
6927
+ import * as path22 from "path";
6928
+ function getFieldDef(path69) {
6929
+ return CONFIG_REGISTRY.find((f) => f.path === path69);
6930
+ }
6931
+ function getSafeFields() {
6932
+ return CONFIG_REGISTRY.filter((f) => f.scope === "safe");
6933
+ }
6934
+ function isHotReloadable(path69) {
6935
+ const def = getFieldDef(path69);
6936
+ return def?.hotReload ?? false;
6937
+ }
6938
+ function resolveOptions(def, config) {
6939
+ if (!def.options) return void 0;
6940
+ return typeof def.options === "function" ? def.options(config) : def.options;
6941
+ }
6942
+ function getConfigValue(config, path69) {
6943
+ const parts = path69.split(".");
6944
+ let current = config;
6945
+ for (const part of parts) {
6946
+ if (current && typeof current === "object" && part in current) {
6947
+ current = current[part];
6948
+ } else {
6949
+ return void 0;
6950
+ }
6951
+ }
6952
+ return current;
6953
+ }
6954
+ async function getFieldValueAsync(field, configManager, settingsManager) {
6955
+ if (field.plugin && settingsManager) {
6956
+ const settings = await settingsManager.loadSettings(field.plugin.name);
6957
+ return settings[field.plugin.key];
6958
+ }
6959
+ return getConfigValue(configManager.get(), field.path);
6960
+ }
6961
+ function validateFieldValue(field, value) {
6962
+ switch (field.type) {
6963
+ case "number":
6964
+ if (typeof value !== "number" || Number.isNaN(value)) {
6965
+ throw new ConfigValidationError(`"${field.path}" expects a number, got ${typeof value}`);
6966
+ }
6967
+ break;
6968
+ case "toggle":
6969
+ if (typeof value !== "boolean") {
6970
+ throw new ConfigValidationError(`"${field.path}" expects a boolean, got ${typeof value}`);
6971
+ }
6972
+ break;
6973
+ case "string":
6974
+ if (typeof value !== "string") {
6975
+ throw new ConfigValidationError(`"${field.path}" expects a string, got ${typeof value}`);
6976
+ }
6977
+ break;
6978
+ case "select": {
6979
+ if (typeof value !== "string") {
6980
+ throw new ConfigValidationError(`"${field.path}" expects a string, got ${typeof value}`);
6981
+ }
6982
+ break;
6983
+ }
6984
+ }
6985
+ }
6986
+ async function setFieldValueAsync(field, value, configManager, settingsManager) {
6987
+ validateFieldValue(field, value);
6988
+ if (field.plugin && settingsManager) {
6989
+ await settingsManager.updatePluginSettings(field.plugin.name, {
6990
+ [field.plugin.key]: value
6991
+ });
6992
+ if (configManager.emit) {
6993
+ configManager.emit("config:changed", { path: field.path, value, oldValue: void 0 });
6994
+ }
6995
+ return { needsRestart: !field.hotReload };
6996
+ }
6997
+ await configManager.setPath(field.path, value);
6998
+ return { needsRestart: !field.hotReload };
6999
+ }
7000
+ var CONFIG_REGISTRY, ConfigValidationError;
7001
+ var init_config_registry = __esm({
7002
+ "src/core/config/config-registry.ts"() {
7003
+ "use strict";
7004
+ init_instance_context();
7005
+ CONFIG_REGISTRY = [
7006
+ {
7007
+ path: "defaultAgent",
7008
+ displayName: "Default Agent",
7009
+ group: "agent",
7010
+ type: "select",
7011
+ options: (config) => {
7012
+ try {
7013
+ const agentsPath = path22.join(getGlobalRoot(), "agents.json");
7014
+ if (fs18.existsSync(agentsPath)) {
7015
+ const data = JSON.parse(fs18.readFileSync(agentsPath, "utf-8"));
7016
+ return Object.keys(data.installed ?? {});
7017
+ }
7018
+ } catch {
7019
+ }
7020
+ return Object.keys(config.agents ?? {});
7021
+ },
7022
+ scope: "safe",
7023
+ hotReload: true
7024
+ },
7025
+ {
7026
+ path: "channels.telegram.outputMode",
7027
+ displayName: "Telegram Output Mode",
7028
+ group: "display",
7029
+ type: "select",
7030
+ options: ["low", "medium", "high"],
7031
+ scope: "safe",
7032
+ hotReload: true
7033
+ },
7034
+ {
7035
+ path: "channels.discord.outputMode",
7036
+ displayName: "Discord Output Mode",
7037
+ group: "display",
7038
+ type: "select",
7039
+ options: ["low", "medium", "high"],
7040
+ scope: "safe",
7041
+ hotReload: true
7042
+ },
7043
+ {
7044
+ path: "logging.level",
7045
+ displayName: "Log Level",
7046
+ group: "logging",
7047
+ type: "select",
7048
+ options: ["silent", "debug", "info", "warn", "error", "fatal"],
7049
+ scope: "safe",
7050
+ hotReload: true
7051
+ },
7052
+ {
7053
+ path: "tunnel.enabled",
7054
+ displayName: "Tunnel",
7055
+ group: "tunnel",
7056
+ type: "toggle",
7057
+ scope: "safe",
7058
+ hotReload: false,
7059
+ plugin: { name: "@openacp/tunnel", key: "enabled" }
7060
+ },
7061
+ {
7062
+ path: "security.maxConcurrentSessions",
7063
+ displayName: "Max Concurrent Sessions",
7064
+ group: "security",
7065
+ type: "number",
7066
+ scope: "safe",
7067
+ hotReload: true,
7068
+ plugin: { name: "@openacp/security", key: "maxConcurrentSessions" }
7069
+ },
7070
+ {
7071
+ path: "security.sessionTimeoutMinutes",
7072
+ displayName: "Session Timeout (min)",
7073
+ group: "security",
7074
+ type: "number",
7075
+ scope: "safe",
7076
+ hotReload: true,
7077
+ plugin: { name: "@openacp/security", key: "sessionTimeoutMinutes" }
7078
+ },
7079
+ {
7080
+ path: "workspace.baseDir",
7081
+ displayName: "Workspace Directory",
7082
+ group: "workspace",
7083
+ type: "string",
7084
+ scope: "safe",
7085
+ hotReload: true
7086
+ },
7087
+ {
7088
+ path: "sessionStore.ttlDays",
7089
+ displayName: "Session Store TTL (days)",
7090
+ group: "storage",
7091
+ type: "number",
7092
+ scope: "safe",
7093
+ hotReload: true
7094
+ },
7095
+ {
7096
+ path: "speech.stt.provider",
7097
+ displayName: "Speech to Text",
7098
+ group: "speech",
7099
+ type: "select",
7100
+ options: ["groq"],
7101
+ scope: "safe",
7102
+ hotReload: true,
7103
+ plugin: { name: "@openacp/speech", key: "sttProvider" }
7104
+ },
7105
+ {
7106
+ path: "speech.stt.apiKey",
7107
+ displayName: "STT API Key",
7108
+ group: "speech",
7109
+ type: "string",
7110
+ scope: "sensitive",
7111
+ hotReload: true,
7112
+ plugin: { name: "@openacp/speech", key: "groqApiKey" }
7113
+ },
7114
+ {
7115
+ path: "agentSwitch.labelHistory",
7116
+ displayName: "Label Agent in History",
7117
+ group: "agent",
7118
+ type: "toggle",
7119
+ scope: "safe",
7120
+ hotReload: true
7121
+ }
7122
+ ];
7123
+ ConfigValidationError = class extends Error {
7124
+ constructor(message) {
7125
+ super(message);
7126
+ this.name = "ConfigValidationError";
7127
+ }
7128
+ };
7129
+ }
7130
+ });
7131
+
6829
7132
  // src/plugins/api-server/middleware/error-handler.ts
6830
7133
  var error_handler_exports = {};
6831
7134
  __export(error_handler_exports, {
@@ -6848,6 +7151,16 @@ function globalErrorHandler(error, _request, reply) {
6848
7151
  });
6849
7152
  return;
6850
7153
  }
7154
+ if (error instanceof ConfigValidationError) {
7155
+ reply.status(400).send({
7156
+ error: {
7157
+ code: "VALIDATION_ERROR",
7158
+ message: error.message,
7159
+ statusCode: 400
7160
+ }
7161
+ });
7162
+ return;
7163
+ }
6851
7164
  if (error instanceof BadRequestError) {
6852
7165
  reply.status(400).send({
6853
7166
  error: {
@@ -6900,6 +7213,7 @@ var NotFoundError, BadRequestError, ServiceUnavailableError, AuthError;
6900
7213
  var init_error_handler = __esm({
6901
7214
  "src/plugins/api-server/middleware/error-handler.ts"() {
6902
7215
  "use strict";
7216
+ init_config_registry();
6903
7217
  NotFoundError = class extends Error {
6904
7218
  constructor(code, message) {
6905
7219
  super(message);
@@ -7204,7 +7518,7 @@ var init_sse_manager = __esm({
7204
7518
  handleRequest(req, res) {
7205
7519
  const parsedUrl = new URL(req.url || "", "http://localhost");
7206
7520
  const sessionFilter = parsedUrl.searchParams.get("sessionId");
7207
- console.log(`[sse] new connection from origin=${req.headers.origin ?? "none"} filter=${sessionFilter ?? "none"} total=${this.sseConnections.size + 1}`);
7521
+ console.log(`[sse] +connection total=${this.sseConnections.size + 1}`);
7208
7522
  const origin = req.headers.origin;
7209
7523
  const corsHeaders = {
7210
7524
  "Content-Type": "text/event-stream",
@@ -7226,7 +7540,7 @@ var init_sse_manager = __esm({
7226
7540
  const cleanup = () => {
7227
7541
  this.sseConnections.delete(res);
7228
7542
  this.sseCleanupHandlers.delete(res);
7229
- console.log(`[sse] connection closed, remaining=${this.sseConnections.size}`);
7543
+ console.log(`[sse] -connection remaining=${this.sseConnections.size}`);
7230
7544
  };
7231
7545
  this.sseCleanupHandlers.set(res, cleanup);
7232
7546
  req.on("close", cleanup);
@@ -7286,8 +7600,8 @@ var static_server_exports = {};
7286
7600
  __export(static_server_exports, {
7287
7601
  StaticServer: () => StaticServer
7288
7602
  });
7289
- import * as fs17 from "fs";
7290
- import * as path21 from "path";
7603
+ import * as fs19 from "fs";
7604
+ import * as path23 from "path";
7291
7605
  import { fileURLToPath as fileURLToPath2 } from "url";
7292
7606
  var MIME_TYPES, StaticServer;
7293
7607
  var init_static_server = __esm({
@@ -7311,16 +7625,16 @@ var init_static_server = __esm({
7311
7625
  this.uiDir = uiDir;
7312
7626
  if (!this.uiDir) {
7313
7627
  const __filename = fileURLToPath2(import.meta.url);
7314
- const candidate = path21.resolve(path21.dirname(__filename), "../../ui/dist");
7315
- if (fs17.existsSync(path21.join(candidate, "index.html"))) {
7628
+ const candidate = path23.resolve(path23.dirname(__filename), "../../ui/dist");
7629
+ if (fs19.existsSync(path23.join(candidate, "index.html"))) {
7316
7630
  this.uiDir = candidate;
7317
7631
  }
7318
7632
  if (!this.uiDir) {
7319
- const publishCandidate = path21.resolve(
7320
- path21.dirname(__filename),
7633
+ const publishCandidate = path23.resolve(
7634
+ path23.dirname(__filename),
7321
7635
  "../ui"
7322
7636
  );
7323
- if (fs17.existsSync(path21.join(publishCandidate, "index.html"))) {
7637
+ if (fs19.existsSync(path23.join(publishCandidate, "index.html"))) {
7324
7638
  this.uiDir = publishCandidate;
7325
7639
  }
7326
7640
  }
@@ -7332,12 +7646,12 @@ var init_static_server = __esm({
7332
7646
  serve(req, res) {
7333
7647
  if (!this.uiDir) return false;
7334
7648
  const urlPath = (req.url || "/").split("?")[0];
7335
- const safePath = path21.normalize(urlPath);
7336
- const filePath = path21.join(this.uiDir, safePath);
7337
- if (!filePath.startsWith(this.uiDir + path21.sep) && filePath !== this.uiDir)
7649
+ const safePath = path23.normalize(urlPath);
7650
+ const filePath = path23.join(this.uiDir, safePath);
7651
+ if (!filePath.startsWith(this.uiDir + path23.sep) && filePath !== this.uiDir)
7338
7652
  return false;
7339
- if (fs17.existsSync(filePath) && fs17.statSync(filePath).isFile()) {
7340
- const ext = path21.extname(filePath);
7653
+ if (fs19.existsSync(filePath) && fs19.statSync(filePath).isFile()) {
7654
+ const ext = path23.extname(filePath);
7341
7655
  const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
7342
7656
  const isHashed = /\.[a-zA-Z0-9]{8,}\.(js|css)$/.test(filePath);
7343
7657
  const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "no-cache";
@@ -7345,16 +7659,16 @@ var init_static_server = __esm({
7345
7659
  "Content-Type": contentType,
7346
7660
  "Cache-Control": cacheControl
7347
7661
  });
7348
- fs17.createReadStream(filePath).pipe(res);
7662
+ fs19.createReadStream(filePath).pipe(res);
7349
7663
  return true;
7350
7664
  }
7351
- const indexPath = path21.join(this.uiDir, "index.html");
7352
- if (fs17.existsSync(indexPath)) {
7665
+ const indexPath = path23.join(this.uiDir, "index.html");
7666
+ if (fs19.existsSync(indexPath)) {
7353
7667
  res.writeHead(200, {
7354
7668
  "Content-Type": "text/html; charset=utf-8",
7355
7669
  "Cache-Control": "no-cache"
7356
7670
  });
7357
- fs17.createReadStream(indexPath).pipe(res);
7671
+ fs19.createReadStream(indexPath).pipe(res);
7358
7672
  return true;
7359
7673
  }
7360
7674
  return false;
@@ -7906,240 +8220,17 @@ var init_config = __esm({
7906
8220
  }
7907
8221
  });
7908
8222
 
7909
- // src/core/instance/instance-context.ts
7910
- var instance_context_exports = {};
7911
- __export(instance_context_exports, {
7912
- createInstanceContext: () => createInstanceContext,
7913
- generateSlug: () => generateSlug,
7914
- getGlobalRoot: () => getGlobalRoot,
7915
- resolveInstanceRoot: () => resolveInstanceRoot
7916
- });
7917
- import path22 from "path";
7918
- import fs18 from "fs";
7919
- import os10 from "os";
7920
- function createInstanceContext(opts) {
7921
- const { id, root, isGlobal } = opts;
7922
- return {
7923
- id,
7924
- root,
7925
- isGlobal,
7926
- paths: {
7927
- config: path22.join(root, "config.json"),
7928
- sessions: path22.join(root, "sessions.json"),
7929
- agents: path22.join(root, "agents.json"),
7930
- registryCache: path22.join(root, "registry-cache.json"),
7931
- plugins: path22.join(root, "plugins"),
7932
- pluginsData: path22.join(root, "plugins", "data"),
7933
- pluginRegistry: path22.join(root, "plugins.json"),
7934
- logs: path22.join(root, "logs"),
7935
- pid: path22.join(root, "openacp.pid"),
7936
- running: path22.join(root, "running"),
7937
- apiPort: path22.join(root, "api.port"),
7938
- apiSecret: path22.join(root, "api-secret"),
7939
- bin: path22.join(root, "bin"),
7940
- cache: path22.join(root, "cache"),
7941
- tunnels: path22.join(root, "tunnels.json"),
7942
- agentsDir: path22.join(root, "agents")
8223
+ // src/core/config/config-migrations.ts
8224
+ import * as fs20 from "fs";
8225
+ import * as path24 from "path";
8226
+ function applyMigrations(raw, migrationList = migrations, ctx) {
8227
+ let changed = false;
8228
+ for (const migration of migrationList) {
8229
+ if (migration.apply(raw, ctx)) {
8230
+ changed = true;
7943
8231
  }
7944
- };
7945
- }
7946
- function generateSlug(name) {
7947
- const slug = name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
7948
- return slug || "openacp";
7949
- }
7950
- function expandHome2(p2) {
7951
- if (p2.startsWith("~")) return path22.join(os10.homedir(), p2.slice(1));
7952
- return p2;
7953
- }
7954
- function resolveInstanceRoot(opts) {
7955
- const cwd = opts.cwd ?? process.cwd();
7956
- if (opts.dir) return path22.join(expandHome2(opts.dir), ".openacp");
7957
- if (opts.local) return path22.join(cwd, ".openacp");
7958
- if (opts.global) return path22.join(os10.homedir(), ".openacp");
7959
- const localRoot = path22.join(cwd, ".openacp");
7960
- if (fs18.existsSync(localRoot)) return localRoot;
7961
- if (process.env.OPENACP_INSTANCE_ROOT) return process.env.OPENACP_INSTANCE_ROOT;
7962
- return null;
7963
- }
7964
- function getGlobalRoot() {
7965
- return path22.join(os10.homedir(), ".openacp");
7966
- }
7967
- var init_instance_context = __esm({
7968
- "src/core/instance/instance-context.ts"() {
7969
- "use strict";
7970
- }
7971
- });
7972
-
7973
- // src/core/config/config-registry.ts
7974
- var config_registry_exports = {};
7975
- __export(config_registry_exports, {
7976
- CONFIG_REGISTRY: () => CONFIG_REGISTRY,
7977
- getConfigValue: () => getConfigValue,
7978
- getFieldDef: () => getFieldDef,
7979
- getSafeFields: () => getSafeFields,
7980
- isHotReloadable: () => isHotReloadable,
7981
- resolveOptions: () => resolveOptions
7982
- });
7983
- import * as fs19 from "fs";
7984
- import * as path23 from "path";
7985
- function getFieldDef(path67) {
7986
- return CONFIG_REGISTRY.find((f) => f.path === path67);
7987
- }
7988
- function getSafeFields() {
7989
- return CONFIG_REGISTRY.filter((f) => f.scope === "safe");
7990
- }
7991
- function isHotReloadable(path67) {
7992
- const def = getFieldDef(path67);
7993
- return def?.hotReload ?? false;
7994
- }
7995
- function resolveOptions(def, config) {
7996
- if (!def.options) return void 0;
7997
- return typeof def.options === "function" ? def.options(config) : def.options;
7998
- }
7999
- function getConfigValue(config, path67) {
8000
- const parts = path67.split(".");
8001
- let current = config;
8002
- for (const part of parts) {
8003
- if (current && typeof current === "object" && part in current) {
8004
- current = current[part];
8005
- } else {
8006
- return void 0;
8007
- }
8008
- }
8009
- return current;
8010
- }
8011
- var CONFIG_REGISTRY;
8012
- var init_config_registry = __esm({
8013
- "src/core/config/config-registry.ts"() {
8014
- "use strict";
8015
- init_instance_context();
8016
- CONFIG_REGISTRY = [
8017
- {
8018
- path: "defaultAgent",
8019
- displayName: "Default Agent",
8020
- group: "agent",
8021
- type: "select",
8022
- options: (config) => {
8023
- try {
8024
- const agentsPath = path23.join(getGlobalRoot(), "agents.json");
8025
- if (fs19.existsSync(agentsPath)) {
8026
- const data = JSON.parse(fs19.readFileSync(agentsPath, "utf-8"));
8027
- return Object.keys(data.installed ?? {});
8028
- }
8029
- } catch {
8030
- }
8031
- return Object.keys(config.agents ?? {});
8032
- },
8033
- scope: "safe",
8034
- hotReload: true
8035
- },
8036
- {
8037
- path: "channels.telegram.outputMode",
8038
- displayName: "Telegram Output Mode",
8039
- group: "display",
8040
- type: "select",
8041
- options: ["low", "medium", "high"],
8042
- scope: "safe",
8043
- hotReload: true
8044
- },
8045
- {
8046
- path: "channels.discord.outputMode",
8047
- displayName: "Discord Output Mode",
8048
- group: "display",
8049
- type: "select",
8050
- options: ["low", "medium", "high"],
8051
- scope: "safe",
8052
- hotReload: true
8053
- },
8054
- {
8055
- path: "logging.level",
8056
- displayName: "Log Level",
8057
- group: "logging",
8058
- type: "select",
8059
- options: ["silent", "debug", "info", "warn", "error", "fatal"],
8060
- scope: "safe",
8061
- hotReload: true
8062
- },
8063
- {
8064
- path: "tunnel.enabled",
8065
- displayName: "Tunnel",
8066
- group: "tunnel",
8067
- type: "toggle",
8068
- scope: "safe",
8069
- hotReload: false
8070
- },
8071
- {
8072
- path: "security.maxConcurrentSessions",
8073
- displayName: "Max Concurrent Sessions",
8074
- group: "security",
8075
- type: "number",
8076
- scope: "safe",
8077
- hotReload: true
8078
- },
8079
- {
8080
- path: "security.sessionTimeoutMinutes",
8081
- displayName: "Session Timeout (min)",
8082
- group: "security",
8083
- type: "number",
8084
- scope: "safe",
8085
- hotReload: true
8086
- },
8087
- {
8088
- path: "workspace.baseDir",
8089
- displayName: "Workspace Directory",
8090
- group: "workspace",
8091
- type: "string",
8092
- scope: "safe",
8093
- hotReload: true
8094
- },
8095
- {
8096
- path: "sessionStore.ttlDays",
8097
- displayName: "Session Store TTL (days)",
8098
- group: "storage",
8099
- type: "number",
8100
- scope: "safe",
8101
- hotReload: true
8102
- },
8103
- {
8104
- path: "speech.stt.provider",
8105
- displayName: "Speech to Text",
8106
- group: "speech",
8107
- type: "select",
8108
- options: ["groq"],
8109
- scope: "safe",
8110
- hotReload: true
8111
- },
8112
- {
8113
- path: "speech.stt.apiKey",
8114
- displayName: "STT API Key",
8115
- group: "speech",
8116
- type: "string",
8117
- scope: "sensitive",
8118
- hotReload: true
8119
- },
8120
- {
8121
- path: "agentSwitch.labelHistory",
8122
- displayName: "Label Agent in History",
8123
- group: "agent",
8124
- type: "toggle",
8125
- scope: "safe",
8126
- hotReload: true
8127
- }
8128
- ];
8129
- }
8130
- });
8131
-
8132
- // src/core/config/config-migrations.ts
8133
- import * as fs20 from "fs";
8134
- import * as path24 from "path";
8135
- function applyMigrations(raw, migrationList = migrations, ctx) {
8136
- let changed = false;
8137
- for (const migration of migrationList) {
8138
- if (migration.apply(raw, ctx)) {
8139
- changed = true;
8140
- }
8141
- }
8142
- return { changed };
8232
+ }
8233
+ return { changed };
8143
8234
  }
8144
8235
  var log11, migrations;
8145
8236
  var init_config_migrations = __esm({
@@ -8533,6 +8624,26 @@ var init_config2 = __esm({
8533
8624
  fs21.mkdirSync(dir, { recursive: true });
8534
8625
  fs21.writeFileSync(this.configPath, JSON.stringify(config, null, 2));
8535
8626
  }
8627
+ async applyEnvToPluginSettings(settingsManager) {
8628
+ const pluginOverrides = [
8629
+ { envVar: "OPENACP_TUNNEL_ENABLED", pluginName: "@openacp/tunnel", key: "enabled", transform: (v) => v === "true" },
8630
+ { envVar: "OPENACP_TUNNEL_PORT", pluginName: "@openacp/tunnel", key: "port", transform: (v) => Number(v) },
8631
+ { envVar: "OPENACP_TUNNEL_PROVIDER", pluginName: "@openacp/tunnel", key: "provider" },
8632
+ { envVar: "OPENACP_API_PORT", pluginName: "@openacp/api-server", key: "port", transform: (v) => Number(v) },
8633
+ { envVar: "OPENACP_SPEECH_STT_PROVIDER", pluginName: "@openacp/speech", key: "sttProvider" },
8634
+ { envVar: "OPENACP_SPEECH_GROQ_API_KEY", pluginName: "@openacp/speech", key: "groqApiKey" },
8635
+ { envVar: "OPENACP_TELEGRAM_BOT_TOKEN", pluginName: "@openacp/telegram", key: "botToken" },
8636
+ { envVar: "OPENACP_TELEGRAM_CHAT_ID", pluginName: "@openacp/telegram", key: "chatId", transform: (v) => Number(v) }
8637
+ ];
8638
+ for (const { envVar, pluginName, key, transform } of pluginOverrides) {
8639
+ const value = process.env[envVar];
8640
+ if (value !== void 0) {
8641
+ const resolved = transform ? transform(value) : value;
8642
+ await settingsManager.updatePluginSettings(pluginName, { [key]: resolved });
8643
+ log12.debug({ envVar, pluginName, key }, "Env var override applied to plugin settings");
8644
+ }
8645
+ }
8646
+ }
8536
8647
  applyEnvOverrides(raw) {
8537
8648
  const overrides = [
8538
8649
  ["OPENACP_TELEGRAM_BOT_TOKEN", ["channels", "telegram", "botToken"]],
@@ -8645,18 +8756,19 @@ function redactDeep(obj) {
8645
8756
  }
8646
8757
  async function configRoutes(app, deps) {
8647
8758
  app.get("/editable", { preHandler: requireScopes("config:read") }, async () => {
8648
- const { getSafeFields: getSafeFields2, resolveOptions: resolveOptions2, getConfigValue: getConfigValue2 } = await Promise.resolve().then(() => (init_config_registry(), config_registry_exports));
8759
+ const { getSafeFields: getSafeFields2, resolveOptions: resolveOptions2, getFieldValueAsync: getFieldValueAsync2 } = await Promise.resolve().then(() => (init_config_registry(), config_registry_exports));
8649
8760
  const config = deps.core.configManager.get();
8761
+ const settingsManager = deps.core.settingsManager;
8650
8762
  const safeFields = getSafeFields2();
8651
- const fields = safeFields.map((def) => ({
8763
+ const fields = await Promise.all(safeFields.map(async (def) => ({
8652
8764
  path: def.path,
8653
8765
  displayName: def.displayName,
8654
8766
  group: def.group,
8655
8767
  type: def.type,
8656
8768
  options: resolveOptions2(def, config),
8657
- value: getConfigValue2(config, def.path),
8769
+ value: await getFieldValueAsync2(def, deps.core.configManager, settingsManager),
8658
8770
  hotReload: def.hotReload
8659
- }));
8771
+ })));
8660
8772
  return { fields };
8661
8773
  });
8662
8774
  app.get("/schema", { preHandler: requireScopes("config:read") }, async () => {
@@ -8676,16 +8788,20 @@ async function configRoutes(app, deps) {
8676
8788
  if (configPath.split(".").some((p2) => BLOCKED_KEYS.has(p2))) {
8677
8789
  return reply.status(400).send({ error: "Invalid config path" });
8678
8790
  }
8679
- const { getFieldDef: getFieldDef2 } = await Promise.resolve().then(() => (init_config_registry(), config_registry_exports));
8791
+ const { getFieldDef: getFieldDef2, setFieldValueAsync: setFieldValueAsync2 } = await Promise.resolve().then(() => (init_config_registry(), config_registry_exports));
8680
8792
  const fieldDef = getFieldDef2(configPath);
8681
8793
  if (!fieldDef || fieldDef.scope !== "safe") {
8682
8794
  return reply.status(403).send({
8683
8795
  error: "This config field cannot be modified via the API"
8684
8796
  });
8685
8797
  }
8686
- await deps.core.configManager.setPath(configPath, value);
8687
- const { isHotReloadable: isHotReloadable2 } = await Promise.resolve().then(() => (init_config_registry(), config_registry_exports));
8688
- const needsRestart = !isHotReloadable2(configPath);
8798
+ const settingsManager = deps.core.settingsManager;
8799
+ const { needsRestart } = await setFieldValueAsync2(
8800
+ fieldDef,
8801
+ value,
8802
+ deps.core.configManager,
8803
+ settingsManager
8804
+ );
8689
8805
  return {
8690
8806
  ok: true,
8691
8807
  needsRestart,
@@ -9196,13 +9312,152 @@ var init_workspace = __esm({
9196
9312
  }
9197
9313
  });
9198
9314
 
9315
+ // src/plugins/api-server/routes/plugins.ts
9316
+ var plugins_exports = {};
9317
+ __export(plugins_exports, {
9318
+ pluginRoutes: () => pluginRoutes
9319
+ });
9320
+ import path26 from "path";
9321
+ import os12 from "os";
9322
+ async function pluginRoutes(app, deps) {
9323
+ const { lifecycleManager } = deps;
9324
+ const admin = [requireScopes("system:admin")];
9325
+ app.get("/", { preHandler: admin }, async () => {
9326
+ if (!lifecycleManager?.registry) return { plugins: [] };
9327
+ const registry = lifecycleManager.registry;
9328
+ const loadedSet = new Set(lifecycleManager.loadedPlugins);
9329
+ const failedSet = new Set(lifecycleManager.failedPlugins);
9330
+ const loadOrderMap = new Map(lifecycleManager.plugins.map((p2) => [p2.name, p2]));
9331
+ const coreMap = new Map(corePlugins.map((p2) => [p2.name, p2]));
9332
+ const plugins = Array.from(registry.list().entries()).map(([name, entry]) => {
9333
+ const def = loadOrderMap.get(name) ?? coreMap.get(name);
9334
+ return {
9335
+ name,
9336
+ version: entry.version,
9337
+ description: entry.description,
9338
+ source: entry.source,
9339
+ enabled: entry.enabled,
9340
+ loaded: loadedSet.has(name),
9341
+ failed: failedSet.has(name),
9342
+ essential: def?.essential ?? false,
9343
+ hasConfigure: typeof def?.configure === "function"
9344
+ };
9345
+ });
9346
+ return { plugins };
9347
+ });
9348
+ app.get("/marketplace", { preHandler: admin }, async (_req, reply) => {
9349
+ try {
9350
+ const data = await registryClient.getRegistry();
9351
+ const installedNames = new Set(
9352
+ lifecycleManager?.registry ? Array.from(lifecycleManager.registry.list().keys()) : []
9353
+ );
9354
+ const plugins = data.plugins.map((p2) => ({
9355
+ ...p2,
9356
+ installed: installedNames.has(p2.name) || installedNames.has(p2.npm)
9357
+ }));
9358
+ return { plugins, categories: data.categories };
9359
+ } catch {
9360
+ return reply.status(503).send({ error: "Marketplace unavailable" });
9361
+ }
9362
+ });
9363
+ app.post("/:name/enable", { preHandler: admin }, async (req, reply) => {
9364
+ if (!lifecycleManager?.registry) {
9365
+ return reply.status(503).send({ error: "Plugin manager unavailable" });
9366
+ }
9367
+ const name = req.params.name;
9368
+ const registry = lifecycleManager.registry;
9369
+ const entry = registry.get(name);
9370
+ if (!entry) {
9371
+ return reply.status(404).send({ error: `Plugin "${name}" not found` });
9372
+ }
9373
+ if (lifecycleManager.loadedPlugins.includes(name)) {
9374
+ registry.setEnabled(name, true);
9375
+ await registry.save();
9376
+ return { ok: true };
9377
+ }
9378
+ let pluginDef = lifecycleManager.plugins.find((p2) => p2.name === name);
9379
+ if (!pluginDef) {
9380
+ if (entry.source === "builtin") {
9381
+ pluginDef = corePlugins.find((p2) => p2.name === name);
9382
+ } else {
9383
+ const { importFromDir: importFromDir2 } = await Promise.resolve().then(() => (init_plugin_installer(), plugin_installer_exports));
9384
+ const instanceRoot = lifecycleManager.instanceRoot ?? path26.join(os12.homedir(), ".openacp");
9385
+ const pluginsDir = path26.join(instanceRoot, "plugins");
9386
+ try {
9387
+ const mod = await importFromDir2(name, pluginsDir);
9388
+ pluginDef = mod.default ?? mod;
9389
+ } catch {
9390
+ return reply.status(500).send({ error: "Plugin module could not be loaded. Try restarting the server." });
9391
+ }
9392
+ }
9393
+ }
9394
+ if (!pluginDef) {
9395
+ return reply.status(500).send({ error: `Plugin definition not found for "${name}"` });
9396
+ }
9397
+ registry.setEnabled(name, true);
9398
+ await registry.save();
9399
+ await lifecycleManager.boot([pluginDef]);
9400
+ if (lifecycleManager.failedPlugins.includes(name)) {
9401
+ return reply.status(500).send({ error: `Plugin "${name}" failed to start` });
9402
+ }
9403
+ return { ok: true };
9404
+ });
9405
+ app.post("/:name/disable", { preHandler: admin }, async (req, reply) => {
9406
+ if (!lifecycleManager?.registry) {
9407
+ return reply.status(503).send({ error: "Plugin manager unavailable" });
9408
+ }
9409
+ const name = req.params.name;
9410
+ const registry = lifecycleManager.registry;
9411
+ const entry = registry.get(name);
9412
+ if (!entry) {
9413
+ return reply.status(404).send({ error: `Plugin "${name}" not found` });
9414
+ }
9415
+ const def = lifecycleManager.plugins.find((p2) => p2.name === name) ?? corePlugins.find((p2) => p2.name === name);
9416
+ if (def?.essential) {
9417
+ return reply.status(409).send({ error: "Essential plugins cannot be disabled" });
9418
+ }
9419
+ await lifecycleManager.unloadPlugin(name);
9420
+ registry.setEnabled(name, false);
9421
+ await registry.save();
9422
+ return { ok: true };
9423
+ });
9424
+ app.delete("/:name", { preHandler: admin }, async (req, reply) => {
9425
+ if (!lifecycleManager?.registry) {
9426
+ return reply.status(503).send({ error: "Plugin manager unavailable" });
9427
+ }
9428
+ const name = req.params.name;
9429
+ const registry = lifecycleManager.registry;
9430
+ const entry = registry.get(name);
9431
+ if (!entry) {
9432
+ return reply.status(404).send({ error: `Plugin "${name}" not found` });
9433
+ }
9434
+ if (entry.source === "builtin") {
9435
+ return reply.status(400).send({ error: "Builtin plugins cannot be uninstalled. Use disable instead." });
9436
+ }
9437
+ await lifecycleManager.unloadPlugin(name);
9438
+ registry.remove(name);
9439
+ await registry.save();
9440
+ return { ok: true };
9441
+ });
9442
+ }
9443
+ var registryClient;
9444
+ var init_plugins = __esm({
9445
+ "src/plugins/api-server/routes/plugins.ts"() {
9446
+ "use strict";
9447
+ init_auth();
9448
+ init_core_plugins();
9449
+ init_registry_client();
9450
+ registryClient = new RegistryClient();
9451
+ }
9452
+ });
9453
+
9199
9454
  // src/core/instance/instance-registry.ts
9200
9455
  var instance_registry_exports = {};
9201
9456
  __export(instance_registry_exports, {
9202
9457
  InstanceRegistry: () => InstanceRegistry
9203
9458
  });
9204
9459
  import fs22 from "fs";
9205
- import path26 from "path";
9460
+ import path27 from "path";
9206
9461
  var InstanceRegistry;
9207
9462
  var init_instance_registry = __esm({
9208
9463
  "src/core/instance/instance-registry.ts"() {
@@ -9240,7 +9495,7 @@ var init_instance_registry = __esm({
9240
9495
  }
9241
9496
  }
9242
9497
  save() {
9243
- const dir = path26.dirname(this.registryPath);
9498
+ const dir = path27.dirname(this.registryPath);
9244
9499
  fs22.mkdirSync(dir, { recursive: true });
9245
9500
  fs22.writeFileSync(this.registryPath, JSON.stringify(this.data, null, 2));
9246
9501
  }
@@ -9276,13 +9531,13 @@ __export(api_server_exports, {
9276
9531
  });
9277
9532
  import * as fs23 from "fs";
9278
9533
  import * as crypto2 from "crypto";
9279
- import * as path27 from "path";
9534
+ import * as path28 from "path";
9280
9535
  import { fileURLToPath as fileURLToPath3 } from "url";
9281
9536
  function getVersion() {
9282
9537
  if (cachedVersion) return cachedVersion;
9283
9538
  try {
9284
9539
  const __filename = fileURLToPath3(import.meta.url);
9285
- const pkgPath = path27.resolve(path27.dirname(__filename), "../../../package.json");
9540
+ const pkgPath = path28.resolve(path28.dirname(__filename), "../../../package.json");
9286
9541
  const pkg = JSON.parse(fs23.readFileSync(pkgPath, "utf-8"));
9287
9542
  cachedVersion = pkg.version ?? "0.0.0-dev";
9288
9543
  } catch {
@@ -9291,7 +9546,7 @@ function getVersion() {
9291
9546
  return cachedVersion;
9292
9547
  }
9293
9548
  function loadOrCreateSecret(secretFilePath) {
9294
- const dir = path27.dirname(secretFilePath);
9549
+ const dir = path28.dirname(secretFilePath);
9295
9550
  fs23.mkdirSync(dir, { recursive: true });
9296
9551
  try {
9297
9552
  const existing = fs23.readFileSync(secretFilePath, "utf-8").trim();
@@ -9317,7 +9572,7 @@ function loadOrCreateSecret(secretFilePath) {
9317
9572
  return secret;
9318
9573
  }
9319
9574
  function writePortFile(portFilePath, port) {
9320
- const dir = path27.dirname(portFilePath);
9575
+ const dir = path28.dirname(portFilePath);
9321
9576
  fs23.mkdirSync(dir, { recursive: true });
9322
9577
  fs23.writeFileSync(portFilePath, String(port));
9323
9578
  }
@@ -9409,10 +9664,10 @@ function createApiServerPlugin() {
9409
9664
  { port: apiConfig.port, host: apiConfig.host, instanceRoot },
9410
9665
  "API server plugin setup \u2014 config loaded"
9411
9666
  );
9412
- portFilePath = path27.join(instanceRoot, "api.port");
9413
- const secretFilePath = path27.join(instanceRoot, "api-secret");
9414
- const jwtSecretFilePath = path27.join(instanceRoot, "jwt-secret");
9415
- const tokensFilePath = path27.join(instanceRoot, "tokens.json");
9667
+ portFilePath = path28.join(instanceRoot, "api.port");
9668
+ const secretFilePath = path28.join(instanceRoot, "api-secret");
9669
+ const jwtSecretFilePath = path28.join(instanceRoot, "jwt-secret");
9670
+ const tokensFilePath = path28.join(instanceRoot, "tokens.json");
9416
9671
  const startedAt = Date.now();
9417
9672
  const secret = loadOrCreateSecret(secretFilePath);
9418
9673
  const jwtSecret = loadOrCreateSecret(jwtSecretFilePath);
@@ -9435,6 +9690,7 @@ function createApiServerPlugin() {
9435
9690
  const { commandRoutes: commandRoutes2 } = await Promise.resolve().then(() => (init_commands2(), commands_exports));
9436
9691
  const { authRoutes: authRoutes2 } = await Promise.resolve().then(() => (init_auth3(), auth_exports3));
9437
9692
  const { workspaceRoute: workspaceRoute2 } = await Promise.resolve().then(() => (init_workspace(), workspace_exports));
9693
+ const { pluginRoutes: pluginRoutes2 } = await Promise.resolve().then(() => (init_plugins(), plugins_exports));
9438
9694
  server = await createApiServer2({
9439
9695
  port: apiConfig.port,
9440
9696
  host: apiConfig.host,
@@ -9453,7 +9709,8 @@ function createApiServerPlugin() {
9453
9709
  getVersion,
9454
9710
  commandRegistry,
9455
9711
  authPreHandler: routeAuthPreHandler,
9456
- contextManager
9712
+ contextManager,
9713
+ lifecycleManager: core.lifecycleManager
9457
9714
  };
9458
9715
  server.registerPlugin("/api/v1/sessions", async (app) => sessionRoutes2(app, deps));
9459
9716
  server.registerPlugin("/api/v1/agents", async (app) => agentRoutes2(app, deps));
@@ -9464,16 +9721,17 @@ function createApiServerPlugin() {
9464
9721
  server.registerPlugin("/api/v1/notify", async (app) => notifyRoutes2(app, deps));
9465
9722
  server.registerPlugin("/api/v1/commands", async (app) => commandRoutes2(app, deps));
9466
9723
  server.registerPlugin("/api/v1/auth", async (app) => authRoutes2(app, { tokenStore, getJwtSecret: () => jwtSecret }));
9724
+ server.registerPlugin("/api/v1/plugins", async (app) => pluginRoutes2(app, deps));
9467
9725
  const { InstanceRegistry: InstanceRegistry2 } = await Promise.resolve().then(() => (init_instance_registry(), instance_registry_exports));
9468
9726
  const { getGlobalRoot: getGlobalRoot2 } = await Promise.resolve().then(() => (init_instance_context(), instance_context_exports));
9469
9727
  const globalRoot = getGlobalRoot2();
9470
- const instanceReg = new InstanceRegistry2(path27.join(globalRoot, "instances.json"));
9728
+ const instanceReg = new InstanceRegistry2(path28.join(globalRoot, "instances.json"));
9471
9729
  instanceReg.load();
9472
9730
  const instanceEntry = instanceReg.getByRoot(instanceRoot);
9473
9731
  const workspaceId = instanceEntry?.id ?? "main";
9474
9732
  const appConfig = core.configManager.get();
9475
9733
  const workspaceName = appConfig.instanceName ?? "Main";
9476
- const workspaceDir = path27.dirname(instanceRoot);
9734
+ const workspaceDir = path28.dirname(instanceRoot);
9477
9735
  server.registerPlugin("/api/v1", async (app) => {
9478
9736
  await app.register(workspaceRoute2, {
9479
9737
  id: workspaceId,
@@ -10408,8 +10666,8 @@ function formatToolSummary(name, rawInput, displaySummary) {
10408
10666
  }
10409
10667
  if (lowerName === "grep") {
10410
10668
  const pattern = args2.pattern ?? "";
10411
- const path67 = args2.path ?? "";
10412
- return pattern ? `\u{1F50D} Grep "${pattern}"${path67 ? ` in ${path67}` : ""}` : `\u{1F527} ${name}`;
10669
+ const path69 = args2.path ?? "";
10670
+ return pattern ? `\u{1F50D} Grep "${pattern}"${path69 ? ` in ${path69}` : ""}` : `\u{1F527} ${name}`;
10413
10671
  }
10414
10672
  if (lowerName === "glob") {
10415
10673
  const pattern = args2.pattern ?? "";
@@ -10445,8 +10703,8 @@ function formatToolTitle(name, rawInput, displayTitle) {
10445
10703
  }
10446
10704
  if (lowerName === "grep") {
10447
10705
  const pattern = args2.pattern ?? "";
10448
- const path67 = args2.path ?? "";
10449
- return pattern ? `"${pattern}"${path67 ? ` in ${path67}` : ""}` : name;
10706
+ const path69 = args2.path ?? "";
10707
+ return pattern ? `"${pattern}"${path69 ? ` in ${path69}` : ""}` : name;
10450
10708
  }
10451
10709
  if (lowerName === "glob") {
10452
10710
  return String(args2.pattern ?? name);
@@ -12254,12 +12512,11 @@ var init_menu = __esm({
12254
12512
 
12255
12513
  // src/plugins/telegram/commands/settings.ts
12256
12514
  import { InlineKeyboard as InlineKeyboard6 } from "grammy";
12257
- function buildSettingsKeyboard(core) {
12258
- const config = core.configManager.get();
12515
+ async function buildSettingsKeyboard(core) {
12259
12516
  const fields = getSafeFields();
12260
12517
  const kb = new InlineKeyboard6();
12261
12518
  for (const field of fields) {
12262
- const value = getConfigValue(config, field.path);
12519
+ const value = await getFieldValueAsync(field, core.configManager, core.settingsManager);
12263
12520
  const label = formatFieldLabel(field, value);
12264
12521
  if (field.type === "toggle") {
12265
12522
  kb.text(`${label}`, `s:toggle:${field.path}`).row();
@@ -12290,7 +12547,7 @@ function formatFieldLabel(field, value) {
12290
12547
  return `${icon} ${field.displayName}: ${displayValue}`;
12291
12548
  }
12292
12549
  async function handleSettings(ctx, core) {
12293
- const kb = buildSettingsKeyboard(core);
12550
+ const kb = await buildSettingsKeyboard(core);
12294
12551
  await ctx.reply(`<b>\u2699\uFE0F Settings</b>
12295
12552
  Tap to change:`, {
12296
12553
  parse_mode: "HTML",
@@ -12300,19 +12557,20 @@ Tap to change:`, {
12300
12557
  function setupSettingsCallbacks(bot, core, getAssistantSession) {
12301
12558
  bot.callbackQuery(/^s:toggle:/, async (ctx) => {
12302
12559
  const fieldPath = ctx.callbackQuery.data.replace("s:toggle:", "");
12303
- const config = core.configManager.get();
12304
- const currentValue = getConfigValue(config, fieldPath);
12560
+ const fieldDef = getSafeFields().find((f) => f.path === fieldPath);
12561
+ if (!fieldDef) return;
12562
+ const settingsManager = core.settingsManager;
12563
+ const currentValue = await getFieldValueAsync(fieldDef, core.configManager, settingsManager);
12305
12564
  const newValue = !currentValue;
12306
12565
  try {
12307
- const updates = buildNestedUpdate(fieldPath, newValue);
12308
- await core.configManager.save(updates, fieldPath);
12566
+ await setFieldValueAsync(fieldDef, newValue, core.configManager, settingsManager);
12309
12567
  const toast = isHotReloadable(fieldPath) ? `\u2705 ${fieldPath} = ${newValue}` : `\u2705 ${fieldPath} = ${newValue} (restart needed)`;
12310
12568
  try {
12311
12569
  await ctx.answerCallbackQuery({ text: toast });
12312
12570
  } catch {
12313
12571
  }
12314
12572
  try {
12315
- await ctx.editMessageReplyMarkup({ reply_markup: buildSettingsKeyboard(core) });
12573
+ await ctx.editMessageReplyMarkup({ reply_markup: await buildSettingsKeyboard(core) });
12316
12574
  } catch {
12317
12575
  }
12318
12576
  } catch (err) {
@@ -12329,7 +12587,7 @@ function setupSettingsCallbacks(bot, core, getAssistantSession) {
12329
12587
  const fieldDef = getSafeFields().find((f) => f.path === fieldPath);
12330
12588
  if (!fieldDef) return;
12331
12589
  const options = resolveOptions(fieldDef, config) ?? [];
12332
- const currentValue = getConfigValue(config, fieldPath);
12590
+ const currentValue = await getFieldValueAsync(fieldDef, core.configManager, core.settingsManager);
12333
12591
  const kb = new InlineKeyboard6();
12334
12592
  for (const opt of options) {
12335
12593
  const marker = opt === String(currentValue) ? " \u2713" : "";
@@ -12353,11 +12611,21 @@ Select a value:`, {
12353
12611
  const parts = ctx.callbackQuery.data.replace("s:pick:", "").split(":");
12354
12612
  const fieldPath = parts.slice(0, -1).join(":");
12355
12613
  const newValue = parts[parts.length - 1];
12614
+ const fieldDef = getSafeFields().find((f) => f.path === fieldPath);
12615
+ if (!fieldDef) return;
12356
12616
  try {
12357
12617
  if (fieldPath === "speech.stt.provider") {
12358
- const config = core.configManager.get();
12359
- const providerConfig = config.speech?.stt?.providers?.[newValue];
12360
- if (!providerConfig?.apiKey) {
12618
+ const sm = core.settingsManager;
12619
+ let hasApiKey = false;
12620
+ if (sm) {
12621
+ const speechSettings = await sm.loadSettings("@openacp/speech");
12622
+ hasApiKey = !!speechSettings.groqApiKey;
12623
+ } else {
12624
+ const config = core.configManager.get();
12625
+ const providerConfig = config.speech?.stt?.providers?.[newValue];
12626
+ hasApiKey = !!providerConfig?.apiKey;
12627
+ }
12628
+ if (!hasApiKey) {
12361
12629
  const assistant = getAssistantSession();
12362
12630
  if (assistant) {
12363
12631
  try {
@@ -12375,8 +12643,7 @@ Select a value:`, {
12375
12643
  return;
12376
12644
  }
12377
12645
  }
12378
- const updates = buildNestedUpdate(fieldPath, newValue);
12379
- await core.configManager.save(updates, fieldPath);
12646
+ await setFieldValueAsync(fieldDef, newValue, core.configManager, core.settingsManager);
12380
12647
  try {
12381
12648
  await ctx.answerCallbackQuery({ text: `\u2705 ${fieldPath} = ${newValue}` });
12382
12649
  } catch {
@@ -12385,7 +12652,7 @@ Select a value:`, {
12385
12652
  await ctx.editMessageText(`<b>\u2699\uFE0F Settings</b>
12386
12653
  Tap to change:`, {
12387
12654
  parse_mode: "HTML",
12388
- reply_markup: buildSettingsKeyboard(core)
12655
+ reply_markup: await buildSettingsKeyboard(core)
12389
12656
  });
12390
12657
  } catch {
12391
12658
  }
@@ -12399,10 +12666,9 @@ Tap to change:`, {
12399
12666
  });
12400
12667
  bot.callbackQuery(/^s:input:/, async (ctx) => {
12401
12668
  const fieldPath = ctx.callbackQuery.data.replace("s:input:", "");
12402
- const config = core.configManager.get();
12403
12669
  const fieldDef = getSafeFields().find((f) => f.path === fieldPath);
12404
12670
  if (!fieldDef) return;
12405
- const currentValue = getConfigValue(config, fieldPath);
12671
+ const currentValue = await getFieldValueAsync(fieldDef, core.configManager, core.settingsManager);
12406
12672
  const assistant = getAssistantSession();
12407
12673
  if (!assistant) {
12408
12674
  try {
@@ -12443,23 +12709,12 @@ Choose an action:`, {
12443
12709
  await ctx.editMessageText(`<b>\u2699\uFE0F Settings</b>
12444
12710
  Tap to change:`, {
12445
12711
  parse_mode: "HTML",
12446
- reply_markup: buildSettingsKeyboard(core)
12712
+ reply_markup: await buildSettingsKeyboard(core)
12447
12713
  });
12448
12714
  } catch {
12449
12715
  }
12450
12716
  });
12451
12717
  }
12452
- function buildNestedUpdate(dotPath, value) {
12453
- const parts = dotPath.split(".");
12454
- const result = {};
12455
- let target = result;
12456
- for (let i = 0; i < parts.length - 1; i++) {
12457
- target[parts[i]] = {};
12458
- target = target[parts[i]];
12459
- }
12460
- target[parts[parts.length - 1]] = value;
12461
- return result;
12462
- }
12463
12718
  var log18;
12464
12719
  var init_settings = __esm({
12465
12720
  "src/plugins/telegram/commands/settings.ts"() {
@@ -12534,7 +12789,7 @@ var init_config4 = __esm({
12534
12789
  // src/core/doctor/checks/agents.ts
12535
12790
  import { execFileSync as execFileSync3 } from "child_process";
12536
12791
  import * as fs25 from "fs";
12537
- import * as path28 from "path";
12792
+ import * as path29 from "path";
12538
12793
  function commandExists2(cmd) {
12539
12794
  try {
12540
12795
  execFileSync3("which", [cmd], { stdio: "pipe" });
@@ -12543,9 +12798,9 @@ function commandExists2(cmd) {
12543
12798
  }
12544
12799
  let dir = process.cwd();
12545
12800
  while (true) {
12546
- const binPath = path28.join(dir, "node_modules", ".bin", cmd);
12801
+ const binPath = path29.join(dir, "node_modules", ".bin", cmd);
12547
12802
  if (fs25.existsSync(binPath)) return true;
12548
- const parent = path28.dirname(dir);
12803
+ const parent = path29.dirname(dir);
12549
12804
  if (parent === dir) break;
12550
12805
  dir = parent;
12551
12806
  }
@@ -12592,7 +12847,115 @@ var init_agents3 = __esm({
12592
12847
  }
12593
12848
  });
12594
12849
 
12850
+ // src/core/plugin/settings-manager.ts
12851
+ var settings_manager_exports = {};
12852
+ __export(settings_manager_exports, {
12853
+ SettingsManager: () => SettingsManager
12854
+ });
12855
+ import fs26 from "fs";
12856
+ import path30 from "path";
12857
+ var SettingsManager, SettingsAPIImpl;
12858
+ var init_settings_manager = __esm({
12859
+ "src/core/plugin/settings-manager.ts"() {
12860
+ "use strict";
12861
+ SettingsManager = class {
12862
+ constructor(basePath) {
12863
+ this.basePath = basePath;
12864
+ }
12865
+ getBasePath() {
12866
+ return this.basePath;
12867
+ }
12868
+ createAPI(pluginName) {
12869
+ const settingsPath = this.getSettingsPath(pluginName);
12870
+ return new SettingsAPIImpl(settingsPath);
12871
+ }
12872
+ async loadSettings(pluginName) {
12873
+ const settingsPath = this.getSettingsPath(pluginName);
12874
+ try {
12875
+ const content = fs26.readFileSync(settingsPath, "utf-8");
12876
+ return JSON.parse(content);
12877
+ } catch {
12878
+ return {};
12879
+ }
12880
+ }
12881
+ validateSettings(_pluginName, settings, schema) {
12882
+ if (!schema) return { valid: true };
12883
+ const result = schema.safeParse(settings);
12884
+ if (result.success) return { valid: true };
12885
+ return {
12886
+ valid: false,
12887
+ errors: result.error.errors.map(
12888
+ (e) => `${e.path.join(".")}: ${e.message}`
12889
+ )
12890
+ };
12891
+ }
12892
+ getSettingsPath(pluginName) {
12893
+ return path30.join(this.basePath, pluginName, "settings.json");
12894
+ }
12895
+ async getPluginSettings(pluginName) {
12896
+ return this.loadSettings(pluginName);
12897
+ }
12898
+ async updatePluginSettings(pluginName, updates) {
12899
+ const api = this.createAPI(pluginName);
12900
+ const current = await api.getAll();
12901
+ await api.setAll({ ...current, ...updates });
12902
+ }
12903
+ };
12904
+ SettingsAPIImpl = class {
12905
+ constructor(settingsPath) {
12906
+ this.settingsPath = settingsPath;
12907
+ }
12908
+ cache = null;
12909
+ readFile() {
12910
+ if (this.cache !== null) return this.cache;
12911
+ try {
12912
+ const content = fs26.readFileSync(this.settingsPath, "utf-8");
12913
+ this.cache = JSON.parse(content);
12914
+ return this.cache;
12915
+ } catch {
12916
+ this.cache = {};
12917
+ return this.cache;
12918
+ }
12919
+ }
12920
+ writeFile(data) {
12921
+ const dir = path30.dirname(this.settingsPath);
12922
+ fs26.mkdirSync(dir, { recursive: true });
12923
+ fs26.writeFileSync(this.settingsPath, JSON.stringify(data, null, 2));
12924
+ this.cache = data;
12925
+ }
12926
+ async get(key) {
12927
+ const data = this.readFile();
12928
+ return data[key];
12929
+ }
12930
+ async set(key, value) {
12931
+ const data = this.readFile();
12932
+ data[key] = value;
12933
+ this.writeFile(data);
12934
+ }
12935
+ async getAll() {
12936
+ return { ...this.readFile() };
12937
+ }
12938
+ async setAll(settings) {
12939
+ this.writeFile({ ...settings });
12940
+ }
12941
+ async delete(key) {
12942
+ const data = this.readFile();
12943
+ delete data[key];
12944
+ this.writeFile(data);
12945
+ }
12946
+ async clear() {
12947
+ this.writeFile({});
12948
+ }
12949
+ async has(key) {
12950
+ const data = this.readFile();
12951
+ return key in data;
12952
+ }
12953
+ };
12954
+ }
12955
+ });
12956
+
12595
12957
  // src/core/doctor/checks/telegram.ts
12958
+ import * as path31 from "path";
12596
12959
  var BOT_TOKEN_REGEX, telegramCheck;
12597
12960
  var init_telegram = __esm({
12598
12961
  "src/core/doctor/checks/telegram.ts"() {
@@ -12607,13 +12970,16 @@ var init_telegram = __esm({
12607
12970
  results.push({ status: "fail", message: "Cannot check Telegram \u2014 config not loaded" });
12608
12971
  return results;
12609
12972
  }
12610
- const tgConfig = ctx.config.channels.telegram;
12611
- if (!tgConfig || !tgConfig.enabled) {
12612
- results.push({ status: "pass", message: "Telegram not enabled (skipped)" });
12973
+ const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
12974
+ const sm = new SettingsManager2(path31.join(ctx.pluginsDir, "data"));
12975
+ const ps = await sm.loadSettings("@openacp/telegram");
12976
+ const legacyCh = ctx.config.channels.telegram;
12977
+ const botToken = ps.botToken ?? legacyCh?.botToken;
12978
+ const chatId = ps.chatId ?? legacyCh?.chatId;
12979
+ if (!botToken && !chatId) {
12980
+ results.push({ status: "pass", message: "Telegram not configured (skipped)" });
12613
12981
  return results;
12614
12982
  }
12615
- const botToken = tgConfig.botToken;
12616
- const chatId = tgConfig.chatId;
12617
12983
  if (!botToken || !BOT_TOKEN_REGEX.test(botToken)) {
12618
12984
  results.push({ status: "fail", message: "Bot token format invalid" });
12619
12985
  return results;
@@ -12689,7 +13055,7 @@ var init_telegram = __esm({
12689
13055
  });
12690
13056
 
12691
13057
  // src/core/doctor/checks/storage.ts
12692
- import * as fs26 from "fs";
13058
+ import * as fs27 from "fs";
12693
13059
  var storageCheck;
12694
13060
  var init_storage = __esm({
12695
13061
  "src/core/doctor/checks/storage.ts"() {
@@ -12699,28 +13065,28 @@ var init_storage = __esm({
12699
13065
  order: 4,
12700
13066
  async run(ctx) {
12701
13067
  const results = [];
12702
- if (!fs26.existsSync(ctx.dataDir)) {
13068
+ if (!fs27.existsSync(ctx.dataDir)) {
12703
13069
  results.push({
12704
13070
  status: "fail",
12705
13071
  message: "Data directory ~/.openacp does not exist",
12706
13072
  fixable: true,
12707
13073
  fixRisk: "safe",
12708
13074
  fix: async () => {
12709
- fs26.mkdirSync(ctx.dataDir, { recursive: true });
13075
+ fs27.mkdirSync(ctx.dataDir, { recursive: true });
12710
13076
  return { success: true, message: "created directory" };
12711
13077
  }
12712
13078
  });
12713
13079
  } else {
12714
13080
  try {
12715
- fs26.accessSync(ctx.dataDir, fs26.constants.W_OK);
13081
+ fs27.accessSync(ctx.dataDir, fs27.constants.W_OK);
12716
13082
  results.push({ status: "pass", message: "Data directory exists and writable" });
12717
13083
  } catch {
12718
13084
  results.push({ status: "fail", message: "Data directory not writable" });
12719
13085
  }
12720
13086
  }
12721
- if (fs26.existsSync(ctx.sessionsPath)) {
13087
+ if (fs27.existsSync(ctx.sessionsPath)) {
12722
13088
  try {
12723
- const content = fs26.readFileSync(ctx.sessionsPath, "utf-8");
13089
+ const content = fs27.readFileSync(ctx.sessionsPath, "utf-8");
12724
13090
  const data = JSON.parse(content);
12725
13091
  if (typeof data === "object" && data !== null && "sessions" in data) {
12726
13092
  results.push({ status: "pass", message: "Sessions file valid" });
@@ -12731,7 +13097,7 @@ var init_storage = __esm({
12731
13097
  fixable: true,
12732
13098
  fixRisk: "risky",
12733
13099
  fix: async () => {
12734
- fs26.writeFileSync(ctx.sessionsPath, JSON.stringify({ version: 1, sessions: {} }, null, 2));
13100
+ fs27.writeFileSync(ctx.sessionsPath, JSON.stringify({ version: 1, sessions: {} }, null, 2));
12735
13101
  return { success: true, message: "reset sessions file" };
12736
13102
  }
12737
13103
  });
@@ -12743,7 +13109,7 @@ var init_storage = __esm({
12743
13109
  fixable: true,
12744
13110
  fixRisk: "risky",
12745
13111
  fix: async () => {
12746
- fs26.writeFileSync(ctx.sessionsPath, JSON.stringify({ version: 1, sessions: {} }, null, 2));
13112
+ fs27.writeFileSync(ctx.sessionsPath, JSON.stringify({ version: 1, sessions: {} }, null, 2));
12747
13113
  return { success: true, message: "reset sessions file" };
12748
13114
  }
12749
13115
  });
@@ -12751,20 +13117,20 @@ var init_storage = __esm({
12751
13117
  } else {
12752
13118
  results.push({ status: "pass", message: "Sessions file not present yet (created on first session)" });
12753
13119
  }
12754
- if (!fs26.existsSync(ctx.logsDir)) {
13120
+ if (!fs27.existsSync(ctx.logsDir)) {
12755
13121
  results.push({
12756
13122
  status: "warn",
12757
13123
  message: "Log directory does not exist",
12758
13124
  fixable: true,
12759
13125
  fixRisk: "safe",
12760
13126
  fix: async () => {
12761
- fs26.mkdirSync(ctx.logsDir, { recursive: true });
13127
+ fs27.mkdirSync(ctx.logsDir, { recursive: true });
12762
13128
  return { success: true, message: "created log directory" };
12763
13129
  }
12764
13130
  });
12765
13131
  } else {
12766
13132
  try {
12767
- fs26.accessSync(ctx.logsDir, fs26.constants.W_OK);
13133
+ fs27.accessSync(ctx.logsDir, fs27.constants.W_OK);
12768
13134
  results.push({ status: "pass", message: "Log directory exists and writable" });
12769
13135
  } catch {
12770
13136
  results.push({ status: "fail", message: "Log directory not writable" });
@@ -12777,7 +13143,7 @@ var init_storage = __esm({
12777
13143
  });
12778
13144
 
12779
13145
  // src/core/doctor/checks/workspace.ts
12780
- import * as fs27 from "fs";
13146
+ import * as fs28 from "fs";
12781
13147
  var workspaceCheck;
12782
13148
  var init_workspace2 = __esm({
12783
13149
  "src/core/doctor/checks/workspace.ts"() {
@@ -12793,20 +13159,20 @@ var init_workspace2 = __esm({
12793
13159
  return results;
12794
13160
  }
12795
13161
  const baseDir = expandHome3(ctx.config.workspace.baseDir);
12796
- if (!fs27.existsSync(baseDir)) {
13162
+ if (!fs28.existsSync(baseDir)) {
12797
13163
  results.push({
12798
13164
  status: "warn",
12799
13165
  message: `Workspace directory does not exist: ${baseDir}`,
12800
13166
  fixable: true,
12801
13167
  fixRisk: "safe",
12802
13168
  fix: async () => {
12803
- fs27.mkdirSync(baseDir, { recursive: true });
13169
+ fs28.mkdirSync(baseDir, { recursive: true });
12804
13170
  return { success: true, message: "created directory" };
12805
13171
  }
12806
13172
  });
12807
13173
  } else {
12808
13174
  try {
12809
- fs27.accessSync(baseDir, fs27.constants.W_OK);
13175
+ fs28.accessSync(baseDir, fs28.constants.W_OK);
12810
13176
  results.push({ status: "pass", message: `Workspace directory exists: ${baseDir}` });
12811
13177
  } catch {
12812
13178
  results.push({ status: "fail", message: `Workspace directory not writable: ${baseDir}` });
@@ -12819,10 +13185,10 @@ var init_workspace2 = __esm({
12819
13185
  });
12820
13186
 
12821
13187
  // src/core/doctor/checks/plugins.ts
12822
- import * as fs28 from "fs";
12823
- import * as path29 from "path";
13188
+ import * as fs29 from "fs";
13189
+ import * as path32 from "path";
12824
13190
  var pluginsCheck;
12825
- var init_plugins = __esm({
13191
+ var init_plugins2 = __esm({
12826
13192
  "src/core/doctor/checks/plugins.ts"() {
12827
13193
  "use strict";
12828
13194
  pluginsCheck = {
@@ -12830,16 +13196,16 @@ var init_plugins = __esm({
12830
13196
  order: 6,
12831
13197
  async run(ctx) {
12832
13198
  const results = [];
12833
- if (!fs28.existsSync(ctx.pluginsDir)) {
13199
+ if (!fs29.existsSync(ctx.pluginsDir)) {
12834
13200
  results.push({
12835
13201
  status: "warn",
12836
13202
  message: "Plugins directory does not exist",
12837
13203
  fixable: true,
12838
13204
  fixRisk: "safe",
12839
13205
  fix: async () => {
12840
- fs28.mkdirSync(ctx.pluginsDir, { recursive: true });
12841
- fs28.writeFileSync(
12842
- path29.join(ctx.pluginsDir, "package.json"),
13206
+ fs29.mkdirSync(ctx.pluginsDir, { recursive: true });
13207
+ fs29.writeFileSync(
13208
+ path32.join(ctx.pluginsDir, "package.json"),
12843
13209
  JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
12844
13210
  );
12845
13211
  return { success: true, message: "initialized plugins directory" };
@@ -12848,15 +13214,15 @@ var init_plugins = __esm({
12848
13214
  return results;
12849
13215
  }
12850
13216
  results.push({ status: "pass", message: "Plugins directory exists" });
12851
- const pkgPath = path29.join(ctx.pluginsDir, "package.json");
12852
- if (!fs28.existsSync(pkgPath)) {
13217
+ const pkgPath = path32.join(ctx.pluginsDir, "package.json");
13218
+ if (!fs29.existsSync(pkgPath)) {
12853
13219
  results.push({
12854
13220
  status: "warn",
12855
13221
  message: "Plugins package.json missing",
12856
13222
  fixable: true,
12857
13223
  fixRisk: "safe",
12858
13224
  fix: async () => {
12859
- fs28.writeFileSync(
13225
+ fs29.writeFileSync(
12860
13226
  pkgPath,
12861
13227
  JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
12862
13228
  );
@@ -12866,7 +13232,7 @@ var init_plugins = __esm({
12866
13232
  return results;
12867
13233
  }
12868
13234
  try {
12869
- const pkg = JSON.parse(fs28.readFileSync(pkgPath, "utf-8"));
13235
+ const pkg = JSON.parse(fs29.readFileSync(pkgPath, "utf-8"));
12870
13236
  const deps = pkg.dependencies || {};
12871
13237
  const count = Object.keys(deps).length;
12872
13238
  results.push({ status: "pass", message: `Plugins package.json valid (${count} plugins)` });
@@ -12877,7 +13243,7 @@ var init_plugins = __esm({
12877
13243
  fixable: true,
12878
13244
  fixRisk: "risky",
12879
13245
  fix: async () => {
12880
- fs28.writeFileSync(
13246
+ fs29.writeFileSync(
12881
13247
  pkgPath,
12882
13248
  JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
12883
13249
  );
@@ -12892,7 +13258,7 @@ var init_plugins = __esm({
12892
13258
  });
12893
13259
 
12894
13260
  // src/core/doctor/checks/daemon.ts
12895
- import * as fs29 from "fs";
13261
+ import * as fs30 from "fs";
12896
13262
  import * as net from "net";
12897
13263
  function isProcessAlive(pid) {
12898
13264
  try {
@@ -12922,8 +13288,8 @@ var init_daemon = __esm({
12922
13288
  order: 7,
12923
13289
  async run(ctx) {
12924
13290
  const results = [];
12925
- if (fs29.existsSync(ctx.pidPath)) {
12926
- const content = fs29.readFileSync(ctx.pidPath, "utf-8").trim();
13291
+ if (fs30.existsSync(ctx.pidPath)) {
13292
+ const content = fs30.readFileSync(ctx.pidPath, "utf-8").trim();
12927
13293
  const pid = parseInt(content, 10);
12928
13294
  if (isNaN(pid)) {
12929
13295
  results.push({
@@ -12932,7 +13298,7 @@ var init_daemon = __esm({
12932
13298
  fixable: true,
12933
13299
  fixRisk: "safe",
12934
13300
  fix: async () => {
12935
- fs29.unlinkSync(ctx.pidPath);
13301
+ fs30.unlinkSync(ctx.pidPath);
12936
13302
  return { success: true, message: "removed invalid PID file" };
12937
13303
  }
12938
13304
  });
@@ -12943,7 +13309,7 @@ var init_daemon = __esm({
12943
13309
  fixable: true,
12944
13310
  fixRisk: "safe",
12945
13311
  fix: async () => {
12946
- fs29.unlinkSync(ctx.pidPath);
13312
+ fs30.unlinkSync(ctx.pidPath);
12947
13313
  return { success: true, message: "removed stale PID file" };
12948
13314
  }
12949
13315
  });
@@ -12951,8 +13317,8 @@ var init_daemon = __esm({
12951
13317
  results.push({ status: "pass", message: `Daemon running (PID ${pid})` });
12952
13318
  }
12953
13319
  }
12954
- if (fs29.existsSync(ctx.portFilePath)) {
12955
- const content = fs29.readFileSync(ctx.portFilePath, "utf-8").trim();
13320
+ if (fs30.existsSync(ctx.portFilePath)) {
13321
+ const content = fs30.readFileSync(ctx.portFilePath, "utf-8").trim();
12956
13322
  const port = parseInt(content, 10);
12957
13323
  if (isNaN(port)) {
12958
13324
  results.push({
@@ -12961,7 +13327,7 @@ var init_daemon = __esm({
12961
13327
  fixable: true,
12962
13328
  fixRisk: "safe",
12963
13329
  fix: async () => {
12964
- fs29.unlinkSync(ctx.portFilePath);
13330
+ fs30.unlinkSync(ctx.portFilePath);
12965
13331
  return { success: true, message: "removed invalid port file" };
12966
13332
  }
12967
13333
  });
@@ -12973,8 +13339,8 @@ var init_daemon = __esm({
12973
13339
  const apiPort = ctx.config.api.port;
12974
13340
  const inUse = await checkPortInUse(apiPort);
12975
13341
  if (inUse) {
12976
- if (fs29.existsSync(ctx.pidPath)) {
12977
- const pid = parseInt(fs29.readFileSync(ctx.pidPath, "utf-8").trim(), 10);
13342
+ if (fs30.existsSync(ctx.pidPath)) {
13343
+ const pid = parseInt(fs30.readFileSync(ctx.pidPath, "utf-8").trim(), 10);
12978
13344
  if (!isNaN(pid) && isProcessAlive(pid)) {
12979
13345
  results.push({ status: "pass", message: `API port ${apiPort} in use by OpenACP daemon` });
12980
13346
  } else {
@@ -12994,9 +13360,9 @@ var init_daemon = __esm({
12994
13360
  });
12995
13361
 
12996
13362
  // src/core/doctor/checks/tunnel.ts
12997
- import * as fs30 from "fs";
12998
- import * as path30 from "path";
12999
- import * as os12 from "os";
13363
+ import * as fs31 from "fs";
13364
+ import * as path33 from "path";
13365
+ import * as os13 from "os";
13000
13366
  import { execFileSync as execFileSync4 } from "child_process";
13001
13367
  var tunnelCheck;
13002
13368
  var init_tunnel3 = __esm({
@@ -13018,10 +13384,10 @@ var init_tunnel3 = __esm({
13018
13384
  const provider = ctx.config.tunnel.provider;
13019
13385
  results.push({ status: "pass", message: `Tunnel provider: ${provider}` });
13020
13386
  if (provider === "cloudflare") {
13021
- const binName = os12.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
13022
- const binPath = path30.join(ctx.dataDir, "bin", binName);
13387
+ const binName = os13.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
13388
+ const binPath = path33.join(ctx.dataDir, "bin", binName);
13023
13389
  let found = false;
13024
- if (fs30.existsSync(binPath)) {
13390
+ if (fs31.existsSync(binPath)) {
13025
13391
  found = true;
13026
13392
  } else {
13027
13393
  try {
@@ -13067,8 +13433,8 @@ var doctor_exports = {};
13067
13433
  __export(doctor_exports, {
13068
13434
  DoctorEngine: () => DoctorEngine
13069
13435
  });
13070
- import * as fs31 from "fs";
13071
- import * as path31 from "path";
13436
+ import * as fs32 from "fs";
13437
+ import * as path34 from "path";
13072
13438
  var ALL_CHECKS, CHECK_TIMEOUT_MS, DoctorEngine;
13073
13439
  var init_doctor = __esm({
13074
13440
  "src/core/doctor/index.ts"() {
@@ -13080,7 +13446,7 @@ var init_doctor = __esm({
13080
13446
  init_telegram();
13081
13447
  init_storage();
13082
13448
  init_workspace2();
13083
- init_plugins();
13449
+ init_plugins2();
13084
13450
  init_daemon();
13085
13451
  init_tunnel3();
13086
13452
  ALL_CHECKS = [
@@ -13150,27 +13516,27 @@ var init_doctor = __esm({
13150
13516
  }
13151
13517
  async buildContext() {
13152
13518
  const dataDir = this.dataDir;
13153
- const configPath = process.env.OPENACP_CONFIG_PATH || path31.join(dataDir, "config.json");
13519
+ const configPath = process.env.OPENACP_CONFIG_PATH || path34.join(dataDir, "config.json");
13154
13520
  let config = null;
13155
13521
  let rawConfig = null;
13156
13522
  try {
13157
- const content = fs31.readFileSync(configPath, "utf-8");
13523
+ const content = fs32.readFileSync(configPath, "utf-8");
13158
13524
  rawConfig = JSON.parse(content);
13159
13525
  const cm = new ConfigManager(configPath);
13160
13526
  await cm.load();
13161
13527
  config = cm.get();
13162
13528
  } catch {
13163
13529
  }
13164
- const logsDir = config ? expandHome3(config.logging.logDir) : path31.join(dataDir, "logs");
13530
+ const logsDir = config ? expandHome3(config.logging.logDir) : path34.join(dataDir, "logs");
13165
13531
  return {
13166
13532
  config,
13167
13533
  rawConfig,
13168
13534
  configPath,
13169
13535
  dataDir,
13170
- sessionsPath: path31.join(dataDir, "sessions.json"),
13171
- pidPath: path31.join(dataDir, "openacp.pid"),
13172
- portFilePath: path31.join(dataDir, "api.port"),
13173
- pluginsDir: path31.join(dataDir, "plugins"),
13536
+ sessionsPath: path34.join(dataDir, "sessions.json"),
13537
+ pidPath: path34.join(dataDir, "openacp.pid"),
13538
+ portFilePath: path34.join(dataDir, "api.port"),
13539
+ pluginsDir: path34.join(dataDir, "plugins"),
13174
13540
  logsDir
13175
13541
  };
13176
13542
  }
@@ -16907,113 +17273,6 @@ var init_core_plugins = __esm({
16907
17273
  }
16908
17274
  });
16909
17275
 
16910
- // src/core/plugin/settings-manager.ts
16911
- var settings_manager_exports = {};
16912
- __export(settings_manager_exports, {
16913
- SettingsManager: () => SettingsManager
16914
- });
16915
- import fs32 from "fs";
16916
- import path32 from "path";
16917
- var SettingsManager, SettingsAPIImpl;
16918
- var init_settings_manager = __esm({
16919
- "src/core/plugin/settings-manager.ts"() {
16920
- "use strict";
16921
- SettingsManager = class {
16922
- constructor(basePath) {
16923
- this.basePath = basePath;
16924
- }
16925
- getBasePath() {
16926
- return this.basePath;
16927
- }
16928
- createAPI(pluginName) {
16929
- const settingsPath = this.getSettingsPath(pluginName);
16930
- return new SettingsAPIImpl(settingsPath);
16931
- }
16932
- async loadSettings(pluginName) {
16933
- const settingsPath = this.getSettingsPath(pluginName);
16934
- try {
16935
- const content = fs32.readFileSync(settingsPath, "utf-8");
16936
- return JSON.parse(content);
16937
- } catch {
16938
- return {};
16939
- }
16940
- }
16941
- validateSettings(_pluginName, settings, schema) {
16942
- if (!schema) return { valid: true };
16943
- const result = schema.safeParse(settings);
16944
- if (result.success) return { valid: true };
16945
- return {
16946
- valid: false,
16947
- errors: result.error.errors.map(
16948
- (e) => `${e.path.join(".")}: ${e.message}`
16949
- )
16950
- };
16951
- }
16952
- getSettingsPath(pluginName) {
16953
- return path32.join(this.basePath, pluginName, "settings.json");
16954
- }
16955
- async getPluginSettings(pluginName) {
16956
- return this.loadSettings(pluginName);
16957
- }
16958
- async updatePluginSettings(pluginName, updates) {
16959
- const api = this.createAPI(pluginName);
16960
- const current = await api.getAll();
16961
- await api.setAll({ ...current, ...updates });
16962
- }
16963
- };
16964
- SettingsAPIImpl = class {
16965
- constructor(settingsPath) {
16966
- this.settingsPath = settingsPath;
16967
- }
16968
- cache = null;
16969
- readFile() {
16970
- if (this.cache !== null) return this.cache;
16971
- try {
16972
- const content = fs32.readFileSync(this.settingsPath, "utf-8");
16973
- this.cache = JSON.parse(content);
16974
- return this.cache;
16975
- } catch {
16976
- this.cache = {};
16977
- return this.cache;
16978
- }
16979
- }
16980
- writeFile(data) {
16981
- const dir = path32.dirname(this.settingsPath);
16982
- fs32.mkdirSync(dir, { recursive: true });
16983
- fs32.writeFileSync(this.settingsPath, JSON.stringify(data, null, 2));
16984
- this.cache = data;
16985
- }
16986
- async get(key) {
16987
- const data = this.readFile();
16988
- return data[key];
16989
- }
16990
- async set(key, value) {
16991
- const data = this.readFile();
16992
- data[key] = value;
16993
- this.writeFile(data);
16994
- }
16995
- async getAll() {
16996
- return { ...this.readFile() };
16997
- }
16998
- async setAll(settings) {
16999
- this.writeFile({ ...settings });
17000
- }
17001
- async delete(key) {
17002
- const data = this.readFile();
17003
- delete data[key];
17004
- this.writeFile(data);
17005
- }
17006
- async clear() {
17007
- this.writeFile({});
17008
- }
17009
- async has(key) {
17010
- const data = this.readFile();
17011
- return key in data;
17012
- }
17013
- };
17014
- }
17015
- });
17016
-
17017
17276
  // src/core/plugin/terminal-io.ts
17018
17277
  import * as clack from "@clack/prompts";
17019
17278
  function isCancel(value) {
@@ -17077,10 +17336,10 @@ var install_context_exports = {};
17077
17336
  __export(install_context_exports, {
17078
17337
  createInstallContext: () => createInstallContext
17079
17338
  });
17080
- import path33 from "path";
17339
+ import path35 from "path";
17081
17340
  function createInstallContext(opts) {
17082
17341
  const { pluginName, settingsManager, basePath, legacyConfig, instanceRoot } = opts;
17083
- const dataDir = path33.join(basePath, pluginName, "data");
17342
+ const dataDir = path35.join(basePath, pluginName, "data");
17084
17343
  return {
17085
17344
  pluginName,
17086
17345
  terminal: createTerminalIO(),
@@ -17108,13 +17367,13 @@ __export(api_client_exports, {
17108
17367
  removeStalePortFile: () => removeStalePortFile
17109
17368
  });
17110
17369
  import * as fs33 from "fs";
17111
- import * as path34 from "path";
17112
- import * as os13 from "os";
17370
+ import * as path36 from "path";
17371
+ import * as os14 from "os";
17113
17372
  function defaultPortFile(root) {
17114
- return path34.join(root ?? DEFAULT_ROOT, "api.port");
17373
+ return path36.join(root ?? DEFAULT_ROOT, "api.port");
17115
17374
  }
17116
17375
  function defaultSecretFile(root) {
17117
- return path34.join(root ?? DEFAULT_ROOT, "api-secret");
17376
+ return path36.join(root ?? DEFAULT_ROOT, "api-secret");
17118
17377
  }
17119
17378
  function readApiPort(portFilePath, instanceRoot) {
17120
17379
  const filePath = portFilePath ?? defaultPortFile(instanceRoot);
@@ -17154,7 +17413,7 @@ var DEFAULT_ROOT;
17154
17413
  var init_api_client = __esm({
17155
17414
  "src/cli/api-client.ts"() {
17156
17415
  "use strict";
17157
- DEFAULT_ROOT = path34.join(os13.homedir(), ".openacp");
17416
+ DEFAULT_ROOT = path36.join(os14.homedir(), ".openacp");
17158
17417
  }
17159
17418
  });
17160
17419
 
@@ -17202,16 +17461,16 @@ var init_suggest = __esm({
17202
17461
 
17203
17462
  // src/cli/instance-hint.ts
17204
17463
  import fs34 from "fs";
17205
- import path35 from "path";
17206
- import os14 from "os";
17464
+ import path37 from "path";
17465
+ import os15 from "os";
17207
17466
  function printInstanceHint(root) {
17208
17467
  const globalRoot = getGlobalRoot();
17209
17468
  const isGlobal = root === globalRoot;
17210
- const displayPath = root.replace(os14.homedir(), "~");
17469
+ const displayPath = root.replace(os15.homedir(), "~");
17211
17470
  const label = isGlobal ? "global" : "local";
17212
17471
  console.log(` Workspace: ${label} \u2014 ${displayPath}`);
17213
17472
  if (isGlobal) {
17214
- const localRoot = path35.join(process.cwd(), ".openacp");
17473
+ const localRoot = path37.join(process.cwd(), ".openacp");
17215
17474
  if (fs34.existsSync(localRoot)) {
17216
17475
  console.log(` \x1B[2mhint: local workspace exists in current directory \u2014 use --local to use it\x1B[0m`);
17217
17476
  }
@@ -17243,22 +17502,22 @@ __export(daemon_exports, {
17243
17502
  });
17244
17503
  import { spawn as spawn6 } from "child_process";
17245
17504
  import * as fs35 from "fs";
17246
- import * as path36 from "path";
17247
- import * as os15 from "os";
17505
+ import * as path38 from "path";
17506
+ import * as os16 from "os";
17248
17507
  function getPidPath(root) {
17249
17508
  const base = root ?? DEFAULT_ROOT2;
17250
- return path36.join(base, "openacp.pid");
17509
+ return path38.join(base, "openacp.pid");
17251
17510
  }
17252
17511
  function getLogDir(root) {
17253
17512
  const base = root ?? DEFAULT_ROOT2;
17254
- return path36.join(base, "logs");
17513
+ return path38.join(base, "logs");
17255
17514
  }
17256
17515
  function getRunningMarker(root) {
17257
17516
  const base = root ?? DEFAULT_ROOT2;
17258
- return path36.join(base, "running");
17517
+ return path38.join(base, "running");
17259
17518
  }
17260
17519
  function writePidFile(pidPath, pid) {
17261
- const dir = path36.dirname(pidPath);
17520
+ const dir = path38.dirname(pidPath);
17262
17521
  fs35.mkdirSync(dir, { recursive: true });
17263
17522
  fs35.writeFileSync(pidPath, String(pid));
17264
17523
  }
@@ -17307,8 +17566,8 @@ function startDaemon(pidPath = getPidPath(), logDir2, instanceRoot) {
17307
17566
  }
17308
17567
  const resolvedLogDir = logDir2 ? expandHome3(logDir2) : getLogDir(instanceRoot);
17309
17568
  fs35.mkdirSync(resolvedLogDir, { recursive: true });
17310
- const logFile = path36.join(resolvedLogDir, "openacp.log");
17311
- const cliPath = path36.resolve(process.argv[1]);
17569
+ const logFile = path38.join(resolvedLogDir, "openacp.log");
17570
+ const cliPath = path38.resolve(process.argv[1]);
17312
17571
  const nodePath = process.execPath;
17313
17572
  const out = fs35.openSync(logFile, "a");
17314
17573
  const err = fs35.openSync(logFile, "a");
@@ -17392,7 +17651,7 @@ async function stopDaemon(pidPath = getPidPath(), instanceRoot) {
17392
17651
  }
17393
17652
  function markRunning(root) {
17394
17653
  const marker = getRunningMarker(root);
17395
- fs35.mkdirSync(path36.dirname(marker), { recursive: true });
17654
+ fs35.mkdirSync(path38.dirname(marker), { recursive: true });
17396
17655
  fs35.writeFileSync(marker, "");
17397
17656
  }
17398
17657
  function clearRunning(root) {
@@ -17409,7 +17668,7 @@ var init_daemon2 = __esm({
17409
17668
  "src/cli/daemon.ts"() {
17410
17669
  "use strict";
17411
17670
  init_config2();
17412
- DEFAULT_ROOT2 = path36.join(os15.homedir(), ".openacp");
17671
+ DEFAULT_ROOT2 = path38.join(os16.homedir(), ".openacp");
17413
17672
  }
17414
17673
  });
17415
17674
 
@@ -17418,12 +17677,12 @@ var stop_exports = {};
17418
17677
  __export(stop_exports, {
17419
17678
  cmdStop: () => cmdStop
17420
17679
  });
17421
- import path38 from "path";
17422
- import os17 from "os";
17680
+ import path40 from "path";
17681
+ import os18 from "os";
17423
17682
  async function cmdStop(args2 = [], instanceRoot) {
17424
17683
  const json = isJsonMode(args2);
17425
17684
  if (json) await muteForJson();
17426
- const root = instanceRoot ?? path38.join(os17.homedir(), ".openacp");
17685
+ const root = instanceRoot ?? path40.join(os18.homedir(), ".openacp");
17427
17686
  if (!json && wantsHelp(args2)) {
17428
17687
  console.log(`
17429
17688
  \x1B[1mopenacp stop\x1B[0m \u2014 Stop the background daemon
@@ -17765,7 +18024,7 @@ var init_mcp_manager = __esm({
17765
18024
 
17766
18025
  // src/core/utils/debug-tracer.ts
17767
18026
  import fs37 from "fs";
17768
- import path39 from "path";
18027
+ import path41 from "path";
17769
18028
  function createDebugTracer(sessionId, workingDirectory) {
17770
18029
  if (!DEBUG_ENABLED) return null;
17771
18030
  return new DebugTracer(sessionId, workingDirectory);
@@ -17779,7 +18038,7 @@ var init_debug_tracer = __esm({
17779
18038
  constructor(sessionId, workingDirectory) {
17780
18039
  this.sessionId = sessionId;
17781
18040
  this.workingDirectory = workingDirectory;
17782
- this.logDir = path39.join(workingDirectory, ".log");
18041
+ this.logDir = path41.join(workingDirectory, ".log");
17783
18042
  }
17784
18043
  dirCreated = false;
17785
18044
  logDir;
@@ -17789,7 +18048,7 @@ var init_debug_tracer = __esm({
17789
18048
  fs37.mkdirSync(this.logDir, { recursive: true });
17790
18049
  this.dirCreated = true;
17791
18050
  }
17792
- const filePath = path39.join(this.logDir, `${this.sessionId}_${layer}.jsonl`);
18051
+ const filePath = path41.join(this.logDir, `${this.sessionId}_${layer}.jsonl`);
17793
18052
  const seen = /* @__PURE__ */ new WeakSet();
17794
18053
  const line = JSON.stringify({ ts: Date.now(), ...data }, (_key, value) => {
17795
18054
  if (typeof value === "object" && value !== null) {
@@ -17813,16 +18072,16 @@ var init_debug_tracer = __esm({
17813
18072
  import { spawn as spawn8, execFileSync as execFileSync5 } from "child_process";
17814
18073
  import { Transform } from "stream";
17815
18074
  import fs38 from "fs";
17816
- import path40 from "path";
18075
+ import path42 from "path";
17817
18076
  import { ClientSideConnection, ndJsonStream } from "@agentclientprotocol/sdk";
17818
18077
  import { PROTOCOL_VERSION } from "@agentclientprotocol/sdk";
17819
18078
  function findPackageRoot(startDir) {
17820
18079
  let dir = startDir;
17821
- while (dir !== path40.dirname(dir)) {
17822
- if (fs38.existsSync(path40.join(dir, "package.json"))) {
18080
+ while (dir !== path42.dirname(dir)) {
18081
+ if (fs38.existsSync(path42.join(dir, "package.json"))) {
17823
18082
  return dir;
17824
18083
  }
17825
- dir = path40.dirname(dir);
18084
+ dir = path42.dirname(dir);
17826
18085
  }
17827
18086
  return startDir;
17828
18087
  }
@@ -17834,8 +18093,8 @@ function resolveAgentCommand(cmd) {
17834
18093
  }
17835
18094
  for (const root of searchRoots) {
17836
18095
  const packageDirs = [
17837
- path40.resolve(root, "node_modules", "@zed-industries", cmd, "dist", "index.js"),
17838
- path40.resolve(root, "node_modules", cmd, "dist", "index.js")
18096
+ path42.resolve(root, "node_modules", "@zed-industries", cmd, "dist", "index.js"),
18097
+ path42.resolve(root, "node_modules", cmd, "dist", "index.js")
17839
18098
  ];
17840
18099
  for (const jsPath of packageDirs) {
17841
18100
  if (fs38.existsSync(jsPath)) {
@@ -17844,7 +18103,7 @@ function resolveAgentCommand(cmd) {
17844
18103
  }
17845
18104
  }
17846
18105
  for (const root of searchRoots) {
17847
- const localBin = path40.resolve(root, "node_modules", ".bin", cmd);
18106
+ const localBin = path42.resolve(root, "node_modules", ".bin", cmd);
17848
18107
  if (fs38.existsSync(localBin)) {
17849
18108
  const content = fs38.readFileSync(localBin, "utf-8");
17850
18109
  if (content.startsWith("#!/usr/bin/env node")) {
@@ -17852,7 +18111,7 @@ function resolveAgentCommand(cmd) {
17852
18111
  }
17853
18112
  const match = content.match(/"([^"]+\.js)"/);
17854
18113
  if (match) {
17855
- const target = path40.resolve(path40.dirname(localBin), match[1]);
18114
+ const target = path42.resolve(path42.dirname(localBin), match[1]);
17856
18115
  if (fs38.existsSync(target)) {
17857
18116
  return { command: process.execPath, args: [target] };
17858
18117
  }
@@ -18253,7 +18512,7 @@ ${stderr}`
18253
18512
  writePath = result.path;
18254
18513
  writeContent = result.content;
18255
18514
  }
18256
- await fs38.promises.mkdir(path40.dirname(writePath), { recursive: true });
18515
+ await fs38.promises.mkdir(path42.dirname(writePath), { recursive: true });
18257
18516
  await fs38.promises.writeFile(writePath, writeContent, "utf-8");
18258
18517
  return {};
18259
18518
  },
@@ -19865,7 +20124,7 @@ var init_message_transformer = __esm({
19865
20124
 
19866
20125
  // src/core/sessions/session-store.ts
19867
20126
  import fs40 from "fs";
19868
- import path41 from "path";
20127
+ import path43 from "path";
19869
20128
  var log30, DEBOUNCE_MS2, JsonFileSessionStore;
19870
20129
  var init_session_store = __esm({
19871
20130
  "src/core/sessions/session-store.ts"() {
@@ -19941,7 +20200,7 @@ var init_session_store = __esm({
19941
20200
  version: 1,
19942
20201
  sessions: Object.fromEntries(this.records)
19943
20202
  };
19944
- const dir = path41.dirname(this.filePath);
20203
+ const dir = path43.dirname(this.filePath);
19945
20204
  if (!fs40.existsSync(dir)) fs40.mkdirSync(dir, { recursive: true });
19946
20205
  fs40.writeFileSync(this.filePath, JSON.stringify(data, null, 2));
19947
20206
  }
@@ -20516,8 +20775,8 @@ __export(agent_store_exports, {
20516
20775
  AgentStore: () => AgentStore
20517
20776
  });
20518
20777
  import * as fs41 from "fs";
20519
- import * as path42 from "path";
20520
- import * as os18 from "os";
20778
+ import * as path44 from "path";
20779
+ import * as os19 from "os";
20521
20780
  import { z as z7 } from "zod";
20522
20781
  var log33, InstalledAgentSchema, AgentStoreSchema, AgentStore;
20523
20782
  var init_agent_store = __esm({
@@ -20545,7 +20804,7 @@ var init_agent_store = __esm({
20545
20804
  data = { version: 1, installed: {} };
20546
20805
  filePath;
20547
20806
  constructor(filePath) {
20548
- this.filePath = filePath ?? path42.join(os18.homedir(), ".openacp", "agents.json");
20807
+ this.filePath = filePath ?? path44.join(os19.homedir(), ".openacp", "agents.json");
20549
20808
  }
20550
20809
  load() {
20551
20810
  if (!fs41.existsSync(this.filePath)) {
@@ -20587,7 +20846,7 @@ var init_agent_store = __esm({
20587
20846
  return key in this.data.installed;
20588
20847
  }
20589
20848
  save() {
20590
- fs41.mkdirSync(path42.dirname(this.filePath), { recursive: true });
20849
+ fs41.mkdirSync(path44.dirname(this.filePath), { recursive: true });
20591
20850
  const tmpPath = this.filePath + ".tmp";
20592
20851
  fs41.writeFileSync(tmpPath, JSON.stringify(this.data, null, 2));
20593
20852
  fs41.renameSync(tmpPath, this.filePath);
@@ -20598,8 +20857,8 @@ var init_agent_store = __esm({
20598
20857
 
20599
20858
  // src/core/agents/agent-installer.ts
20600
20859
  import * as fs42 from "fs";
20601
- import * as path43 from "path";
20602
- import * as os19 from "os";
20860
+ import * as path45 from "path";
20861
+ import * as os20 from "os";
20603
20862
  function getPlatformKey() {
20604
20863
  const platform2 = PLATFORM_MAP[process.platform] ?? process.platform;
20605
20864
  const arch = ARCH_MAP[process.arch] ?? process.arch;
@@ -20650,7 +20909,7 @@ function buildInstalledAgent(registryId, name, version, dist, binaryPath) {
20650
20909
  binaryPath: null
20651
20910
  };
20652
20911
  }
20653
- const absCmd = path43.resolve(binaryPath, dist.cmd);
20912
+ const absCmd = path45.resolve(binaryPath, dist.cmd);
20654
20913
  return {
20655
20914
  registryId,
20656
20915
  name,
@@ -20723,7 +20982,7 @@ Install it with: pip install uv`;
20723
20982
  return { ok: true, agentKey, setupSteps: setup?.setupSteps };
20724
20983
  }
20725
20984
  async function downloadAndExtract(agentId, archiveUrl, progress, agentsDir) {
20726
- const destDir = path43.join(agentsDir ?? DEFAULT_AGENTS_DIR, agentId);
20985
+ const destDir = path45.join(agentsDir ?? DEFAULT_AGENTS_DIR, agentId);
20727
20986
  fs42.mkdirSync(destDir, { recursive: true });
20728
20987
  await progress?.onStep("Downloading...");
20729
20988
  log34.info({ agentId, url: archiveUrl }, "Downloading agent binary");
@@ -20767,15 +21026,15 @@ function validateExtractedPaths(destDir) {
20767
21026
  for (const entry of entries) {
20768
21027
  const dirent = entry;
20769
21028
  const parentPath = dirent.parentPath ?? dirent.path ?? destDir;
20770
- const fullPath = path43.join(parentPath, entry.name);
21029
+ const fullPath = path45.join(parentPath, entry.name);
20771
21030
  let realPath;
20772
21031
  try {
20773
21032
  realPath = fs42.realpathSync(fullPath);
20774
21033
  } catch {
20775
21034
  const linkTarget = fs42.readlinkSync(fullPath);
20776
- realPath = path43.resolve(path43.dirname(fullPath), linkTarget);
21035
+ realPath = path45.resolve(path45.dirname(fullPath), linkTarget);
20777
21036
  }
20778
- if (!realPath.startsWith(realDest + path43.sep) && realPath !== realDest) {
21037
+ if (!realPath.startsWith(realDest + path45.sep) && realPath !== realDest) {
20779
21038
  fs42.rmSync(destDir, { recursive: true, force: true });
20780
21039
  throw new Error(`Archive contains unsafe path: ${entry.name}`);
20781
21040
  }
@@ -20783,7 +21042,7 @@ function validateExtractedPaths(destDir) {
20783
21042
  }
20784
21043
  async function extractTarGz(buffer, destDir) {
20785
21044
  const { execFileSync: execFileSync8 } = await import("child_process");
20786
- const tmpFile = path43.join(destDir, "_archive.tar.gz");
21045
+ const tmpFile = path45.join(destDir, "_archive.tar.gz");
20787
21046
  fs42.writeFileSync(tmpFile, buffer);
20788
21047
  try {
20789
21048
  execFileSync8("tar", ["xzf", tmpFile, "-C", destDir], { stdio: "pipe" });
@@ -20794,7 +21053,7 @@ async function extractTarGz(buffer, destDir) {
20794
21053
  }
20795
21054
  async function extractZip(buffer, destDir) {
20796
21055
  const { execFileSync: execFileSync8 } = await import("child_process");
20797
- const tmpFile = path43.join(destDir, "_archive.zip");
21056
+ const tmpFile = path45.join(destDir, "_archive.zip");
20798
21057
  fs42.writeFileSync(tmpFile, buffer);
20799
21058
  try {
20800
21059
  execFileSync8("unzip", ["-o", tmpFile, "-d", destDir], { stdio: "pipe" });
@@ -20819,7 +21078,7 @@ var init_agent_installer = __esm({
20819
21078
  init_log();
20820
21079
  init_agent_dependencies();
20821
21080
  log34 = createChildLogger({ module: "agent-installer" });
20822
- DEFAULT_AGENTS_DIR = path43.join(os19.homedir(), ".openacp", "agents");
21081
+ DEFAULT_AGENTS_DIR = path45.join(os20.homedir(), ".openacp", "agents");
20823
21082
  ARCH_MAP = {
20824
21083
  arm64: "aarch64",
20825
21084
  x64: "x86_64"
@@ -20838,8 +21097,8 @@ __export(agent_catalog_exports, {
20838
21097
  AgentCatalog: () => AgentCatalog
20839
21098
  });
20840
21099
  import * as fs43 from "fs";
20841
- import * as path44 from "path";
20842
- import * as os20 from "os";
21100
+ import * as path46 from "path";
21101
+ import * as os21 from "os";
20843
21102
  var log35, REGISTRY_URL2, DEFAULT_TTL_HOURS, AgentCatalog;
20844
21103
  var init_agent_catalog = __esm({
20845
21104
  "src/core/agents/agent-catalog.ts"() {
@@ -20858,7 +21117,7 @@ var init_agent_catalog = __esm({
20858
21117
  agentsDir;
20859
21118
  constructor(store, cachePath, agentsDir) {
20860
21119
  this.store = store ?? new AgentStore();
20861
- this.cachePath = cachePath ?? path44.join(os20.homedir(), ".openacp", "registry-cache.json");
21120
+ this.cachePath = cachePath ?? path46.join(os21.homedir(), ".openacp", "registry-cache.json");
20862
21121
  this.agentsDir = agentsDir;
20863
21122
  }
20864
21123
  load() {
@@ -20879,7 +21138,7 @@ var init_agent_catalog = __esm({
20879
21138
  ttlHours: DEFAULT_TTL_HOURS,
20880
21139
  data
20881
21140
  };
20882
- fs43.mkdirSync(path44.dirname(this.cachePath), { recursive: true });
21141
+ fs43.mkdirSync(path46.dirname(this.cachePath), { recursive: true });
20883
21142
  fs43.writeFileSync(this.cachePath, JSON.stringify(cache, null, 2));
20884
21143
  log35.info({ count: this.registryAgents.length }, "Registry updated");
20885
21144
  } catch (err) {
@@ -21065,9 +21324,9 @@ var init_agent_catalog = __esm({
21065
21324
  }
21066
21325
  try {
21067
21326
  const candidates = [
21068
- path44.join(import.meta.dirname, "data", "registry-snapshot.json"),
21069
- path44.join(import.meta.dirname, "..", "data", "registry-snapshot.json"),
21070
- path44.join(import.meta.dirname, "..", "..", "data", "registry-snapshot.json")
21327
+ path46.join(import.meta.dirname, "data", "registry-snapshot.json"),
21328
+ path46.join(import.meta.dirname, "..", "data", "registry-snapshot.json"),
21329
+ path46.join(import.meta.dirname, "..", "..", "data", "registry-snapshot.json")
21071
21330
  ];
21072
21331
  for (const candidate of candidates) {
21073
21332
  if (fs43.existsSync(candidate)) {
@@ -21365,7 +21624,7 @@ var init_error_tracker = __esm({
21365
21624
 
21366
21625
  // src/core/plugin/plugin-storage.ts
21367
21626
  import fs44 from "fs";
21368
- import path45 from "path";
21627
+ import path47 from "path";
21369
21628
  var PluginStorageImpl;
21370
21629
  var init_plugin_storage = __esm({
21371
21630
  "src/core/plugin/plugin-storage.ts"() {
@@ -21375,8 +21634,8 @@ var init_plugin_storage = __esm({
21375
21634
  dataDir;
21376
21635
  writeChain = Promise.resolve();
21377
21636
  constructor(baseDir) {
21378
- this.dataDir = path45.join(baseDir, "data");
21379
- this.kvPath = path45.join(baseDir, "kv.json");
21637
+ this.dataDir = path47.join(baseDir, "data");
21638
+ this.kvPath = path47.join(baseDir, "kv.json");
21380
21639
  fs44.mkdirSync(baseDir, { recursive: true });
21381
21640
  }
21382
21641
  readKv() {
@@ -21422,8 +21681,8 @@ var init_plugin_storage = __esm({
21422
21681
  });
21423
21682
 
21424
21683
  // src/core/plugin/plugin-context.ts
21425
- import path46 from "path";
21426
- import os21 from "os";
21684
+ import path48 from "path";
21685
+ import os22 from "os";
21427
21686
  function requirePermission(permissions, required, action) {
21428
21687
  if (!permissions.includes(required)) {
21429
21688
  throw new Error(`Plugin does not have '${required}' permission required for ${action}`);
@@ -21442,7 +21701,7 @@ function createPluginContext(opts) {
21442
21701
  config,
21443
21702
  core
21444
21703
  } = opts;
21445
- const instanceRoot = opts.instanceRoot ?? path46.join(os21.homedir(), ".openacp");
21704
+ const instanceRoot = opts.instanceRoot ?? path48.join(os22.homedir(), ".openacp");
21446
21705
  const registeredListeners = [];
21447
21706
  const registeredCommands = [];
21448
21707
  const noopLog = {
@@ -21665,7 +21924,7 @@ var init_lifecycle_manager = __esm({
21665
21924
  log;
21666
21925
  settingsManager;
21667
21926
  pluginRegistry;
21668
- instanceRoot;
21927
+ _instanceRoot;
21669
21928
  contexts = /* @__PURE__ */ new Map();
21670
21929
  loadOrder = [];
21671
21930
  _loaded = /* @__PURE__ */ new Set();
@@ -21679,6 +21938,14 @@ var init_lifecycle_manager = __esm({
21679
21938
  get registry() {
21680
21939
  return this.pluginRegistry;
21681
21940
  }
21941
+ /** Plugin definitions currently in load order (loaded + failed). */
21942
+ get plugins() {
21943
+ return [...this.loadOrder];
21944
+ }
21945
+ /** Root directory of this OpenACP instance (e.g. ~/.openacp). */
21946
+ get instanceRoot() {
21947
+ return this._instanceRoot;
21948
+ }
21682
21949
  constructor(opts) {
21683
21950
  this.serviceRegistry = opts?.serviceRegistry ?? new ServiceRegistry();
21684
21951
  this.middlewareChain = opts?.middlewareChain ?? new MiddlewareChain();
@@ -21698,7 +21965,7 @@ var init_lifecycle_manager = __esm({
21698
21965
  this.log = opts?.log;
21699
21966
  this.settingsManager = opts?.settingsManager;
21700
21967
  this.pluginRegistry = opts?.pluginRegistry;
21701
- this.instanceRoot = opts?.instanceRoot;
21968
+ this._instanceRoot = opts?.instanceRoot;
21702
21969
  }
21703
21970
  getPluginLogger(pluginName) {
21704
21971
  if (this.log && typeof this.log.child === "function") {
@@ -21809,7 +22076,7 @@ var init_lifecycle_manager = __esm({
21809
22076
  config: this.config,
21810
22077
  core: this.core,
21811
22078
  log: this.log,
21812
- instanceRoot: this.instanceRoot
22079
+ instanceRoot: this._instanceRoot
21813
22080
  });
21814
22081
  try {
21815
22082
  await withTimeout(plugin2.setup(ctx), SETUP_TIMEOUT_MS, `${plugin2.name}.setup()`);
@@ -22285,8 +22552,8 @@ var init_core_items = __esm({
22285
22552
  });
22286
22553
 
22287
22554
  // src/core/core.ts
22288
- import path47 from "path";
22289
- import os22 from "os";
22555
+ import path49 from "path";
22556
+ import os23 from "os";
22290
22557
  var log39, OpenACPCore;
22291
22558
  var init_core = __esm({
22292
22559
  "src/core/core.ts"() {
@@ -22354,6 +22621,9 @@ var init_core = __esm({
22354
22621
  get contextManager() {
22355
22622
  return this.getService("context");
22356
22623
  }
22624
+ get settingsManager() {
22625
+ return this.lifecycleManager.settingsManager;
22626
+ }
22357
22627
  constructor(configManager, ctx) {
22358
22628
  this.configManager = configManager;
22359
22629
  this.instanceContext = ctx;
@@ -22365,7 +22635,7 @@ var init_core = __esm({
22365
22635
  );
22366
22636
  this.agentCatalog.load();
22367
22637
  this.agentManager = new AgentManager(this.agentCatalog);
22368
- const storePath = ctx?.paths.sessions ?? path47.join(os22.homedir(), ".openacp", "sessions.json");
22638
+ const storePath = ctx?.paths.sessions ?? path49.join(os23.homedir(), ".openacp", "sessions.json");
22369
22639
  this.sessionStore = new JsonFileSessionStore(
22370
22640
  storePath,
22371
22641
  config.sessionStore.ttlDays
@@ -22389,7 +22659,7 @@ var init_core = __esm({
22389
22659
  sessions: this.sessionManager,
22390
22660
  config: this.configManager,
22391
22661
  core: this,
22392
- storagePath: ctx?.paths.pluginsData ?? path47.join(os22.homedir(), ".openacp", "plugins", "data"),
22662
+ storagePath: ctx?.paths.pluginsData ?? path49.join(os23.homedir(), ".openacp", "plugins", "data"),
22393
22663
  instanceRoot: ctx?.root,
22394
22664
  log: createChildLogger({ module: "plugin" })
22395
22665
  });
@@ -22421,22 +22691,36 @@ var init_core = __esm({
22421
22691
  log39.info({ level: value }, "Log level changed at runtime");
22422
22692
  }
22423
22693
  if (configPath.startsWith("speech.")) {
22424
- const speechSvc = this.speechService;
22694
+ const speechSvc = this.lifecycleManager.serviceRegistry.get("speech");
22425
22695
  if (speechSvc) {
22426
- const newConfig = this.configManager.get();
22427
- const newSpeechConfig = newConfig.speech ?? {
22428
- stt: { provider: null, providers: {} },
22429
- tts: { provider: null, providers: {} }
22430
- };
22431
- speechSvc.refreshProviders(newSpeechConfig);
22432
- log39.info("Speech service config updated at runtime");
22696
+ const settingsMgr = this.settingsManager;
22697
+ if (settingsMgr) {
22698
+ const pluginCfg = await settingsMgr.loadSettings("@openacp/speech");
22699
+ const groqApiKey = pluginCfg.groqApiKey;
22700
+ const sttProviders = {};
22701
+ if (groqApiKey) {
22702
+ sttProviders.groq = { apiKey: groqApiKey };
22703
+ }
22704
+ const newSpeechConfig = {
22705
+ stt: {
22706
+ provider: groqApiKey ? "groq" : null,
22707
+ providers: sttProviders
22708
+ },
22709
+ tts: {
22710
+ provider: pluginCfg.ttsProvider ?? null,
22711
+ providers: {}
22712
+ }
22713
+ };
22714
+ speechSvc.refreshProviders(newSpeechConfig);
22715
+ log39.info("Speech service config updated at runtime (from plugin settings)");
22716
+ }
22433
22717
  }
22434
22718
  }
22435
22719
  }
22436
22720
  );
22437
22721
  registerCoreMenuItems(this.menuRegistry);
22438
22722
  if (ctx?.root) {
22439
- this.assistantRegistry.setInstanceRoot(path47.dirname(ctx.root));
22723
+ this.assistantRegistry.setInstanceRoot(path49.dirname(ctx.root));
22440
22724
  }
22441
22725
  this.assistantRegistry.register(createSessionsSection(this));
22442
22726
  this.assistantRegistry.register(createAgentsSection(this));
@@ -22530,7 +22814,7 @@ var init_core = __esm({
22530
22814
  if (!result) return;
22531
22815
  message = result;
22532
22816
  }
22533
- const access2 = this.securityGuard.checkAccess(message);
22817
+ const access2 = await this.securityGuard.checkAccess(message);
22534
22818
  if (!access2.allowed) {
22535
22819
  log39.warn({ userId: message.userId, reason: access2.reason }, "Access denied");
22536
22820
  if (access2.reason.includes("Session limit")) {
@@ -23762,21 +24046,25 @@ async function printStartBanner() {
23762
24046
  console.log(`${c.dim} AI coding agents, anywhere. v${version}${c.reset}
23763
24047
  `);
23764
24048
  }
23765
- function summarizeConfig(config) {
24049
+ async function summarizeConfig(config, settingsManager) {
23766
24050
  const lines = [];
24051
+ const channelDefs = [
24052
+ { id: "telegram", label: "Telegram", pluginName: "@openacp/telegram", keys: ["botToken", "chatId"] },
24053
+ { id: "discord", label: "Discord", pluginName: "@openacp/adapter-discord", keys: ["guildId", "token"] }
24054
+ ];
23767
24055
  const channelStatuses = [];
23768
- for (const [id, meta] of Object.entries({
23769
- telegram: "Telegram",
23770
- discord: "Discord"
23771
- })) {
23772
- const ch = config.channels[id];
23773
- if (ch?.enabled) {
23774
- channelStatuses.push(`${meta} (enabled)`);
23775
- } else if (ch && Object.keys(ch).length > 1) {
23776
- channelStatuses.push(`${meta} (disabled)`);
23777
- } else {
23778
- channelStatuses.push(`${meta} (not configured)`);
24056
+ for (const def of channelDefs) {
24057
+ const legacyCh = config.channels[def.id];
24058
+ let configured = !!legacyCh && Object.keys(legacyCh).length > 1;
24059
+ let enabled = legacyCh?.enabled === true;
24060
+ if (settingsManager) {
24061
+ const ps = await settingsManager.loadSettings(def.pluginName);
24062
+ if (def.keys.some((k) => ps[k])) {
24063
+ configured = true;
24064
+ enabled = ps.enabled !== false;
24065
+ }
23779
24066
  }
24067
+ channelStatuses.push(`${def.label} (${enabled ? "enabled" : configured ? "disabled" : "not configured"})`);
23780
24068
  }
23781
24069
  lines.push(`Channels: ${channelStatuses.join(", ")}`);
23782
24070
  lines.push(`Default agent: ${config.defaultAgent}`);
@@ -23853,9 +24141,8 @@ async function setupAgents() {
23853
24141
  await catalog.refreshRegistryIfStale();
23854
24142
  if (!catalog.getInstalledAgent("claude")) {
23855
24143
  const claudeRegistry = catalog.findRegistryAgent("claude-acp");
23856
- if (claudeRegistry) {
23857
- await catalog.install("claude-acp");
23858
- } else {
24144
+ const installed2 = claudeRegistry ? await catalog.install("claude-acp") : null;
24145
+ if (!installed2?.ok) {
23859
24146
  const { AgentStore: AgentStore2 } = await Promise.resolve().then(() => (init_agent_store(), agent_store_exports));
23860
24147
  const store = new AgentStore2();
23861
24148
  store.load();
@@ -23993,8 +24280,8 @@ __export(autostart_exports, {
23993
24280
  });
23994
24281
  import { execFileSync as execFileSync7 } from "child_process";
23995
24282
  import * as fs45 from "fs";
23996
- import * as path48 from "path";
23997
- import * as os23 from "os";
24283
+ import * as path50 from "path";
24284
+ import * as os24 from "os";
23998
24285
  function isAutoStartSupported() {
23999
24286
  return process.platform === "darwin" || process.platform === "linux";
24000
24287
  }
@@ -24006,7 +24293,7 @@ function escapeSystemdValue(str) {
24006
24293
  return `"${escaped}"`;
24007
24294
  }
24008
24295
  function generateLaunchdPlist(nodePath, cliPath, logDir2) {
24009
- const logFile = path48.join(logDir2, "openacp.log");
24296
+ const logFile = path50.join(logDir2, "openacp.log");
24010
24297
  return `<?xml version="1.0" encoding="UTF-8"?>
24011
24298
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
24012
24299
  <plist version="1.0">
@@ -24051,12 +24338,12 @@ function installAutoStart(logDir2) {
24051
24338
  return { success: false, error: "Auto-start not supported on this platform" };
24052
24339
  }
24053
24340
  const nodePath = process.execPath;
24054
- const cliPath = path48.resolve(process.argv[1]);
24055
- const resolvedLogDir = logDir2.startsWith("~") ? path48.join(os23.homedir(), logDir2.slice(1)) : logDir2;
24341
+ const cliPath = path50.resolve(process.argv[1]);
24342
+ const resolvedLogDir = logDir2.startsWith("~") ? path50.join(os24.homedir(), logDir2.slice(1)) : logDir2;
24056
24343
  try {
24057
24344
  if (process.platform === "darwin") {
24058
24345
  const plist = generateLaunchdPlist(nodePath, cliPath, resolvedLogDir);
24059
- const dir = path48.dirname(LAUNCHD_PLIST_PATH);
24346
+ const dir = path50.dirname(LAUNCHD_PLIST_PATH);
24060
24347
  fs45.mkdirSync(dir, { recursive: true });
24061
24348
  fs45.writeFileSync(LAUNCHD_PLIST_PATH, plist);
24062
24349
  execFileSync7("launchctl", ["load", LAUNCHD_PLIST_PATH], { stdio: "pipe" });
@@ -24065,7 +24352,7 @@ function installAutoStart(logDir2) {
24065
24352
  }
24066
24353
  if (process.platform === "linux") {
24067
24354
  const unit = generateSystemdUnit(nodePath, cliPath);
24068
- const dir = path48.dirname(SYSTEMD_SERVICE_PATH);
24355
+ const dir = path50.dirname(SYSTEMD_SERVICE_PATH);
24069
24356
  fs45.mkdirSync(dir, { recursive: true });
24070
24357
  fs45.writeFileSync(SYSTEMD_SERVICE_PATH, unit);
24071
24358
  execFileSync7("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
@@ -24131,8 +24418,8 @@ var init_autostart = __esm({
24131
24418
  init_log();
24132
24419
  log40 = createChildLogger({ module: "autostart" });
24133
24420
  LAUNCHD_LABEL = "com.openacp.daemon";
24134
- LAUNCHD_PLIST_PATH = path48.join(os23.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
24135
- SYSTEMD_SERVICE_PATH = path48.join(os23.homedir(), ".config", "systemd", "user", "openacp.service");
24421
+ LAUNCHD_PLIST_PATH = path50.join(os24.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
24422
+ SYSTEMD_SERVICE_PATH = path50.join(os24.homedir(), ".config", "systemd", "user", "openacp.service");
24136
24423
  }
24137
24424
  });
24138
24425
 
@@ -24251,27 +24538,44 @@ var init_setup_integrations = __esm({
24251
24538
  });
24252
24539
 
24253
24540
  // src/core/setup/setup-channels.ts
24254
- import * as path49 from "path";
24541
+ import * as path51 from "path";
24255
24542
  import * as clack7 from "@clack/prompts";
24256
- function getChannelStatuses(config) {
24543
+ async function getChannelStatuses(config, settingsManager) {
24257
24544
  const statuses = [];
24258
24545
  for (const [id, meta] of Object.entries(CHANNEL_META)) {
24259
24546
  const ch = config.channels[id];
24260
- const enabled = ch?.enabled === true;
24261
- const configured = !!ch && Object.keys(ch).length > 1;
24547
+ let configured = !!ch && Object.keys(ch).length > 1;
24548
+ let enabled = ch?.enabled === true;
24262
24549
  let hint;
24263
- if (id === "telegram" && ch?.botToken && typeof ch.botToken === "string" && ch.botToken !== "YOUR_BOT_TOKEN_HERE") {
24264
- hint = `Chat ID: ${ch.chatId}`;
24550
+ if (settingsManager && id === "telegram") {
24551
+ const ps = await settingsManager.loadSettings("@openacp/telegram");
24552
+ if (ps.botToken && ps.chatId) {
24553
+ configured = true;
24554
+ enabled = ps.enabled !== false;
24555
+ hint = `Chat ID: ${ps.chatId}`;
24556
+ }
24557
+ } else if (settingsManager && id === "discord") {
24558
+ const ps = await settingsManager.loadSettings("@openacp/adapter-discord");
24559
+ if (ps.guildId || ps.token) {
24560
+ configured = true;
24561
+ enabled = ps.enabled !== false;
24562
+ hint = ps.guildId ? `Guild: ${ps.guildId}` : void 0;
24563
+ }
24265
24564
  }
24266
- if (id === "discord" && ch?.guildId) {
24267
- hint = `Guild: ${ch.guildId}`;
24565
+ if (!hint) {
24566
+ if (id === "telegram" && ch?.botToken && typeof ch.botToken === "string" && ch.botToken !== "YOUR_BOT_TOKEN_HERE") {
24567
+ hint = `Chat ID: ${ch.chatId}`;
24568
+ }
24569
+ if (id === "discord" && ch?.guildId) {
24570
+ hint = `Guild: ${ch.guildId}`;
24571
+ }
24268
24572
  }
24269
24573
  statuses.push({ id, label: meta.label, configured, enabled, hint });
24270
24574
  }
24271
24575
  return statuses;
24272
24576
  }
24273
- function noteChannelStatus(config) {
24274
- const statuses = getChannelStatuses(config);
24577
+ async function noteChannelStatus(config, settingsManager) {
24578
+ const statuses = await getChannelStatuses(config, settingsManager);
24275
24579
  const lines = statuses.map((s) => {
24276
24580
  const status = s.enabled ? "enabled" : s.configured ? "disabled" : "not configured";
24277
24581
  const hintStr = s.hint ? ` \u2014 ${s.hint}` : "";
@@ -24296,7 +24600,7 @@ async function promptConfiguredAction(label) {
24296
24600
  })
24297
24601
  );
24298
24602
  }
24299
- async function configureViaPlugin(channelId) {
24603
+ async function configureViaPlugin(channelId, settingsManager) {
24300
24604
  const pluginMap = {
24301
24605
  telegram: { importPath: "../../plugins/telegram/index.js", name: "@openacp/telegram" }
24302
24606
  };
@@ -24315,24 +24619,27 @@ async function configureViaPlugin(channelId) {
24315
24619
  }
24316
24620
  }
24317
24621
  if (plugin2?.configure) {
24318
- const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
24319
24622
  const { createInstallContext: createInstallContext2 } = await Promise.resolve().then(() => (init_install_context(), install_context_exports));
24320
- const basePath = path49.join(getGlobalRoot(), "plugins", "data");
24321
- const settingsManager = new SettingsManager2(basePath);
24623
+ let sm = settingsManager;
24624
+ if (!sm) {
24625
+ const { SettingsManager: SM } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
24626
+ const basePath = path51.join(getGlobalRoot(), "plugins", "data");
24627
+ sm = new SM(basePath);
24628
+ }
24322
24629
  const ctx = createInstallContext2({
24323
24630
  pluginName: plugin2.name,
24324
- settingsManager,
24325
- basePath
24631
+ settingsManager: sm,
24632
+ basePath: sm.getBasePath()
24326
24633
  });
24327
24634
  await plugin2.configure(ctx);
24328
24635
  }
24329
24636
  }
24330
- async function configureChannels(config) {
24637
+ async function configureChannels(config, settingsManager) {
24331
24638
  const next = structuredClone(config);
24332
24639
  let changed = false;
24333
- noteChannelStatus(next);
24640
+ await noteChannelStatus(next, settingsManager);
24334
24641
  while (true) {
24335
- const statuses = getChannelStatuses(next);
24642
+ const statuses = await getChannelStatuses(next, settingsManager);
24336
24643
  const options = statuses.map((s) => {
24337
24644
  const status = s.enabled ? "enabled" : s.configured ? "disabled" : "not configured";
24338
24645
  return {
@@ -24353,8 +24660,8 @@ async function configureChannels(config) {
24353
24660
  if (choice === "__done__") break;
24354
24661
  const channelId = choice;
24355
24662
  const meta = CHANNEL_META[channelId];
24356
- const existing = next.channels[channelId];
24357
- const isConfigured = !!existing && Object.keys(existing).length > 1;
24663
+ const statuses2 = await getChannelStatuses(next, settingsManager);
24664
+ const isConfigured = statuses2.find((s) => s.id === channelId)?.configured ?? false;
24358
24665
  if (isConfigured) {
24359
24666
  const action = await promptConfiguredAction(meta.label);
24360
24667
  if (action === "skip") continue;
@@ -24379,7 +24686,7 @@ async function configureChannels(config) {
24379
24686
  continue;
24380
24687
  }
24381
24688
  }
24382
- await configureViaPlugin(channelId);
24689
+ await configureViaPlugin(channelId, settingsManager);
24383
24690
  changed = true;
24384
24691
  }
24385
24692
  return { config: next, changed };
@@ -24399,55 +24706,70 @@ __export(instance_copy_exports, {
24399
24706
  copyInstance: () => copyInstance
24400
24707
  });
24401
24708
  import fs46 from "fs";
24402
- import path50 from "path";
24709
+ import path52 from "path";
24403
24710
  async function copyInstance(src, dst, opts) {
24404
24711
  const { inheritableKeys = {}, onProgress } = opts;
24405
24712
  fs46.mkdirSync(dst, { recursive: true });
24406
- const configSrc = path50.join(src, "config.json");
24713
+ const configSrc = path52.join(src, "config.json");
24407
24714
  if (fs46.existsSync(configSrc)) {
24408
24715
  onProgress?.("Configuration", "start");
24409
24716
  const config = JSON.parse(fs46.readFileSync(configSrc, "utf-8"));
24410
24717
  delete config.instanceName;
24411
- if (config.api) delete config.api.port;
24412
- if (config.tunnel) delete config.tunnel.port;
24413
- fs46.writeFileSync(path50.join(dst, "config.json"), JSON.stringify(config, null, 2));
24718
+ delete config.security;
24719
+ delete config.tunnel;
24720
+ delete config.api;
24721
+ delete config.speech;
24722
+ delete config.usage;
24723
+ if (config.channels && typeof config.channels === "object") {
24724
+ const CORE_CHANNEL_KEYS = /* @__PURE__ */ new Set(["enabled", "outputMode", "adapter", "displayVerbosity"]);
24725
+ for (const ch of Object.values(config.channels)) {
24726
+ if (ch && typeof ch === "object") {
24727
+ for (const key of Object.keys(ch)) {
24728
+ if (!CORE_CHANNEL_KEYS.has(key)) {
24729
+ delete ch[key];
24730
+ }
24731
+ }
24732
+ }
24733
+ }
24734
+ }
24735
+ fs46.writeFileSync(path52.join(dst, "config.json"), JSON.stringify(config, null, 2));
24414
24736
  onProgress?.("Configuration", "done");
24415
24737
  }
24416
- const pluginsSrc = path50.join(src, "plugins.json");
24738
+ const pluginsSrc = path52.join(src, "plugins.json");
24417
24739
  if (fs46.existsSync(pluginsSrc)) {
24418
24740
  onProgress?.("Plugin list", "start");
24419
- fs46.copyFileSync(pluginsSrc, path50.join(dst, "plugins.json"));
24741
+ fs46.copyFileSync(pluginsSrc, path52.join(dst, "plugins.json"));
24420
24742
  onProgress?.("Plugin list", "done");
24421
24743
  }
24422
- const pluginsDir = path50.join(src, "plugins");
24744
+ const pluginsDir = path52.join(src, "plugins");
24423
24745
  if (fs46.existsSync(pluginsDir)) {
24424
24746
  onProgress?.("Plugins", "start");
24425
- const dstPlugins = path50.join(dst, "plugins");
24747
+ const dstPlugins = path52.join(dst, "plugins");
24426
24748
  fs46.mkdirSync(dstPlugins, { recursive: true });
24427
- const pkgJson = path50.join(pluginsDir, "package.json");
24428
- if (fs46.existsSync(pkgJson)) fs46.copyFileSync(pkgJson, path50.join(dstPlugins, "package.json"));
24429
- const nodeModules = path50.join(pluginsDir, "node_modules");
24430
- if (fs46.existsSync(nodeModules)) fs46.cpSync(nodeModules, path50.join(dstPlugins, "node_modules"), { recursive: true });
24749
+ const pkgJson = path52.join(pluginsDir, "package.json");
24750
+ if (fs46.existsSync(pkgJson)) fs46.copyFileSync(pkgJson, path52.join(dstPlugins, "package.json"));
24751
+ const nodeModules = path52.join(pluginsDir, "node_modules");
24752
+ if (fs46.existsSync(nodeModules)) fs46.cpSync(nodeModules, path52.join(dstPlugins, "node_modules"), { recursive: true });
24431
24753
  onProgress?.("Plugins", "done");
24432
24754
  }
24433
- const agentsJson = path50.join(src, "agents.json");
24755
+ const agentsJson = path52.join(src, "agents.json");
24434
24756
  if (fs46.existsSync(agentsJson)) {
24435
24757
  onProgress?.("Agents", "start");
24436
- fs46.copyFileSync(agentsJson, path50.join(dst, "agents.json"));
24437
- const agentsDir = path50.join(src, "agents");
24438
- if (fs46.existsSync(agentsDir)) fs46.cpSync(agentsDir, path50.join(dst, "agents"), { recursive: true });
24758
+ fs46.copyFileSync(agentsJson, path52.join(dst, "agents.json"));
24759
+ const agentsDir = path52.join(src, "agents");
24760
+ if (fs46.existsSync(agentsDir)) fs46.cpSync(agentsDir, path52.join(dst, "agents"), { recursive: true });
24439
24761
  onProgress?.("Agents", "done");
24440
24762
  }
24441
- const binDir = path50.join(src, "bin");
24763
+ const binDir = path52.join(src, "bin");
24442
24764
  if (fs46.existsSync(binDir)) {
24443
24765
  onProgress?.("Tools", "start");
24444
- fs46.cpSync(binDir, path50.join(dst, "bin"), { recursive: true });
24766
+ fs46.cpSync(binDir, path52.join(dst, "bin"), { recursive: true });
24445
24767
  onProgress?.("Tools", "done");
24446
24768
  }
24447
- const pluginDataSrc = path50.join(src, "plugins", "data");
24769
+ const pluginDataSrc = path52.join(src, "plugins", "data");
24448
24770
  if (fs46.existsSync(pluginDataSrc)) {
24449
24771
  onProgress?.("Preferences", "start");
24450
- copyPluginSettings(pluginDataSrc, path50.join(dst, "plugins", "data"), inheritableKeys);
24772
+ copyPluginSettings(pluginDataSrc, path52.join(dst, "plugins", "data"), inheritableKeys);
24451
24773
  onProgress?.("Preferences", "done");
24452
24774
  }
24453
24775
  }
@@ -24462,10 +24784,10 @@ function copyPluginSettings(srcData, dstData, inheritableKeys) {
24462
24784
  if (key in settings) filtered[key] = settings[key];
24463
24785
  }
24464
24786
  if (Object.keys(filtered).length > 0) {
24465
- const relative = path50.relative(srcData, path50.dirname(settingsPath));
24466
- const dstDir = path50.join(dstData, relative);
24787
+ const relative = path52.relative(srcData, path52.dirname(settingsPath));
24788
+ const dstDir = path52.join(dstData, relative);
24467
24789
  fs46.mkdirSync(dstDir, { recursive: true });
24468
- fs46.writeFileSync(path50.join(dstDir, "settings.json"), JSON.stringify(filtered, null, 2));
24790
+ fs46.writeFileSync(path52.join(dstDir, "settings.json"), JSON.stringify(filtered, null, 2));
24469
24791
  }
24470
24792
  } catch {
24471
24793
  }
@@ -24476,15 +24798,15 @@ function walkPluginDirs(base, cb) {
24476
24798
  for (const entry of fs46.readdirSync(base, { withFileTypes: true })) {
24477
24799
  if (!entry.isDirectory()) continue;
24478
24800
  if (entry.name.startsWith("@")) {
24479
- const scopeDir = path50.join(base, entry.name);
24801
+ const scopeDir = path52.join(base, entry.name);
24480
24802
  for (const sub of fs46.readdirSync(scopeDir, { withFileTypes: true })) {
24481
24803
  if (!sub.isDirectory()) continue;
24482
24804
  const pluginName = `${entry.name}/${sub.name}`;
24483
- const settingsPath = path50.join(scopeDir, sub.name, "settings.json");
24805
+ const settingsPath = path52.join(scopeDir, sub.name, "settings.json");
24484
24806
  if (fs46.existsSync(settingsPath)) cb(pluginName, settingsPath);
24485
24807
  }
24486
24808
  } else {
24487
- const settingsPath = path50.join(base, entry.name, "settings.json");
24809
+ const settingsPath = path52.join(base, entry.name, "settings.json");
24488
24810
  if (fs46.existsSync(settingsPath)) cb(entry.name, settingsPath);
24489
24811
  }
24490
24812
  }
@@ -24497,14 +24819,14 @@ var init_instance_copy = __esm({
24497
24819
 
24498
24820
  // src/core/setup/git-protect.ts
24499
24821
  import fs47 from "fs";
24500
- import path51 from "path";
24822
+ import path53 from "path";
24501
24823
  function protectLocalInstance(projectDir) {
24502
24824
  ensureGitignore(projectDir);
24503
24825
  ensureClaudeMd(projectDir);
24504
24826
  printSecurityWarning();
24505
24827
  }
24506
24828
  function ensureGitignore(projectDir) {
24507
- const gitignorePath = path51.join(projectDir, ".gitignore");
24829
+ const gitignorePath = path53.join(projectDir, ".gitignore");
24508
24830
  const entry = ".openacp";
24509
24831
  if (fs47.existsSync(gitignorePath)) {
24510
24832
  const content = fs47.readFileSync(gitignorePath, "utf-8");
@@ -24524,7 +24846,7 @@ ${entry}
24524
24846
  }
24525
24847
  }
24526
24848
  function ensureClaudeMd(projectDir) {
24527
- const claudeMdPath = path51.join(projectDir, "CLAUDE.md");
24849
+ const claudeMdPath = path53.join(projectDir, "CLAUDE.md");
24528
24850
  const marker = "## Local OpenACP Workspace";
24529
24851
  if (fs47.existsSync(claudeMdPath)) {
24530
24852
  const content = fs47.readFileSync(claudeMdPath, "utf-8");
@@ -24567,9 +24889,9 @@ var init_git_protect = __esm({
24567
24889
  });
24568
24890
 
24569
24891
  // src/core/setup/wizard.ts
24570
- import * as path52 from "path";
24892
+ import * as path54 from "path";
24571
24893
  import * as fs48 from "fs";
24572
- import * as os24 from "os";
24894
+ import * as os25 from "os";
24573
24895
  import * as clack8 from "@clack/prompts";
24574
24896
  import { randomUUID as randomUUID2 } from "crypto";
24575
24897
  async function fetchCommunityAdapters() {
@@ -24591,7 +24913,7 @@ function checkDesktopAppInstalled() {
24591
24913
  const platform2 = process.platform;
24592
24914
  if (platform2 === "darwin") {
24593
24915
  candidates.push("/Applications/OpenACP.app");
24594
- candidates.push(`${os24.homedir()}/Applications/OpenACP.app`);
24916
+ candidates.push(`${os25.homedir()}/Applications/OpenACP.app`);
24595
24917
  } else if (platform2 === "win32") {
24596
24918
  const localAppData = process.env["LOCALAPPDATA"] ?? "";
24597
24919
  candidates.push(`${localAppData}\\Programs\\OpenACP\\OpenACP.exe`);
@@ -24599,7 +24921,7 @@ function checkDesktopAppInstalled() {
24599
24921
  } else {
24600
24922
  candidates.push("/usr/bin/openacp-desktop");
24601
24923
  candidates.push("/usr/local/bin/openacp-desktop");
24602
- candidates.push(`${os24.homedir()}/.local/bin/openacp-desktop`);
24924
+ candidates.push(`${os25.homedir()}/.local/bin/openacp-desktop`);
24603
24925
  }
24604
24926
  return candidates.some((p2) => fs48.existsSync(p2));
24605
24927
  }
@@ -24616,8 +24938,8 @@ async function runSetup(configManager, opts) {
24616
24938
  const isGlobal = instanceRoot === getGlobalRoot();
24617
24939
  let instanceName = opts?.instanceName;
24618
24940
  if (!instanceName) {
24619
- const defaultName = isGlobal ? "Global workspace" : path52.basename(path52.dirname(instanceRoot));
24620
- const locationHint = isGlobal ? "global (~/.openacp)" : `local (${instanceRoot.replace(/\/.openacp$/, "").replace(os24.homedir(), "~")})`;
24941
+ const defaultName = isGlobal ? "Global workspace" : path54.basename(path54.dirname(instanceRoot));
24942
+ const locationHint = isGlobal ? "global (~/.openacp)" : `local (${instanceRoot.replace(/\/.openacp$/, "").replace(os25.homedir(), "~")})`;
24621
24943
  const nameResult = await clack8.text({
24622
24944
  message: `Name for this workspace (${locationHint})`,
24623
24945
  initialValue: defaultName,
@@ -24627,13 +24949,13 @@ async function runSetup(configManager, opts) {
24627
24949
  instanceName = nameResult.trim();
24628
24950
  }
24629
24951
  const globalRoot = getGlobalRoot();
24630
- const registryPath = path52.join(globalRoot, "instances.json");
24952
+ const registryPath = path54.join(globalRoot, "instances.json");
24631
24953
  const instanceRegistry = new InstanceRegistry(registryPath);
24632
24954
  await instanceRegistry.load();
24633
24955
  let didCopy = false;
24634
24956
  if (opts?.from) {
24635
- const fromRoot = path52.join(opts.from, ".openacp");
24636
- if (fs48.existsSync(path52.join(fromRoot, "config.json"))) {
24957
+ const fromRoot = path54.join(opts.from, ".openacp");
24958
+ if (fs48.existsSync(path54.join(fromRoot, "config.json"))) {
24637
24959
  const inheritableMap = buildInheritableKeysMap();
24638
24960
  await copyInstance(fromRoot, instanceRoot, { inheritableKeys: inheritableMap });
24639
24961
  didCopy = true;
@@ -24644,7 +24966,7 @@ async function runSetup(configManager, opts) {
24644
24966
  }
24645
24967
  if (!didCopy) {
24646
24968
  const existingInstances = instanceRegistry.list().filter(
24647
- (e) => fs48.existsSync(path52.join(e.root, "config.json")) && e.root !== instanceRoot
24969
+ (e) => fs48.existsSync(path54.join(e.root, "config.json")) && e.root !== instanceRoot
24648
24970
  );
24649
24971
  if (existingInstances.length > 0) {
24650
24972
  let singleLabel;
@@ -24652,12 +24974,12 @@ async function runSetup(configManager, opts) {
24652
24974
  const e = existingInstances[0];
24653
24975
  let name = e.id;
24654
24976
  try {
24655
- const cfg = JSON.parse(fs48.readFileSync(path52.join(e.root, "config.json"), "utf-8"));
24977
+ const cfg = JSON.parse(fs48.readFileSync(path54.join(e.root, "config.json"), "utf-8"));
24656
24978
  if (cfg.instanceName) name = cfg.instanceName;
24657
24979
  } catch {
24658
24980
  }
24659
24981
  const isGlobalEntry = e.root === getGlobalRoot();
24660
- const displayPath = e.root.replace(os24.homedir(), "~");
24982
+ const displayPath = e.root.replace(os25.homedir(), "~");
24661
24983
  const type = isGlobalEntry ? "global" : "local";
24662
24984
  singleLabel = `${name} workspace (${type} \u2014 ${displayPath})`;
24663
24985
  }
@@ -24677,12 +24999,12 @@ async function runSetup(configManager, opts) {
24677
24999
  options: existingInstances.map((e) => {
24678
25000
  let name = e.id;
24679
25001
  try {
24680
- const cfg = JSON.parse(fs48.readFileSync(path52.join(e.root, "config.json"), "utf-8"));
25002
+ const cfg = JSON.parse(fs48.readFileSync(path54.join(e.root, "config.json"), "utf-8"));
24681
25003
  if (cfg.instanceName) name = cfg.instanceName;
24682
25004
  } catch {
24683
25005
  }
24684
25006
  const isGlobalEntry = e.root === getGlobalRoot();
24685
- const displayPath = e.root.replace(os24.homedir(), "~");
25007
+ const displayPath = e.root.replace(os25.homedir(), "~");
24686
25008
  const type = isGlobalEntry ? "global" : "local";
24687
25009
  return { value: e.root, label: `${name} workspace (${type} \u2014 ${displayPath})` };
24688
25010
  })
@@ -24774,9 +25096,9 @@ async function runSetup(configManager, opts) {
24774
25096
  if (channelId.startsWith("official:")) {
24775
25097
  const npmPackage = channelId.slice("official:".length);
24776
25098
  const { execFileSync: execFileSync8 } = await import("child_process");
24777
- const pluginsDir = path52.join(instanceRoot, "plugins");
24778
- const nodeModulesDir = path52.join(pluginsDir, "node_modules");
24779
- const installedPath = path52.join(nodeModulesDir, npmPackage);
25099
+ const pluginsDir = path54.join(instanceRoot, "plugins");
25100
+ const nodeModulesDir = path54.join(pluginsDir, "node_modules");
25101
+ const installedPath = path54.join(nodeModulesDir, npmPackage);
24780
25102
  if (!fs48.existsSync(installedPath)) {
24781
25103
  try {
24782
25104
  clack8.log.step(`Installing ${npmPackage}...`);
@@ -24790,9 +25112,9 @@ async function runSetup(configManager, opts) {
24790
25112
  }
24791
25113
  }
24792
25114
  try {
24793
- const installedPkgPath = path52.join(nodeModulesDir, npmPackage, "package.json");
25115
+ const installedPkgPath = path54.join(nodeModulesDir, npmPackage, "package.json");
24794
25116
  const installedPkg = JSON.parse(fs48.readFileSync(installedPkgPath, "utf-8"));
24795
- const pluginModule = await import(path52.join(nodeModulesDir, npmPackage, installedPkg.main ?? "dist/index.js"));
25117
+ const pluginModule = await import(path54.join(nodeModulesDir, npmPackage, installedPkg.main ?? "dist/index.js"));
24796
25118
  const plugin2 = pluginModule.default;
24797
25119
  if (plugin2?.install) {
24798
25120
  const installCtx = createInstallContext2({
@@ -24822,8 +25144,8 @@ async function runSetup(configManager, opts) {
24822
25144
  if (channelId.startsWith("community:")) {
24823
25145
  const npmPackage = channelId.slice("community:".length);
24824
25146
  const { execFileSync: execFileSync8 } = await import("child_process");
24825
- const pluginsDir = path52.join(instanceRoot, "plugins");
24826
- const nodeModulesDir = path52.join(pluginsDir, "node_modules");
25147
+ const pluginsDir = path54.join(instanceRoot, "plugins");
25148
+ const nodeModulesDir = path54.join(pluginsDir, "node_modules");
24827
25149
  try {
24828
25150
  execFileSync8("npm", ["install", npmPackage, "--prefix", pluginsDir, "--save"], {
24829
25151
  stdio: "inherit",
@@ -24835,9 +25157,9 @@ async function runSetup(configManager, opts) {
24835
25157
  }
24836
25158
  try {
24837
25159
  const { readFileSync: readFileSync19 } = await import("fs");
24838
- const installedPkgPath = path52.join(nodeModulesDir, npmPackage, "package.json");
25160
+ const installedPkgPath = path54.join(nodeModulesDir, npmPackage, "package.json");
24839
25161
  const installedPkg = JSON.parse(readFileSync19(installedPkgPath, "utf-8"));
24840
- const pluginModule = await import(path52.join(nodeModulesDir, npmPackage, installedPkg.main ?? "dist/index.js"));
25162
+ const pluginModule = await import(path54.join(nodeModulesDir, npmPackage, installedPkg.main ?? "dist/index.js"));
24841
25163
  const plugin2 = pluginModule.default;
24842
25164
  if (plugin2?.install) {
24843
25165
  const installCtx = createInstallContext2({
@@ -24892,7 +25214,7 @@ async function runSetup(configManager, opts) {
24892
25214
  security,
24893
25215
  logging: {
24894
25216
  level: "info",
24895
- logDir: path52.join(instanceRoot, "logs"),
25217
+ logDir: path54.join(instanceRoot, "logs"),
24896
25218
  maxFileSize: "10m",
24897
25219
  maxFiles: 7,
24898
25220
  sessionLogRetentionDays: 30
@@ -24944,9 +25266,9 @@ async function runSetup(configManager, opts) {
24944
25266
  instanceRegistry.register(id, instanceRoot);
24945
25267
  await instanceRegistry.save();
24946
25268
  }
24947
- const isLocal = instanceRoot !== path52.join(getGlobalRoot());
25269
+ const isLocal = instanceRoot !== path54.join(getGlobalRoot());
24948
25270
  if (isLocal) {
24949
- const projectDir = path52.dirname(instanceRoot);
25271
+ const projectDir = path54.dirname(instanceRoot);
24950
25272
  protectLocalInstance(projectDir);
24951
25273
  }
24952
25274
  clack8.outro(`Config saved to ${configManager.getConfigPath()}`);
@@ -25010,20 +25332,20 @@ async function selectSection(hasSelection) {
25010
25332
  })
25011
25333
  );
25012
25334
  }
25013
- async function runReconfigure(configManager) {
25335
+ async function runReconfigure(configManager, settingsManager) {
25014
25336
  await printStartBanner();
25015
25337
  clack8.intro("OpenACP \u2014 Reconfigure");
25016
25338
  try {
25017
25339
  await configManager.load();
25018
25340
  let config = configManager.get();
25019
- clack8.note(summarizeConfig(config), "Current configuration");
25341
+ clack8.note(await summarizeConfig(config, settingsManager), "Current configuration");
25020
25342
  let ranSection = false;
25021
25343
  while (true) {
25022
25344
  const choice = await selectSection(ranSection);
25023
25345
  if (choice === "__continue") break;
25024
25346
  ranSection = true;
25025
25347
  if (choice === "channels") {
25026
- const result = await configureChannels(config);
25348
+ const result = await configureChannels(config, settingsManager);
25027
25349
  if (result.changed) {
25028
25350
  config = { ...config, channels: result.config.channels };
25029
25351
  await configManager.writeNew(config);
@@ -25234,7 +25556,7 @@ __export(dev_loader_exports, {
25234
25556
  DevPluginLoader: () => DevPluginLoader
25235
25557
  });
25236
25558
  import fs49 from "fs";
25237
- import path53 from "path";
25559
+ import path55 from "path";
25238
25560
  var loadCounter, DevPluginLoader;
25239
25561
  var init_dev_loader = __esm({
25240
25562
  "src/core/plugin/dev-loader.ts"() {
@@ -25243,11 +25565,11 @@ var init_dev_loader = __esm({
25243
25565
  DevPluginLoader = class {
25244
25566
  pluginPath;
25245
25567
  constructor(pluginPath) {
25246
- this.pluginPath = path53.resolve(pluginPath);
25568
+ this.pluginPath = path55.resolve(pluginPath);
25247
25569
  }
25248
25570
  async load() {
25249
- const distIndex = path53.join(this.pluginPath, "dist", "index.js");
25250
- const srcIndex = path53.join(this.pluginPath, "src", "index.ts");
25571
+ const distIndex = path55.join(this.pluginPath, "dist", "index.js");
25572
+ const srcIndex = path55.join(this.pluginPath, "src", "index.ts");
25251
25573
  if (!fs49.existsSync(distIndex) && !fs49.existsSync(srcIndex)) {
25252
25574
  throw new Error(`Plugin not found at ${this.pluginPath}. Expected dist/index.js or src/index.ts`);
25253
25575
  }
@@ -25266,7 +25588,7 @@ var init_dev_loader = __esm({
25266
25588
  return this.pluginPath;
25267
25589
  }
25268
25590
  getDistPath() {
25269
- return path53.join(this.pluginPath, "dist");
25591
+ return path55.join(this.pluginPath, "dist");
25270
25592
  }
25271
25593
  };
25272
25594
  }
@@ -25278,13 +25600,13 @@ __export(main_exports, {
25278
25600
  RESTART_EXIT_CODE: () => RESTART_EXIT_CODE,
25279
25601
  startServer: () => startServer
25280
25602
  });
25281
- import path54 from "path";
25603
+ import path56 from "path";
25282
25604
  import { randomUUID as randomUUID3 } from "crypto";
25283
25605
  import fs50 from "fs";
25284
25606
  async function startServer(opts) {
25285
25607
  const globalRoot = getGlobalRoot();
25286
25608
  if (!opts?.instanceContext) {
25287
- const reg = new InstanceRegistry(path54.join(globalRoot, "instances.json"));
25609
+ const reg = new InstanceRegistry(path56.join(globalRoot, "instances.json"));
25288
25610
  reg.load();
25289
25611
  const entry = reg.getByRoot(globalRoot);
25290
25612
  opts = { ...opts, instanceContext: createInstanceContext({ id: entry?.id ?? randomUUID3(), root: globalRoot, isGlobal: true }) };
@@ -25325,6 +25647,7 @@ async function startServer(opts) {
25325
25647
  const config = configManager.get();
25326
25648
  initLogger(config.logging);
25327
25649
  log.debug({ configPath: configManager.getConfigPath() }, "Config loaded");
25650
+ await configManager.applyEnvToPluginSettings(settingsManager);
25328
25651
  if (pluginRegistry.list().size === 0) {
25329
25652
  await autoRegisterBuiltinPlugins(settingsManager, pluginRegistry, configManager);
25330
25653
  }
@@ -25368,22 +25691,22 @@ async function startServer(opts) {
25368
25691
  try {
25369
25692
  let modulePath;
25370
25693
  if (name.startsWith("/") || name.startsWith(".")) {
25371
- const resolved = path54.resolve(name);
25372
- const pkgPath = path54.join(resolved, "package.json");
25694
+ const resolved = path56.resolve(name);
25695
+ const pkgPath = path56.join(resolved, "package.json");
25373
25696
  const pkg = JSON.parse(await fs50.promises.readFile(pkgPath, "utf-8"));
25374
- modulePath = path54.join(resolved, pkg.main || "dist/index.js");
25697
+ modulePath = path56.join(resolved, pkg.main || "dist/index.js");
25375
25698
  } else {
25376
- const nodeModulesDir = path54.join(ctx.paths.plugins, "node_modules");
25377
- let pkgDir = path54.join(nodeModulesDir, name);
25378
- if (!fs50.existsSync(path54.join(pkgDir, "package.json"))) {
25699
+ const nodeModulesDir = path56.join(ctx.paths.plugins, "node_modules");
25700
+ let pkgDir = path56.join(nodeModulesDir, name);
25701
+ if (!fs50.existsSync(path56.join(pkgDir, "package.json"))) {
25379
25702
  let found = false;
25380
25703
  const scopes = fs50.existsSync(nodeModulesDir) ? fs50.readdirSync(nodeModulesDir).filter((d) => d.startsWith("@")) : [];
25381
25704
  for (const scope of scopes) {
25382
- const scopeDir = path54.join(nodeModulesDir, scope);
25705
+ const scopeDir = path56.join(nodeModulesDir, scope);
25383
25706
  const pkgs = fs50.readdirSync(scopeDir);
25384
25707
  for (const pkg2 of pkgs) {
25385
- const candidateDir = path54.join(scopeDir, pkg2);
25386
- const candidatePkgPath = path54.join(candidateDir, "package.json");
25708
+ const candidateDir = path56.join(scopeDir, pkg2);
25709
+ const candidatePkgPath = path56.join(candidateDir, "package.json");
25387
25710
  if (fs50.existsSync(candidatePkgPath)) {
25388
25711
  try {
25389
25712
  const candidatePkg = JSON.parse(fs50.readFileSync(candidatePkgPath, "utf-8"));
@@ -25400,9 +25723,9 @@ async function startServer(opts) {
25400
25723
  if (found) break;
25401
25724
  }
25402
25725
  }
25403
- const pkgJsonPath = path54.join(pkgDir, "package.json");
25726
+ const pkgJsonPath = path56.join(pkgDir, "package.json");
25404
25727
  const pkg = JSON.parse(await fs50.promises.readFile(pkgJsonPath, "utf-8"));
25405
- modulePath = path54.join(pkgDir, pkg.main || "dist/index.js");
25728
+ modulePath = path56.join(pkgDir, pkg.main || "dist/index.js");
25406
25729
  }
25407
25730
  log.debug({ plugin: name, modulePath }, "Loading community plugin");
25408
25731
  const mod = await import(modulePath);
@@ -25565,7 +25888,7 @@ async function startServer(opts) {
25565
25888
  await core.start();
25566
25889
  try {
25567
25890
  const globalRoot2 = getGlobalRoot();
25568
- const registryPath = path54.join(globalRoot2, "instances.json");
25891
+ const registryPath = path56.join(globalRoot2, "instances.json");
25569
25892
  const instanceReg = new InstanceRegistry(registryPath);
25570
25893
  await instanceReg.load();
25571
25894
  if (!instanceReg.getByRoot(ctx.root)) {
@@ -25728,13 +26051,13 @@ var restart_exports = {};
25728
26051
  __export(restart_exports, {
25729
26052
  cmdRestart: () => cmdRestart
25730
26053
  });
25731
- import path55 from "path";
25732
- import os25 from "os";
26054
+ import path57 from "path";
26055
+ import os26 from "os";
25733
26056
  import { randomUUID as randomUUID4 } from "crypto";
25734
26057
  async function cmdRestart(args2 = [], instanceRoot) {
25735
26058
  const json = isJsonMode(args2);
25736
26059
  if (json) await muteForJson();
25737
- const root = instanceRoot ?? path55.join(os25.homedir(), ".openacp");
26060
+ const root = instanceRoot ?? path57.join(os26.homedir(), ".openacp");
25738
26061
  if (!json && wantsHelp(args2)) {
25739
26062
  console.log(`
25740
26063
  \x1B[1mopenacp restart\x1B[0m \u2014 Restart the background daemon
@@ -25782,7 +26105,7 @@ Stops the running daemon (if any) and starts a new one.
25782
26105
  printInstanceHint(root);
25783
26106
  console.log("Starting in foreground mode...");
25784
26107
  const { startServer: startServer2 } = await Promise.resolve().then(() => (init_main(), main_exports));
25785
- const reg = new InstanceRegistry(path55.join(getGlobalRoot(), "instances.json"));
26108
+ const reg = new InstanceRegistry(path57.join(getGlobalRoot(), "instances.json"));
25786
26109
  reg.load();
25787
26110
  const existingEntry = reg.getByRoot(root);
25788
26111
  const ctx = createInstanceContext({
@@ -25798,7 +26121,7 @@ Stops the running daemon (if any) and starts a new one.
25798
26121
  console.error(result.error);
25799
26122
  process.exit(1);
25800
26123
  }
25801
- if (json) jsonSuccess({ pid: result.pid, instanceId: path55.basename(root), dir: root });
26124
+ if (json) jsonSuccess({ pid: result.pid, instanceId: path57.basename(root), dir: root });
25802
26125
  printInstanceHint(root);
25803
26126
  console.log(`OpenACP daemon started (PID ${result.pid})`);
25804
26127
  }
@@ -25822,8 +26145,8 @@ __export(status_exports, {
25822
26145
  readInstanceInfo: () => readInstanceInfo
25823
26146
  });
25824
26147
  import fs51 from "fs";
25825
- import path56 from "path";
25826
- import os26 from "os";
26148
+ import path58 from "path";
26149
+ import os27 from "os";
25827
26150
  async function cmdStatus(args2 = [], instanceRoot) {
25828
26151
  const json = isJsonMode(args2);
25829
26152
  if (json) await muteForJson();
@@ -25840,7 +26163,7 @@ async function cmdStatus(args2 = [], instanceRoot) {
25840
26163
  await showSingleInstance(root, json);
25841
26164
  }
25842
26165
  async function showAllInstances(json = false) {
25843
- const registryPath = path56.join(getGlobalRoot(), "instances.json");
26166
+ const registryPath = path58.join(getGlobalRoot(), "instances.json");
25844
26167
  const registry = new InstanceRegistry(registryPath);
25845
26168
  await registry.load();
25846
26169
  const instances = registry.list();
@@ -25875,7 +26198,7 @@ async function showAllInstances(json = false) {
25875
26198
  const mode = info.pid ? info.runMode === "daemon" ? "daemon" : "fg" : "\u2014";
25876
26199
  const api = info.apiPort ? String(info.apiPort) : "\u2014";
25877
26200
  const tunnel = info.tunnelPort ? String(info.tunnelPort) : "\u2014";
25878
- const dir = entry.root.replace(/\/.openacp$/, "").replace(os26.homedir(), "~");
26201
+ const dir = entry.root.replace(/\/.openacp$/, "").replace(os27.homedir(), "~");
25879
26202
  const channels = info.channels.join(", ") || "\u2014";
25880
26203
  const name = info.name ?? entry.id;
25881
26204
  console.log(` ${status.padEnd(10)} ${entry.id.padEnd(16)} ${name.padEnd(16)} ${dir.padEnd(20)} ${mode.padEnd(8)} ${channels.padEnd(10)} ${api.padEnd(6)} ${tunnel}`);
@@ -25883,7 +26206,7 @@ async function showAllInstances(json = false) {
25883
26206
  console.log("");
25884
26207
  }
25885
26208
  async function showInstanceById(id, json = false) {
25886
- const registryPath = path56.join(getGlobalRoot(), "instances.json");
26209
+ const registryPath = path58.join(getGlobalRoot(), "instances.json");
25887
26210
  const registry = new InstanceRegistry(registryPath);
25888
26211
  await registry.load();
25889
26212
  const entry = registry.get(id);
@@ -25898,7 +26221,7 @@ async function showSingleInstance(root, json = false) {
25898
26221
  const info = readInstanceInfo(root);
25899
26222
  if (json) {
25900
26223
  jsonSuccess({
25901
- id: path56.basename(root),
26224
+ id: path58.basename(root),
25902
26225
  name: info.name,
25903
26226
  status: info.pid ? "online" : "offline",
25904
26227
  pid: info.pid,
@@ -25929,13 +26252,13 @@ function readInstanceInfo(root) {
25929
26252
  channels: []
25930
26253
  };
25931
26254
  try {
25932
- const config = JSON.parse(fs51.readFileSync(path56.join(root, "config.json"), "utf-8"));
26255
+ const config = JSON.parse(fs51.readFileSync(path58.join(root, "config.json"), "utf-8"));
25933
26256
  result.name = config.instanceName ?? null;
25934
26257
  result.runMode = config.runMode ?? null;
25935
26258
  } catch {
25936
26259
  }
25937
26260
  try {
25938
- const pid = parseInt(fs51.readFileSync(path56.join(root, "openacp.pid"), "utf-8").trim());
26261
+ const pid = parseInt(fs51.readFileSync(path58.join(root, "openacp.pid"), "utf-8").trim());
25939
26262
  if (!isNaN(pid)) {
25940
26263
  process.kill(pid, 0);
25941
26264
  result.pid = pid;
@@ -25943,19 +26266,19 @@ function readInstanceInfo(root) {
25943
26266
  } catch {
25944
26267
  }
25945
26268
  try {
25946
- const port = parseInt(fs51.readFileSync(path56.join(root, "api.port"), "utf-8").trim());
26269
+ const port = parseInt(fs51.readFileSync(path58.join(root, "api.port"), "utf-8").trim());
25947
26270
  if (!isNaN(port)) result.apiPort = port;
25948
26271
  } catch {
25949
26272
  }
25950
26273
  try {
25951
- const tunnels = JSON.parse(fs51.readFileSync(path56.join(root, "tunnels.json"), "utf-8"));
26274
+ const tunnels = JSON.parse(fs51.readFileSync(path58.join(root, "tunnels.json"), "utf-8"));
25952
26275
  const entries = Object.values(tunnels);
25953
26276
  const systemEntry = entries.find((t) => t.type === "system");
25954
26277
  if (systemEntry?.port) result.tunnelPort = systemEntry.port;
25955
26278
  } catch {
25956
26279
  }
25957
26280
  try {
25958
- const plugins = JSON.parse(fs51.readFileSync(path56.join(root, "plugins.json"), "utf-8"));
26281
+ const plugins = JSON.parse(fs51.readFileSync(path58.join(root, "plugins.json"), "utf-8"));
25959
26282
  const adapters = ["@openacp/telegram", "@openacp/discord", "@openacp/slack"];
25960
26283
  for (const name of adapters) {
25961
26284
  if (plugins.installed?.[name] && plugins.installed[name].enabled !== false) {
@@ -25970,7 +26293,7 @@ function formatInstanceStatus(root) {
25970
26293
  const info = readInstanceInfo(root);
25971
26294
  if (!info.pid) return null;
25972
26295
  const isGlobal = root === getGlobalRoot();
25973
- const displayPath = root.replace(os26.homedir(), "~");
26296
+ const displayPath = root.replace(os27.homedir(), "~");
25974
26297
  const label = isGlobal ? "global" : "local";
25975
26298
  const lines = [];
25976
26299
  lines.push(` PID: ${info.pid}`);
@@ -26039,7 +26362,7 @@ var config_editor_exports = {};
26039
26362
  __export(config_editor_exports, {
26040
26363
  runConfigEditor: () => runConfigEditor
26041
26364
  });
26042
- import * as path57 from "path";
26365
+ import * as path59 from "path";
26043
26366
  import * as clack9 from "@clack/prompts";
26044
26367
  async function select8(opts) {
26045
26368
  const result = await clack9.select({
@@ -26070,11 +26393,19 @@ async function input(opts) {
26070
26393
  }
26071
26394
  return result;
26072
26395
  }
26073
- async function editTelegram(config, updates) {
26396
+ async function editTelegram(config, updates, settingsManager) {
26074
26397
  const tg = config.channels?.telegram ?? {};
26075
- const currentToken = tg.botToken ?? "";
26076
- const currentChatId = tg.chatId ?? 0;
26077
- const currentEnabled = tg.enabled ?? false;
26398
+ let currentToken = tg.botToken ?? "";
26399
+ let currentChatId = tg.chatId ?? 0;
26400
+ let currentEnabled = tg.enabled ?? false;
26401
+ if (settingsManager) {
26402
+ const ps = await settingsManager.loadSettings("@openacp/telegram");
26403
+ if (Object.keys(ps).length > 0) {
26404
+ currentToken = ps.botToken ?? currentToken;
26405
+ currentChatId = ps.chatId ?? currentChatId;
26406
+ currentEnabled = ps.enabled ?? currentEnabled;
26407
+ }
26408
+ }
26078
26409
  console.log(header("Telegram"));
26079
26410
  console.log(` Enabled : ${currentEnabled ? ok2("yes") : dim2("no")}`);
26080
26411
  const tokenDisplay = currentToken.length > 12 ? currentToken.slice(0, 6) + "..." + currentToken.slice(-6) : currentToken || dim2("(not set)");
@@ -26089,7 +26420,11 @@ async function editTelegram(config, updates) {
26089
26420
  return updates.channels.telegram;
26090
26421
  };
26091
26422
  while (true) {
26092
- const isEnabled = (() => {
26423
+ const isEnabled = await (async () => {
26424
+ if (settingsManager) {
26425
+ const ps = await settingsManager.loadSettings("@openacp/telegram");
26426
+ if ("enabled" in ps) return ps.enabled;
26427
+ }
26093
26428
  const ch = updates.channels;
26094
26429
  const tgUp = ch?.telegram;
26095
26430
  if (tgUp && "enabled" in tgUp) return tgUp.enabled;
@@ -26106,8 +26441,12 @@ async function editTelegram(config, updates) {
26106
26441
  });
26107
26442
  if (choice === "back") break;
26108
26443
  if (choice === "toggle") {
26109
- const tgUp = ensureTelegramUpdates();
26110
- tgUp.enabled = !isEnabled;
26444
+ if (settingsManager) {
26445
+ await settingsManager.updatePluginSettings("@openacp/telegram", { enabled: !isEnabled });
26446
+ } else {
26447
+ const tgUp = ensureTelegramUpdates();
26448
+ tgUp.enabled = !isEnabled;
26449
+ }
26111
26450
  console.log(!isEnabled ? ok2("Telegram enabled") : ok2("Telegram disabled"));
26112
26451
  }
26113
26452
  if (choice === "token") {
@@ -26127,9 +26466,13 @@ async function editTelegram(config, updates) {
26127
26466
  } catch {
26128
26467
  console.log(warn2("Telegram validator not available \u2014 skipping validation"));
26129
26468
  }
26130
- const tgUp = ensureTelegramUpdates();
26131
- tgUp.botToken = token.trim();
26132
- tgUp.enabled = true;
26469
+ if (settingsManager) {
26470
+ await settingsManager.updatePluginSettings("@openacp/telegram", { botToken: token.trim(), enabled: true });
26471
+ } else {
26472
+ const tgUp = ensureTelegramUpdates();
26473
+ tgUp.botToken = token.trim();
26474
+ tgUp.enabled = true;
26475
+ }
26133
26476
  }
26134
26477
  if (choice === "chatid") {
26135
26478
  const chatIdStr = await input({
@@ -26144,8 +26487,8 @@ async function editTelegram(config, updates) {
26144
26487
  const chatId = Number(chatIdStr.trim());
26145
26488
  const tokenForValidation = (() => {
26146
26489
  const ch = updates.channels;
26147
- const tgUp2 = ch?.telegram;
26148
- if (typeof tgUp2?.botToken === "string") return tgUp2.botToken;
26490
+ const tgUp = ch?.telegram;
26491
+ if (typeof tgUp?.botToken === "string") return tgUp.botToken;
26149
26492
  return currentToken;
26150
26493
  })();
26151
26494
  try {
@@ -26159,8 +26502,12 @@ async function editTelegram(config, updates) {
26159
26502
  } catch {
26160
26503
  console.log(warn2("Telegram validator not available \u2014 skipping validation"));
26161
26504
  }
26162
- const tgUp = ensureTelegramUpdates();
26163
- tgUp.chatId = chatId;
26505
+ if (settingsManager) {
26506
+ await settingsManager.updatePluginSettings("@openacp/telegram", { chatId });
26507
+ } else {
26508
+ const tgUp = ensureTelegramUpdates();
26509
+ tgUp.chatId = chatId;
26510
+ }
26164
26511
  }
26165
26512
  }
26166
26513
  }
@@ -26200,7 +26547,7 @@ async function editDiscord(_config, _updates) {
26200
26547
  const { createInstallContext: createInstallContext2 } = await Promise.resolve().then(() => (init_install_context(), install_context_exports));
26201
26548
  const { getGlobalRoot: getGlobalRoot2 } = await Promise.resolve().then(() => (init_instance_context(), instance_context_exports));
26202
26549
  const root = getGlobalRoot2();
26203
- const basePath = path57.join(root, "plugins");
26550
+ const basePath = path59.join(root, "plugins", "data");
26204
26551
  const settingsManager = new SettingsManager2(basePath);
26205
26552
  const ctx = createInstallContext2({
26206
26553
  pluginName: plugin2.name,
@@ -26213,12 +26560,18 @@ async function editDiscord(_config, _updates) {
26213
26560
  console.log(warn2("This plugin does not have a configure() method yet."));
26214
26561
  }
26215
26562
  }
26216
- async function editChannels(config, updates) {
26217
- const tgEnabled = config.channels?.telegram?.enabled !== false && config.channels?.telegram;
26218
- const dcEnabled = config.channels?.discord?.enabled !== false && config.channels?.discord;
26563
+ async function editChannels(config, updates, settingsManager) {
26564
+ let tgConfigured = !!config.channels?.telegram;
26565
+ let dcConfigured = !!config.channels?.discord;
26566
+ if (settingsManager) {
26567
+ const tgPs = await settingsManager.loadSettings("@openacp/telegram");
26568
+ if (tgPs.botToken && tgPs.chatId) tgConfigured = true;
26569
+ const dcPs = await settingsManager.loadSettings("@openacp/adapter-discord");
26570
+ if (dcPs.guildId || dcPs.token) dcConfigured = true;
26571
+ }
26219
26572
  console.log(header("Channels"));
26220
- console.log(` Telegram : ${tgEnabled ? ok2("configured") : dim2("not configured")}`);
26221
- console.log(` Discord : ${dcEnabled ? ok2("configured") : dim2("not configured")}`);
26573
+ console.log(` Telegram : ${tgConfigured ? ok2("configured") : dim2("not configured")}`);
26574
+ console.log(` Discord : ${dcConfigured ? ok2("configured") : dim2("not configured")}`);
26222
26575
  console.log("");
26223
26576
  while (true) {
26224
26577
  const choice = await select8({
@@ -26230,7 +26583,7 @@ async function editChannels(config, updates) {
26230
26583
  ]
26231
26584
  });
26232
26585
  if (choice === "back") break;
26233
- if (choice === "telegram") await editTelegram(config, updates);
26586
+ if (choice === "telegram") await editTelegram(config, updates, settingsManager);
26234
26587
  if (choice === "discord") await editDiscord(config, updates);
26235
26588
  }
26236
26589
  }
@@ -26277,8 +26630,18 @@ async function editWorkspace(config, updates) {
26277
26630
  updates.workspace = { baseDir: newDir.trim() };
26278
26631
  console.log(ok2(`Workspace set to ${newDir.trim()}`));
26279
26632
  }
26280
- async function editSecurity(config, updates) {
26281
- const sec = config.security ?? { allowedUserIds: [], maxConcurrentSessions: 20, sessionTimeoutMinutes: 60 };
26633
+ async function editSecurity(config, updates, settingsManager) {
26634
+ let sec = config.security ?? { allowedUserIds: [], maxConcurrentSessions: 20, sessionTimeoutMinutes: 60 };
26635
+ if (settingsManager) {
26636
+ const ps = await settingsManager.loadSettings("@openacp/security");
26637
+ if (Object.keys(ps).length > 0) {
26638
+ sec = {
26639
+ allowedUserIds: ps.allowedUserIds ?? sec.allowedUserIds,
26640
+ maxConcurrentSessions: ps.maxConcurrentSessions ?? sec.maxConcurrentSessions,
26641
+ sessionTimeoutMinutes: ps.sessionTimeoutMinutes ?? sec.sessionTimeoutMinutes
26642
+ };
26643
+ }
26644
+ }
26282
26645
  console.log(header("Security"));
26283
26646
  console.log(` Allowed user IDs : ${sec.allowedUserIds?.length ? sec.allowedUserIds.join(", ") : dim2("(all users allowed)")}`);
26284
26647
  console.log(` Max concurrent sessions : ${sec.maxConcurrentSessions}`);
@@ -26304,8 +26667,12 @@ async function editSecurity(config, updates) {
26304
26667
  return true;
26305
26668
  }
26306
26669
  });
26307
- if (!updates.security) updates.security = {};
26308
- updates.security.maxConcurrentSessions = Number(val.trim());
26670
+ if (settingsManager) {
26671
+ await settingsManager.updatePluginSettings("@openacp/security", { maxConcurrentSessions: Number(val.trim()) });
26672
+ } else {
26673
+ if (!updates.security) updates.security = {};
26674
+ updates.security.maxConcurrentSessions = Number(val.trim());
26675
+ }
26309
26676
  console.log(ok2(`Max concurrent sessions set to ${val.trim()}`));
26310
26677
  }
26311
26678
  if (choice === "timeout") {
@@ -26318,8 +26685,12 @@ async function editSecurity(config, updates) {
26318
26685
  return true;
26319
26686
  }
26320
26687
  });
26321
- if (!updates.security) updates.security = {};
26322
- updates.security.sessionTimeoutMinutes = Number(val.trim());
26688
+ if (settingsManager) {
26689
+ await settingsManager.updatePluginSettings("@openacp/security", { sessionTimeoutMinutes: Number(val.trim()) });
26690
+ } else {
26691
+ if (!updates.security) updates.security = {};
26692
+ updates.security.sessionTimeoutMinutes = Number(val.trim());
26693
+ }
26323
26694
  console.log(ok2(`Session timeout set to ${val.trim()} minutes`));
26324
26695
  }
26325
26696
  }
@@ -26444,8 +26815,14 @@ async function editRunMode(config, updates) {
26444
26815
  }
26445
26816
  }
26446
26817
  }
26447
- async function editApi(config, updates) {
26448
- const api = config.api ?? { port: 21420, host: "127.0.0.1" };
26818
+ async function editApi(config, updates, settingsManager) {
26819
+ let api = config.api ?? { port: 21420, host: "127.0.0.1" };
26820
+ if (settingsManager) {
26821
+ const ps = await settingsManager.loadSettings("@openacp/api-server");
26822
+ if (Object.keys(ps).length > 0) {
26823
+ api = { port: ps.port ?? api.port, host: ps.host ?? api.host };
26824
+ }
26825
+ }
26449
26826
  console.log(header("API"));
26450
26827
  console.log(` Port : ${api.port}`);
26451
26828
  console.log(` Host : ${api.host} ${dim2("(localhost only)")}`);
@@ -26459,11 +26836,21 @@ async function editApi(config, updates) {
26459
26836
  return true;
26460
26837
  }
26461
26838
  });
26462
- updates.api = { port: Number(newPort.trim()) };
26839
+ if (settingsManager) {
26840
+ await settingsManager.updatePluginSettings("@openacp/api-server", { port: Number(newPort.trim()) });
26841
+ } else {
26842
+ updates.api = { port: Number(newPort.trim()) };
26843
+ }
26463
26844
  console.log(ok2(`API port set to ${newPort.trim()}`));
26464
26845
  }
26465
- async function editTunnel(config, updates) {
26466
- const tunnel = config.tunnel ?? { enabled: false, port: 3100, provider: "cloudflare", options: {}, storeTtlMinutes: 60, auth: { enabled: false } };
26846
+ async function editTunnel(config, updates, settingsManager) {
26847
+ let tunnel = config.tunnel ?? { enabled: false, port: 3100, provider: "cloudflare", options: {}, storeTtlMinutes: 60, auth: { enabled: false } };
26848
+ if (settingsManager) {
26849
+ const ps = await settingsManager.loadSettings("@openacp/tunnel");
26850
+ if (Object.keys(ps).length > 0) {
26851
+ tunnel = { ...tunnel, ...ps };
26852
+ }
26853
+ }
26467
26854
  const currentUpdates = updates.tunnel ?? {};
26468
26855
  const getVal = (key, fallback) => key in currentUpdates ? currentUpdates[key] : tunnel[key] ?? fallback;
26469
26856
  console.log(header("Tunnel"));
@@ -26490,6 +26877,9 @@ async function editTunnel(config, updates) {
26490
26877
  const tun = updates.tunnel;
26491
26878
  if (choice === "toggle") {
26492
26879
  const current = getVal("enabled", false);
26880
+ if (settingsManager) {
26881
+ await settingsManager.updatePluginSettings("@openacp/tunnel", { enabled: !current });
26882
+ }
26493
26883
  tun.enabled = !current;
26494
26884
  console.log(!current ? ok2("Tunnel enabled") : ok2("Tunnel disabled"));
26495
26885
  }
@@ -26503,6 +26893,9 @@ async function editTunnel(config, updates) {
26503
26893
  { name: "Tailscale Funnel", value: "tailscale" }
26504
26894
  ]
26505
26895
  });
26896
+ if (settingsManager) {
26897
+ await settingsManager.updatePluginSettings("@openacp/tunnel", { provider, options: {} });
26898
+ }
26506
26899
  tun.provider = provider;
26507
26900
  tun.options = {};
26508
26901
  console.log(ok2(`Provider set to ${provider}`));
@@ -26517,6 +26910,9 @@ async function editTunnel(config, updates) {
26517
26910
  return true;
26518
26911
  }
26519
26912
  });
26913
+ if (settingsManager) {
26914
+ await settingsManager.updatePluginSettings("@openacp/tunnel", { port: Number(val.trim()) });
26915
+ }
26520
26916
  tun.port = Number(val.trim());
26521
26917
  console.log(ok2(`Tunnel port set to ${val.trim()}`));
26522
26918
  }
@@ -26524,11 +26920,17 @@ async function editTunnel(config, updates) {
26524
26920
  const provider = getVal("provider", "cloudflare");
26525
26921
  const currentOptions = getVal("options", {});
26526
26922
  await editProviderOptions(provider, currentOptions, tun);
26923
+ if (settingsManager) {
26924
+ await settingsManager.updatePluginSettings("@openacp/tunnel", { options: tun.options });
26925
+ }
26527
26926
  }
26528
26927
  if (choice === "auth") {
26529
26928
  const currentAuth = getVal("auth", { enabled: false });
26530
26929
  if (currentAuth.enabled) {
26531
26930
  tun.auth = { enabled: false };
26931
+ if (settingsManager) {
26932
+ await settingsManager.updatePluginSettings("@openacp/tunnel", { auth: { enabled: false } });
26933
+ }
26532
26934
  console.log(ok2("Tunnel auth disabled"));
26533
26935
  } else {
26534
26936
  const token = await input({
@@ -26536,6 +26938,9 @@ async function editTunnel(config, updates) {
26536
26938
  default: ""
26537
26939
  });
26538
26940
  tun.auth = token.trim() ? { enabled: true, token: token.trim() } : { enabled: true };
26941
+ if (settingsManager) {
26942
+ await settingsManager.updatePluginSettings("@openacp/tunnel", { auth: tun.auth });
26943
+ }
26539
26944
  console.log(ok2("Tunnel auth enabled"));
26540
26945
  }
26541
26946
  }
@@ -26601,7 +27006,7 @@ async function editProviderOptions(provider, currentOptions, tun) {
26601
27006
  console.log(dim2(`No configurable options for provider "${provider}"`));
26602
27007
  }
26603
27008
  }
26604
- async function runConfigEditor(configManager, mode = "file", apiPort) {
27009
+ async function runConfigEditor(configManager, mode = "file", apiPort, settingsManager) {
26605
27010
  await configManager.load();
26606
27011
  const config = configManager.get();
26607
27012
  const updates = {};
@@ -26636,14 +27041,14 @@ ${c2.cyan}${c2.bold}OpenACP Config Editor${c2.reset}`);
26636
27041
  break;
26637
27042
  }
26638
27043
  const sectionUpdates = {};
26639
- if (choice === "channels") await editChannels(config, sectionUpdates);
27044
+ if (choice === "channels") await editChannels(config, sectionUpdates, settingsManager);
26640
27045
  else if (choice === "agent") await editAgent(config, sectionUpdates);
26641
27046
  else if (choice === "workspace") await editWorkspace(config, sectionUpdates);
26642
- else if (choice === "security") await editSecurity(config, sectionUpdates);
27047
+ else if (choice === "security") await editSecurity(config, sectionUpdates, settingsManager);
26643
27048
  else if (choice === "logging") await editLogging(config, sectionUpdates);
26644
27049
  else if (choice === "runMode") await editRunMode(config, sectionUpdates);
26645
- else if (choice === "api") await editApi(config, sectionUpdates);
26646
- else if (choice === "tunnel") await editTunnel(config, sectionUpdates);
27050
+ else if (choice === "api") await editApi(config, sectionUpdates, settingsManager);
27051
+ else if (choice === "tunnel") await editTunnel(config, sectionUpdates, settingsManager);
26647
27052
  if (mode === "api" && Object.keys(sectionUpdates).length > 0) {
26648
27053
  await sendConfigViaApi(apiPort, sectionUpdates);
26649
27054
  await configManager.load();
@@ -26663,17 +27068,17 @@ ${c2.cyan}${c2.bold}OpenACP Config Editor${c2.reset}`);
26663
27068
  async function sendConfigViaApi(port, updates) {
26664
27069
  const { apiCall: call } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
26665
27070
  const paths = flattenToPaths(updates);
26666
- for (const { path: path67, value } of paths) {
27071
+ for (const { path: path69, value } of paths) {
26667
27072
  const res = await call(port, "/api/config", {
26668
27073
  method: "PATCH",
26669
27074
  headers: { "Content-Type": "application/json" },
26670
- body: JSON.stringify({ path: path67, value })
27075
+ body: JSON.stringify({ path: path69, value })
26671
27076
  });
26672
27077
  const data = await res.json();
26673
27078
  if (!res.ok) {
26674
- console.log(warn2(`Failed to update ${path67}: ${data.error}`));
27079
+ console.log(warn2(`Failed to update ${path69}: ${data.error}`));
26675
27080
  } else if (data.needsRestart) {
26676
- console.log(warn2(`${path67} updated \u2014 restart required`));
27081
+ console.log(warn2(`${path69} updated \u2014 restart required`));
26677
27082
  }
26678
27083
  }
26679
27084
  }
@@ -26782,35 +27187,35 @@ __export(instance_prompt_exports, {
26782
27187
  promptForInstance: () => promptForInstance
26783
27188
  });
26784
27189
  import fs56 from "fs";
26785
- import path65 from "path";
26786
- import os31 from "os";
27190
+ import path67 from "path";
27191
+ import os32 from "os";
26787
27192
  async function promptForInstance(opts) {
26788
27193
  const globalRoot = getGlobalRoot();
26789
- const globalConfigExists = fs56.existsSync(path65.join(globalRoot, "config.json"));
27194
+ const globalConfigExists = fs56.existsSync(path67.join(globalRoot, "config.json"));
26790
27195
  const cwd = process.cwd();
26791
- const localRoot = path65.join(cwd, ".openacp");
27196
+ const localRoot = path67.join(cwd, ".openacp");
26792
27197
  if (!globalConfigExists) return globalRoot;
26793
27198
  const isTTY = process.stdin.isTTY && process.stdout.isTTY;
26794
27199
  if (!isTTY) return globalRoot;
26795
- const registryPath = path65.join(globalRoot, "instances.json");
27200
+ const registryPath = path67.join(globalRoot, "instances.json");
26796
27201
  const registry = new InstanceRegistry(registryPath);
26797
27202
  registry.load();
26798
27203
  const instances = registry.list().filter((e) => fs56.existsSync(e.root));
26799
27204
  const instanceOptions = instances.map((e) => {
26800
27205
  let name = e.id;
26801
27206
  try {
26802
- const raw = fs56.readFileSync(path65.join(e.root, "config.json"), "utf-8");
27207
+ const raw = fs56.readFileSync(path67.join(e.root, "config.json"), "utf-8");
26803
27208
  const parsed = JSON.parse(raw);
26804
27209
  if (parsed.instanceName) name = parsed.instanceName;
26805
27210
  } catch {
26806
27211
  }
26807
27212
  const isGlobal = e.root === globalRoot;
26808
- const displayPath = e.root.replace(os31.homedir(), "~");
27213
+ const displayPath = e.root.replace(os32.homedir(), "~");
26809
27214
  const type = isGlobal ? "global" : "local";
26810
27215
  return { value: e.root, label: `${name} workspace (${type} \u2014 ${displayPath})` };
26811
27216
  });
26812
27217
  if (instanceOptions.length === 0) {
26813
- const globalDisplay = globalRoot.replace(os31.homedir(), "~");
27218
+ const globalDisplay = globalRoot.replace(os32.homedir(), "~");
26814
27219
  instanceOptions.push({ value: globalRoot, label: `Global workspace (${globalDisplay})` });
26815
27220
  }
26816
27221
  if (instanceOptions.length === 1 && !opts.allowCreate) {
@@ -26821,7 +27226,7 @@ async function promptForInstance(opts) {
26821
27226
  label: o.label
26822
27227
  }));
26823
27228
  if (opts.allowCreate) {
26824
- const localDisplay = localRoot.replace(os31.homedir(), "~");
27229
+ const localDisplay = localRoot.replace(os32.homedir(), "~");
26825
27230
  options.push({ value: localRoot, label: `New local workspace (${localDisplay})` });
26826
27231
  }
26827
27232
  const clack10 = await import("@clack/prompts");
@@ -26847,7 +27252,7 @@ var init_instance_prompt = __esm({
26847
27252
 
26848
27253
  // src/cli.ts
26849
27254
  import { setDefaultAutoSelectFamily } from "net";
26850
- import path66 from "path";
27255
+ import path68 from "path";
26851
27256
 
26852
27257
  // src/cli/commands/help.ts
26853
27258
  function printHelp() {
@@ -27086,11 +27491,11 @@ Shows all plugins registered in the plugin registry.
27086
27491
  `);
27087
27492
  return;
27088
27493
  }
27089
- const os32 = await import("os");
27090
- const path67 = await import("path");
27494
+ const os33 = await import("os");
27495
+ const path69 = await import("path");
27091
27496
  const { PluginRegistry: PluginRegistry2 } = await Promise.resolve().then(() => (init_plugin_registry(), plugin_registry_exports));
27092
- const root = instanceRoot ?? path67.join(os32.homedir(), ".openacp");
27093
- const registryPath = path67.join(root, "plugins.json");
27497
+ const root = instanceRoot ?? path69.join(os33.homedir(), ".openacp");
27498
+ const registryPath = path69.join(root, "plugins.json");
27094
27499
  const registry = new PluginRegistry2(registryPath);
27095
27500
  await registry.load();
27096
27501
  const plugins = registry.list();
@@ -27225,11 +27630,11 @@ async function cmdPlugin(args2 = [], instanceRoot) {
27225
27630
  }
27226
27631
  async function setPluginEnabled(name, enabled, instanceRoot, json = false) {
27227
27632
  if (json) await muteForJson();
27228
- const os32 = await import("os");
27229
- const path67 = await import("path");
27633
+ const os33 = await import("os");
27634
+ const path69 = await import("path");
27230
27635
  const { PluginRegistry: PluginRegistry2 } = await Promise.resolve().then(() => (init_plugin_registry(), plugin_registry_exports));
27231
- const root = instanceRoot ?? path67.join(os32.homedir(), ".openacp");
27232
- const registryPath = path67.join(root, "plugins.json");
27636
+ const root = instanceRoot ?? path69.join(os33.homedir(), ".openacp");
27637
+ const registryPath = path69.join(root, "plugins.json");
27233
27638
  const registry = new PluginRegistry2(registryPath);
27234
27639
  await registry.load();
27235
27640
  const entry = registry.get(name);
@@ -27244,8 +27649,8 @@ async function setPluginEnabled(name, enabled, instanceRoot, json = false) {
27244
27649
  console.log(`Plugin ${name} ${enabled ? "enabled" : "disabled"}. Restart to apply.`);
27245
27650
  }
27246
27651
  async function configurePlugin(name, instanceRoot) {
27247
- const os32 = await import("os");
27248
- const path67 = await import("path");
27652
+ const os33 = await import("os");
27653
+ const path69 = await import("path");
27249
27654
  const { corePlugins: corePlugins2 } = await Promise.resolve().then(() => (init_core_plugins(), core_plugins_exports));
27250
27655
  const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
27251
27656
  const { createInstallContext: createInstallContext2 } = await Promise.resolve().then(() => (init_install_context(), install_context_exports));
@@ -27254,8 +27659,8 @@ async function configurePlugin(name, instanceRoot) {
27254
27659
  console.error(`Plugin "${name}" not found.`);
27255
27660
  process.exit(1);
27256
27661
  }
27257
- const root = instanceRoot ?? path67.join(os32.homedir(), ".openacp");
27258
- const basePath = path67.join(root, "plugins", "data");
27662
+ const root = instanceRoot ?? path69.join(os33.homedir(), ".openacp");
27663
+ const basePath = path69.join(root, "plugins", "data");
27259
27664
  const settingsManager = new SettingsManager2(basePath);
27260
27665
  const ctx = createInstallContext2({ pluginName: name, settingsManager, basePath });
27261
27666
  if (plugin2.configure) {
@@ -27268,14 +27673,14 @@ async function configurePlugin(name, instanceRoot) {
27268
27673
  }
27269
27674
  async function installPlugin(input2, instanceRoot, json = false) {
27270
27675
  if (json) await muteForJson();
27271
- const os32 = await import("os");
27272
- const path67 = await import("path");
27676
+ const os33 = await import("os");
27677
+ const path69 = await import("path");
27273
27678
  const { execFileSync: execFileSync8 } = await import("child_process");
27274
27679
  const { getCurrentVersion: getCurrentVersion2 } = await Promise.resolve().then(() => (init_version(), version_exports));
27275
27680
  const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
27276
27681
  const { createInstallContext: createInstallContext2 } = await Promise.resolve().then(() => (init_install_context(), install_context_exports));
27277
27682
  const { PluginRegistry: PluginRegistry2 } = await Promise.resolve().then(() => (init_plugin_registry(), plugin_registry_exports));
27278
- const root = instanceRoot ?? path67.join(os32.homedir(), ".openacp");
27683
+ const root = instanceRoot ?? path69.join(os33.homedir(), ".openacp");
27279
27684
  let pkgName;
27280
27685
  let pkgVersion;
27281
27686
  if (input2.startsWith("@")) {
@@ -27320,9 +27725,9 @@ async function installPlugin(input2, instanceRoot, json = false) {
27320
27725
  if (!json) console.log(`Installing ${installSpec}...`);
27321
27726
  const { corePlugins: corePlugins2 } = await Promise.resolve().then(() => (init_core_plugins(), core_plugins_exports));
27322
27727
  const builtinPlugin = corePlugins2.find((p2) => p2.name === pkgName);
27323
- const basePath = path67.join(root, "plugins", "data");
27728
+ const basePath = path69.join(root, "plugins", "data");
27324
27729
  const settingsManager = new SettingsManager2(basePath);
27325
- const registryPath = path67.join(root, "plugins.json");
27730
+ const registryPath = path69.join(root, "plugins.json");
27326
27731
  const pluginRegistry = new PluginRegistry2(registryPath);
27327
27732
  await pluginRegistry.load();
27328
27733
  if (builtinPlugin) {
@@ -27342,8 +27747,8 @@ async function installPlugin(input2, instanceRoot, json = false) {
27342
27747
  console.log(`\u2713 ${builtinPlugin.name} installed! Restart to activate.`);
27343
27748
  return;
27344
27749
  }
27345
- const pluginsDir = path67.join(root, "plugins");
27346
- const nodeModulesDir = path67.join(pluginsDir, "node_modules");
27750
+ const pluginsDir = path69.join(root, "plugins");
27751
+ const nodeModulesDir = path69.join(pluginsDir, "node_modules");
27347
27752
  try {
27348
27753
  execFileSync8("npm", ["install", installSpec, "--prefix", pluginsDir, "--save"], {
27349
27754
  stdio: json ? "pipe" : "inherit",
@@ -27357,8 +27762,8 @@ async function installPlugin(input2, instanceRoot, json = false) {
27357
27762
  const cliVersion = getCurrentVersion2();
27358
27763
  const isLocalPath = pkgName.startsWith("/") || pkgName.startsWith(".");
27359
27764
  try {
27360
- const pluginRoot = isLocalPath ? path67.resolve(pkgName) : path67.join(nodeModulesDir, pkgName);
27361
- const installedPkgPath = path67.join(pluginRoot, "package.json");
27765
+ const pluginRoot = isLocalPath ? path69.resolve(pkgName) : path69.join(nodeModulesDir, pkgName);
27766
+ const installedPkgPath = path69.join(pluginRoot, "package.json");
27362
27767
  const { readFileSync: readFileSync19 } = await import("fs");
27363
27768
  const installedPkg = JSON.parse(readFileSync19(installedPkgPath, "utf-8"));
27364
27769
  const minVersion = installedPkg.engines?.openacp?.replace(/[>=^~\s]/g, "");
@@ -27373,7 +27778,7 @@ async function installPlugin(input2, instanceRoot, json = false) {
27373
27778
  }
27374
27779
  }
27375
27780
  }
27376
- const pluginModule = await import(path67.join(pluginRoot, installedPkg.main ?? "dist/index.js"));
27781
+ const pluginModule = await import(path69.join(pluginRoot, installedPkg.main ?? "dist/index.js"));
27377
27782
  const plugin2 = pluginModule.default;
27378
27783
  if (plugin2?.install) {
27379
27784
  const ctx = createInstallContext2({ pluginName: plugin2.name ?? pkgName, settingsManager, basePath });
@@ -27403,12 +27808,12 @@ async function installPlugin(input2, instanceRoot, json = false) {
27403
27808
  }
27404
27809
  async function uninstallPlugin(name, purge, instanceRoot, json = false) {
27405
27810
  if (json) await muteForJson();
27406
- const os32 = await import("os");
27407
- const path67 = await import("path");
27811
+ const os33 = await import("os");
27812
+ const path69 = await import("path");
27408
27813
  const fs57 = await import("fs");
27409
27814
  const { PluginRegistry: PluginRegistry2 } = await Promise.resolve().then(() => (init_plugin_registry(), plugin_registry_exports));
27410
- const root = instanceRoot ?? path67.join(os32.homedir(), ".openacp");
27411
- const registryPath = path67.join(root, "plugins.json");
27815
+ const root = instanceRoot ?? path69.join(os33.homedir(), ".openacp");
27816
+ const registryPath = path69.join(root, "plugins.json");
27412
27817
  const registry = new PluginRegistry2(registryPath);
27413
27818
  await registry.load();
27414
27819
  const entry = registry.get(name);
@@ -27428,7 +27833,7 @@ async function uninstallPlugin(name, purge, instanceRoot, json = false) {
27428
27833
  if (plugin2?.uninstall) {
27429
27834
  const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
27430
27835
  const { createInstallContext: createInstallContext2 } = await Promise.resolve().then(() => (init_install_context(), install_context_exports));
27431
- const basePath = path67.join(root, "plugins", "data");
27836
+ const basePath = path69.join(root, "plugins", "data");
27432
27837
  const settingsManager = new SettingsManager2(basePath);
27433
27838
  const ctx = createInstallContext2({ pluginName: name, settingsManager, basePath });
27434
27839
  await plugin2.uninstall(ctx, { purge });
@@ -27436,7 +27841,7 @@ async function uninstallPlugin(name, purge, instanceRoot, json = false) {
27436
27841
  } catch {
27437
27842
  }
27438
27843
  if (purge) {
27439
- const pluginDir = path67.join(root, "plugins", name);
27844
+ const pluginDir = path69.join(root, "plugins", name);
27440
27845
  fs57.rmSync(pluginDir, { recursive: true, force: true });
27441
27846
  }
27442
27847
  registry.remove(name);
@@ -28266,13 +28671,13 @@ init_version();
28266
28671
  init_helpers();
28267
28672
  init_output();
28268
28673
  init_instance_hint();
28269
- import path37 from "path";
28270
- import os16 from "os";
28674
+ import path39 from "path";
28675
+ import os17 from "os";
28271
28676
  import fs36 from "fs";
28272
28677
  async function cmdStart(args2 = [], instanceRoot) {
28273
28678
  const json = isJsonMode(args2);
28274
28679
  if (json) await muteForJson();
28275
- const root = instanceRoot ?? path37.join(os16.homedir(), ".openacp");
28680
+ const root = instanceRoot ?? path39.join(os17.homedir(), ".openacp");
28276
28681
  if (!json && wantsHelp(args2)) {
28277
28682
  console.log(`
28278
28683
  \x1B[1mopenacp start\x1B[0m \u2014 Start OpenACP as a background daemon
@@ -28298,7 +28703,7 @@ Requires an existing config \u2014 run 'openacp' first to set up.
28298
28703
  await checkAndPromptUpdate();
28299
28704
  const { startDaemon: startDaemon2, getPidPath: getPidPath2 } = await Promise.resolve().then(() => (init_daemon2(), daemon_exports));
28300
28705
  const { ConfigManager: ConfigManager2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
28301
- const cm = new ConfigManager2(path37.join(root, "config.json"));
28706
+ const cm = new ConfigManager2(path39.join(root, "config.json"));
28302
28707
  if (await cm.exists()) {
28303
28708
  await cm.load();
28304
28709
  const config = cm.get();
@@ -28309,11 +28714,11 @@ Requires an existing config \u2014 run 'openacp' first to set up.
28309
28714
  process.exit(1);
28310
28715
  }
28311
28716
  if (json) {
28312
- let instanceId = path37.basename(root);
28717
+ let instanceId = path39.basename(root);
28313
28718
  try {
28314
28719
  const { getGlobalRoot: getGlobalRoot2 } = await Promise.resolve().then(() => (init_instance_context(), instance_context_exports));
28315
28720
  const { InstanceRegistry: InstanceRegistry2 } = await Promise.resolve().then(() => (init_instance_registry(), instance_registry_exports));
28316
- const reg = new InstanceRegistry2(path37.join(getGlobalRoot2(), "instances.json"));
28721
+ const reg = new InstanceRegistry2(path39.join(getGlobalRoot2(), "instances.json"));
28317
28722
  reg.load();
28318
28723
  const entry = reg.getByRoot(root);
28319
28724
  if (entry) instanceId = entry.id;
@@ -28321,7 +28726,7 @@ Requires an existing config \u2014 run 'openacp' first to set up.
28321
28726
  }
28322
28727
  let port = null;
28323
28728
  try {
28324
- const portStr = fs36.readFileSync(path37.join(root, "api.port"), "utf-8").trim();
28729
+ const portStr = fs36.readFileSync(path39.join(root, "api.port"), "utf-8").trim();
28325
28730
  port = parseInt(portStr) || null;
28326
28731
  } catch {
28327
28732
  port = config.api.port ?? null;
@@ -28330,7 +28735,7 @@ Requires an existing config \u2014 run 'openacp' first to set up.
28330
28735
  pid: result.pid,
28331
28736
  instanceId,
28332
28737
  name: config.instanceName ?? null,
28333
- directory: path37.dirname(root),
28738
+ directory: path39.dirname(root),
28334
28739
  dir: root,
28335
28740
  port
28336
28741
  });
@@ -28467,16 +28872,20 @@ the API for live updates. When stopped, edits config file directly.
28467
28872
  }
28468
28873
  const { runConfigEditor: runConfigEditor2 } = await Promise.resolve().then(() => (init_config_editor(), config_editor_exports));
28469
28874
  const { ConfigManager: ConfigManager2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
28875
+ const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
28876
+ const { getGlobalRoot: getGlobalRoot2 } = await Promise.resolve().then(() => (init_instance_context(), instance_context_exports));
28470
28877
  const cm = new ConfigManager2(instanceRoot ? pathMod.join(instanceRoot, "config.json") : void 0);
28471
28878
  if (!await cm.exists()) {
28472
28879
  console.error('No config found. Run "openacp" first to set up.');
28473
28880
  process.exit(1);
28474
28881
  }
28882
+ const root = instanceRoot ?? getGlobalRoot2();
28883
+ const settingsManager = new SettingsManager2(pathMod.join(root, "plugins", "data"));
28475
28884
  const port = readApiPort(void 0, instanceRoot);
28476
28885
  if (port !== null) {
28477
- await runConfigEditor2(cm, "api", port);
28886
+ await runConfigEditor2(cm, "api", port, settingsManager);
28478
28887
  } else {
28479
- await runConfigEditor2(cm, "file");
28888
+ await runConfigEditor2(cm, "file", void 0, settingsManager);
28480
28889
  }
28481
28890
  }
28482
28891
 
@@ -28497,9 +28906,9 @@ start fresh with the setup wizard. The daemon must be stopped first.
28497
28906
  `);
28498
28907
  return;
28499
28908
  }
28500
- const os32 = await import("os");
28501
- const path67 = await import("path");
28502
- const root = instanceRoot ?? path67.join(os32.homedir(), ".openacp");
28909
+ const os33 = await import("os");
28910
+ const path69 = await import("path");
28911
+ const root = instanceRoot ?? path69.join(os33.homedir(), ".openacp");
28503
28912
  const { getStatus: getStatus2, getPidPath: getPidPath2 } = await Promise.resolve().then(() => (init_daemon2(), daemon_exports));
28504
28913
  const status = getStatus2(getPidPath2(root));
28505
28914
  if (status.running) {
@@ -28657,11 +29066,11 @@ init_instance_context();
28657
29066
  init_status();
28658
29067
  init_output();
28659
29068
  init_helpers();
28660
- import path58 from "path";
28661
- import os27 from "os";
29069
+ import path60 from "path";
29070
+ import os28 from "os";
28662
29071
  import fs52 from "fs";
28663
29072
  async function buildInstanceListEntries() {
28664
- const registryPath = path58.join(getGlobalRoot(), "instances.json");
29073
+ const registryPath = path60.join(getGlobalRoot(), "instances.json");
28665
29074
  const registry = new InstanceRegistry(registryPath);
28666
29075
  registry.load();
28667
29076
  return registry.list().map((entry) => {
@@ -28669,7 +29078,7 @@ async function buildInstanceListEntries() {
28669
29078
  return {
28670
29079
  id: entry.id,
28671
29080
  name: info.name,
28672
- directory: path58.dirname(entry.root),
29081
+ directory: path60.dirname(entry.root),
28673
29082
  root: entry.root,
28674
29083
  status: info.pid ? "running" : "stopped",
28675
29084
  port: info.apiPort
@@ -28719,7 +29128,7 @@ async function cmdInstancesList(args2) {
28719
29128
  for (const e of entries) {
28720
29129
  const status = e.status === "running" ? "\u25CF running" : "\u25CB stopped";
28721
29130
  const port = e.port ? `:${e.port}` : "\u2014";
28722
- const dir = e.directory.replace(os27.homedir(), "~");
29131
+ const dir = e.directory.replace(os28.homedir(), "~");
28723
29132
  const name = (e.name ?? e.id).padEnd(16);
28724
29133
  console.log(` ${status.padEnd(10)} ${e.id.padEnd(16)} ${name} ${dir} ${port}`);
28725
29134
  }
@@ -28742,9 +29151,9 @@ async function cmdInstancesCreate(args2) {
28742
29151
  const agentIdx = args2.indexOf("--agent");
28743
29152
  const agent = agentIdx !== -1 ? args2[agentIdx + 1] : void 0;
28744
29153
  const noInteractive = args2.includes("--no-interactive");
28745
- const resolvedDir = path58.resolve(rawDir.replace(/^~/, os27.homedir()));
28746
- const instanceRoot = path58.join(resolvedDir, ".openacp");
28747
- const registryPath = path58.join(getGlobalRoot(), "instances.json");
29154
+ const resolvedDir = path60.resolve(rawDir.replace(/^~/, os28.homedir()));
29155
+ const instanceRoot = path60.join(resolvedDir, ".openacp");
29156
+ const registryPath = path60.join(getGlobalRoot(), "instances.json");
28748
29157
  const registry = new InstanceRegistry(registryPath);
28749
29158
  registry.load();
28750
29159
  if (fs52.existsSync(instanceRoot)) {
@@ -28754,8 +29163,8 @@ async function cmdInstancesCreate(args2) {
28754
29163
  console.error(`Error: Instance already exists at ${resolvedDir} (id: ${existing.id})`);
28755
29164
  process.exit(1);
28756
29165
  }
28757
- const configPath = path58.join(instanceRoot, "config.json");
28758
- let name2 = path58.basename(resolvedDir);
29166
+ const configPath = path60.join(instanceRoot, "config.json");
29167
+ let name2 = path60.basename(resolvedDir);
28759
29168
  try {
28760
29169
  const config = JSON.parse(fs52.readFileSync(configPath, "utf-8"));
28761
29170
  name2 = config.instanceName ?? name2;
@@ -28770,15 +29179,15 @@ async function cmdInstancesCreate(args2) {
28770
29179
  const name = instanceName ?? `openacp-${registry.list().length + 1}`;
28771
29180
  const id = registry.uniqueId(generateSlug(name));
28772
29181
  if (rawFrom) {
28773
- const fromRoot = path58.join(path58.resolve(rawFrom.replace(/^~/, os27.homedir())), ".openacp");
28774
- if (!fs52.existsSync(path58.join(fromRoot, "config.json"))) {
29182
+ const fromRoot = path60.join(path60.resolve(rawFrom.replace(/^~/, os28.homedir())), ".openacp");
29183
+ if (!fs52.existsSync(path60.join(fromRoot, "config.json"))) {
28775
29184
  console.error(`Error: No OpenACP instance found at ${rawFrom}`);
28776
29185
  process.exit(1);
28777
29186
  }
28778
29187
  fs52.mkdirSync(instanceRoot, { recursive: true });
28779
29188
  const { copyInstance: copyInstance2 } = await Promise.resolve().then(() => (init_instance_copy(), instance_copy_exports));
28780
29189
  await copyInstance2(fromRoot, instanceRoot, {});
28781
- const configPath = path58.join(instanceRoot, "config.json");
29190
+ const configPath = path60.join(instanceRoot, "config.json");
28782
29191
  try {
28783
29192
  const config = JSON.parse(fs52.readFileSync(configPath, "utf-8"));
28784
29193
  config.instanceName = name;
@@ -28789,14 +29198,14 @@ async function cmdInstancesCreate(args2) {
28789
29198
  fs52.mkdirSync(instanceRoot, { recursive: true });
28790
29199
  const config = { instanceName: name, runMode: "daemon" };
28791
29200
  if (agent) config.defaultAgent = agent;
28792
- fs52.writeFileSync(path58.join(instanceRoot, "config.json"), JSON.stringify(config, null, 2));
28793
- fs52.writeFileSync(path58.join(instanceRoot, "plugins.json"), JSON.stringify({ version: 1, installed: {} }, null, 2));
29201
+ fs52.writeFileSync(path60.join(instanceRoot, "config.json"), JSON.stringify(config, null, 2));
29202
+ fs52.writeFileSync(path60.join(instanceRoot, "plugins.json"), JSON.stringify({ version: 1, installed: {} }, null, 2));
28794
29203
  } else {
28795
29204
  fs52.mkdirSync(instanceRoot, { recursive: true });
28796
29205
  const config = { instanceName: name, runMode: "daemon" };
28797
29206
  if (agent) config.defaultAgent = agent;
28798
- fs52.writeFileSync(path58.join(instanceRoot, "config.json"), JSON.stringify(config, null, 2));
28799
- fs52.writeFileSync(path58.join(instanceRoot, "plugins.json"), JSON.stringify({ version: 1, installed: {} }, null, 2));
29207
+ fs52.writeFileSync(path60.join(instanceRoot, "config.json"), JSON.stringify(config, null, 2));
29208
+ fs52.writeFileSync(path60.join(instanceRoot, "plugins.json"), JSON.stringify({ version: 1, installed: {} }, null, 2));
28800
29209
  console.log(`Instance created at ${resolvedDir}. Run 'openacp setup' inside that directory to configure it.`);
28801
29210
  }
28802
29211
  registry.register(id, instanceRoot);
@@ -28808,7 +29217,7 @@ async function outputInstance(json, { id, root }) {
28808
29217
  const entry = {
28809
29218
  id,
28810
29219
  name: info.name,
28811
- directory: path58.dirname(root),
29220
+ directory: path60.dirname(root),
28812
29221
  root,
28813
29222
  status: info.pid ? "running" : "stopped",
28814
29223
  port: info.apiPort
@@ -28817,7 +29226,7 @@ async function outputInstance(json, { id, root }) {
28817
29226
  jsonSuccess(entry);
28818
29227
  return;
28819
29228
  }
28820
- console.log(`Instance created: ${info.name ?? id} at ${path58.dirname(root)}`);
29229
+ console.log(`Instance created: ${info.name ?? id} at ${path60.dirname(root)}`);
28821
29230
  }
28822
29231
 
28823
29232
  // src/cli/commands/integrate.ts
@@ -29580,22 +29989,22 @@ Options:
29580
29989
  }
29581
29990
 
29582
29991
  // src/cli/commands/onboard.ts
29583
- import * as path59 from "path";
29992
+ import * as path61 from "path";
29584
29993
  async function cmdOnboard(instanceRoot) {
29585
29994
  const { ConfigManager: ConfigManager2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
29586
29995
  const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
29587
29996
  const { PluginRegistry: PluginRegistry2 } = await Promise.resolve().then(() => (init_plugin_registry(), plugin_registry_exports));
29588
29997
  const { getGlobalRoot: getGlobalRoot2 } = await Promise.resolve().then(() => (init_instance_context(), instance_context_exports));
29589
29998
  const OPENACP_DIR = instanceRoot ?? getGlobalRoot2();
29590
- const PLUGINS_DATA_DIR = path59.join(OPENACP_DIR, "plugins", "data");
29591
- const REGISTRY_PATH = path59.join(OPENACP_DIR, "plugins.json");
29592
- const cm = new ConfigManager2(path59.join(OPENACP_DIR, "config.json"));
29999
+ const PLUGINS_DATA_DIR = path61.join(OPENACP_DIR, "plugins", "data");
30000
+ const REGISTRY_PATH = path61.join(OPENACP_DIR, "plugins.json");
30001
+ const cm = new ConfigManager2(path61.join(OPENACP_DIR, "config.json"));
29593
30002
  const settingsManager = new SettingsManager2(PLUGINS_DATA_DIR);
29594
30003
  const pluginRegistry = new PluginRegistry2(REGISTRY_PATH);
29595
30004
  await pluginRegistry.load();
29596
30005
  if (await cm.exists()) {
29597
30006
  const { runReconfigure: runReconfigure2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
29598
- await runReconfigure2(cm);
30007
+ await runReconfigure2(cm, settingsManager);
29599
30008
  } else {
29600
30009
  const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
29601
30010
  await runSetup2(cm, { skipRunMode: true, settingsManager, pluginRegistry, instanceRoot: OPENACP_DIR });
@@ -29608,17 +30017,17 @@ init_instance_context();
29608
30017
  init_instance_registry();
29609
30018
  init_instance_hint();
29610
30019
  init_output();
29611
- import path60 from "path";
29612
- import os28 from "os";
30020
+ import path62 from "path";
30021
+ import os29 from "os";
29613
30022
  import fs53 from "fs";
29614
30023
  import { randomUUID as randomUUID5 } from "crypto";
29615
30024
  async function cmdDefault(command2, instanceRoot) {
29616
30025
  const args2 = command2 ? [command2] : [];
29617
30026
  const json = isJsonMode(args2);
29618
30027
  if (json) await muteForJson();
29619
- const root = instanceRoot ?? path60.join(os28.homedir(), ".openacp");
29620
- const pluginsDataDir = path60.join(root, "plugins", "data");
29621
- const registryPath = path60.join(root, "plugins.json");
30028
+ const root = instanceRoot ?? path62.join(os29.homedir(), ".openacp");
30029
+ const pluginsDataDir = path62.join(root, "plugins", "data");
30030
+ const registryPath = path62.join(root, "plugins.json");
29622
30031
  const forceForeground = command2 === "--foreground";
29623
30032
  if (command2 && !command2.startsWith("-")) {
29624
30033
  const { suggestMatch: suggestMatch2 } = await Promise.resolve().then(() => (init_suggest(), suggest_exports));
@@ -29650,7 +30059,7 @@ async function cmdDefault(command2, instanceRoot) {
29650
30059
  }
29651
30060
  await checkAndPromptUpdate();
29652
30061
  const { ConfigManager: ConfigManager2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
29653
- const configPath = path60.join(root, "config.json");
30062
+ const configPath = path62.join(root, "config.json");
29654
30063
  const cm = new ConfigManager2(configPath);
29655
30064
  if (!await cm.exists()) {
29656
30065
  const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
@@ -29678,9 +30087,9 @@ async function cmdDefault(command2, instanceRoot) {
29678
30087
  process.exit(1);
29679
30088
  }
29680
30089
  if (json) {
29681
- let instanceId2 = path60.basename(root);
30090
+ let instanceId2 = path62.basename(root);
29682
30091
  try {
29683
- const reg2 = new InstanceRegistry(path60.join(getGlobalRoot(), "instances.json"));
30092
+ const reg2 = new InstanceRegistry(path62.join(getGlobalRoot(), "instances.json"));
29684
30093
  reg2.load();
29685
30094
  const entry = reg2.getByRoot(root);
29686
30095
  if (entry) instanceId2 = entry.id;
@@ -29688,7 +30097,7 @@ async function cmdDefault(command2, instanceRoot) {
29688
30097
  }
29689
30098
  let port = null;
29690
30099
  try {
29691
- const portStr = fs53.readFileSync(path60.join(root, "api.port"), "utf-8").trim();
30100
+ const portStr = fs53.readFileSync(path62.join(root, "api.port"), "utf-8").trim();
29692
30101
  port = parseInt(portStr) || null;
29693
30102
  } catch {
29694
30103
  port = config.api.port ?? null;
@@ -29697,7 +30106,7 @@ async function cmdDefault(command2, instanceRoot) {
29697
30106
  pid: result.pid,
29698
30107
  instanceId: instanceId2,
29699
30108
  name: config.instanceName ?? null,
29700
- directory: path60.dirname(root),
30109
+ directory: path62.dirname(root),
29701
30110
  dir: root,
29702
30111
  port
29703
30112
  });
@@ -29710,7 +30119,7 @@ async function cmdDefault(command2, instanceRoot) {
29710
30119
  markRunning2(root);
29711
30120
  printInstanceHint(root);
29712
30121
  const { startServer: startServer2 } = await Promise.resolve().then(() => (init_main(), main_exports));
29713
- const reg = new InstanceRegistry(path60.join(getGlobalRoot(), "instances.json"));
30122
+ const reg = new InstanceRegistry(path62.join(getGlobalRoot(), "instances.json"));
29714
30123
  reg.load();
29715
30124
  const existingEntry = reg.getByRoot(root);
29716
30125
  const instanceId = existingEntry?.id ?? randomUUID5();
@@ -29722,7 +30131,7 @@ async function cmdDefault(command2, instanceRoot) {
29722
30131
  if (json) {
29723
30132
  let port = null;
29724
30133
  try {
29725
- const portStr = fs53.readFileSync(path60.join(root, "api.port"), "utf-8").trim();
30134
+ const portStr = fs53.readFileSync(path62.join(root, "api.port"), "utf-8").trim();
29726
30135
  port = parseInt(portStr) || null;
29727
30136
  } catch {
29728
30137
  port = config.api.port ?? null;
@@ -29731,7 +30140,7 @@ async function cmdDefault(command2, instanceRoot) {
29731
30140
  pid: process.pid,
29732
30141
  instanceId,
29733
30142
  name: config.instanceName ?? null,
29734
- directory: path60.dirname(root),
30143
+ directory: path62.dirname(root),
29735
30144
  dir: root,
29736
30145
  port
29737
30146
  });
@@ -29800,7 +30209,7 @@ async function showAlreadyRunningMenu(root) {
29800
30209
  // src/cli/commands/dev.ts
29801
30210
  init_helpers();
29802
30211
  import fs54 from "fs";
29803
- import path61 from "path";
30212
+ import path63 from "path";
29804
30213
  async function cmdDev(args2 = []) {
29805
30214
  if (wantsHelp(args2)) {
29806
30215
  console.log(`
@@ -29827,12 +30236,12 @@ async function cmdDev(args2 = []) {
29827
30236
  console.error("Error: missing plugin path. Usage: openacp dev <plugin-path>");
29828
30237
  process.exit(1);
29829
30238
  }
29830
- const pluginPath = path61.resolve(pluginPathArg);
30239
+ const pluginPath = path63.resolve(pluginPathArg);
29831
30240
  if (!fs54.existsSync(pluginPath)) {
29832
30241
  console.error(`Error: plugin path does not exist: ${pluginPath}`);
29833
30242
  process.exit(1);
29834
30243
  }
29835
- const tsconfigPath = path61.join(pluginPath, "tsconfig.json");
30244
+ const tsconfigPath = path63.join(pluginPath, "tsconfig.json");
29836
30245
  const hasTsconfig = fs54.existsSync(tsconfigPath);
29837
30246
  if (hasTsconfig) {
29838
30247
  console.log("Compiling plugin TypeScript...");
@@ -29874,10 +30283,10 @@ async function cmdDev(args2 = []) {
29874
30283
 
29875
30284
  // src/cli/commands/attach.ts
29876
30285
  init_helpers();
29877
- import path62 from "path";
29878
- import os29 from "os";
30286
+ import path64 from "path";
30287
+ import os30 from "os";
29879
30288
  async function cmdAttach(args2 = [], instanceRoot) {
29880
- const root = instanceRoot ?? path62.join(os29.homedir(), ".openacp");
30289
+ const root = instanceRoot ?? path64.join(os30.homedir(), ".openacp");
29881
30290
  if (wantsHelp(args2)) {
29882
30291
  console.log(`
29883
30292
  \x1B[1mopenacp attach\x1B[0m \u2014 Attach to a running daemon
@@ -29911,15 +30320,15 @@ Press Ctrl+C to detach.
29911
30320
  console.log("");
29912
30321
  const { spawn: spawn9 } = await import("child_process");
29913
30322
  const { expandHome: expandHome4 } = await Promise.resolve().then(() => (init_config2(), config_exports));
29914
- let logDir2 = path62.join(root, "logs");
30323
+ let logDir2 = path64.join(root, "logs");
29915
30324
  try {
29916
- const configPath = path62.join(root, "config.json");
30325
+ const configPath = path64.join(root, "config.json");
29917
30326
  const { readFileSync: readFileSync19 } = await import("fs");
29918
30327
  const config = JSON.parse(readFileSync19(configPath, "utf-8"));
29919
30328
  if (config.logging?.logDir) logDir2 = expandHome4(config.logging.logDir);
29920
30329
  } catch {
29921
30330
  }
29922
- const logFile = path62.join(logDir2, "openacp.log");
30331
+ const logFile = path64.join(logDir2, "openacp.log");
29923
30332
  const tail = spawn9("tail", ["-f", "-n", "50", logFile], { stdio: "inherit" });
29924
30333
  tail.on("error", (err) => {
29925
30334
  console.error(`Cannot tail log file: ${err.message}`);
@@ -29931,8 +30340,8 @@ Press Ctrl+C to detach.
29931
30340
  init_api_client();
29932
30341
  init_instance_registry();
29933
30342
  init_output();
29934
- import path63 from "path";
29935
- import os30 from "os";
30343
+ import path65 from "path";
30344
+ import os31 from "os";
29936
30345
  import qrcode from "qrcode-terminal";
29937
30346
  async function cmdRemote(args2, instanceRoot) {
29938
30347
  const json = isJsonMode(args2);
@@ -29947,7 +30356,7 @@ async function cmdRemote(args2, instanceRoot) {
29947
30356
  const scopes = scopesRaw ? scopesRaw.split(",").map((s) => s.trim()) : void 0;
29948
30357
  let resolvedInstanceRoot2 = instanceRoot;
29949
30358
  if (instanceId) {
29950
- const registryPath = path63.join(os30.homedir(), ".openacp", "instances.json");
30359
+ const registryPath = path65.join(os31.homedir(), ".openacp", "instances.json");
29951
30360
  const registry = new InstanceRegistry(registryPath);
29952
30361
  await registry.load();
29953
30362
  const entry = registry.get(instanceId);
@@ -30085,7 +30494,7 @@ function extractFlag(args2, flag) {
30085
30494
  // src/cli/commands/setup.ts
30086
30495
  init_output();
30087
30496
  import * as fs55 from "fs";
30088
- import * as path64 from "path";
30497
+ import * as path66 from "path";
30089
30498
  function parseFlag(args2, flag) {
30090
30499
  const idx = args2.indexOf(flag);
30091
30500
  return idx !== -1 ? args2[idx + 1] : void 0;
@@ -30113,7 +30522,7 @@ async function cmdSetup(args2, instanceRoot) {
30113
30522
  }
30114
30523
  const runMode = rawRunMode;
30115
30524
  const defaultAgent = agentRaw.split(",")[0].trim();
30116
- const configPath = path64.join(instanceRoot, "config.json");
30525
+ const configPath = path66.join(instanceRoot, "config.json");
30117
30526
  let existing = {};
30118
30527
  if (fs55.existsSync(configPath)) {
30119
30528
  try {
@@ -30221,7 +30630,7 @@ async function main() {
30221
30630
  if (envRoot) {
30222
30631
  const { createInstanceContext: createInstanceContext2, getGlobalRoot: getGlobal } = await Promise.resolve().then(() => (init_instance_context(), instance_context_exports));
30223
30632
  const { InstanceRegistry: InstanceRegistry2 } = await Promise.resolve().then(() => (init_instance_registry(), instance_registry_exports));
30224
- const registry = new InstanceRegistry2(path66.join(getGlobal(), "instances.json"));
30633
+ const registry = new InstanceRegistry2(path68.join(getGlobal(), "instances.json"));
30225
30634
  await registry.load();
30226
30635
  const entry = registry.getByRoot(envRoot);
30227
30636
  const { randomUUID: randomUUID6 } = await import("crypto");