@openacp/cli 2026.41.1 → 2026.41.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
@@ -833,9 +833,7 @@ Register with \`ctx.registerMiddleware(hook, { priority?, handler })\`. Return \
833
833
  - \`session:afterDestroy\` \u2014 after session destroyed (sessionId, reason, durationMs, promptCount)
834
834
 
835
835
  ### Control
836
- - \`mode:beforeChange\` \u2014 before mode change (sessionId, fromMode, toMode)
837
836
  - \`config:beforeChange\` \u2014 before config change (sessionId, configId, oldValue, newValue)
838
- - \`model:beforeChange\` \u2014 before model change (sessionId, fromModel, toModel)
839
837
  - \`agent:beforeCancel\` \u2014 before agent cancellation (sessionId, reason)
840
838
  - \`agent:beforeSwitch\` \u2014 **blocking** before agent switch (sessionId, fromAgent, toAgent). Return null/false to block.
841
839
  - \`agent:afterSwitch\` \u2014 **fire-and-forget** after agent switch (sessionId, fromAgent, toAgent, resumed). Observational only.
@@ -3136,10 +3134,6 @@ var init_history_recorder = __esm({
3136
3134
  steps.push(step2);
3137
3135
  break;
3138
3136
  }
3139
- case "current_mode_update": {
3140
- steps.push({ type: "mode_change", modeId: event.modeId });
3141
- break;
3142
- }
3143
3137
  case "config_option_update": {
3144
3138
  for (const opt of event.options) {
3145
3139
  steps.push({
@@ -4570,7 +4564,7 @@ var init_cloudflare = __esm({
4570
4564
  settle(() => reject(new Error("Cloudflare tunnel timed out after 30s")));
4571
4565
  }, 3e4);
4572
4566
  try {
4573
- this.child = spawn(binaryPath, args2, { stdio: ["ignore", "pipe", "pipe"] });
4567
+ this.child = spawn(binaryPath, args2, { stdio: ["ignore", "pipe", "pipe"], detached: true });
4574
4568
  } catch {
4575
4569
  clearTimeout(timeout);
4576
4570
  settle(() => reject(new Error(`Failed to start cloudflared at ${binaryPath}`)));
@@ -4606,11 +4600,16 @@ var init_cloudflare = __esm({
4606
4600
  });
4607
4601
  });
4608
4602
  }
4609
- async stop() {
4603
+ async stop(force = false) {
4610
4604
  const child = this.child;
4611
4605
  if (!child) return;
4612
4606
  this.child = null;
4613
4607
  this.exitCallback = null;
4608
+ if (force) {
4609
+ child.kill("SIGKILL");
4610
+ log3.info("Cloudflare tunnel force-killed");
4611
+ return;
4612
+ }
4614
4613
  child.kill("SIGTERM");
4615
4614
  const exited = await Promise.race([
4616
4615
  new Promise((resolve8) => child.on("exit", () => resolve8(true))),
@@ -4679,7 +4678,7 @@ var init_ngrok = __esm({
4679
4678
  settle(() => reject(new Error("ngrok tunnel timed out after 30s. Is ngrok installed?")));
4680
4679
  }, 3e4);
4681
4680
  try {
4682
- this.child = spawn2("ngrok", args2, { stdio: ["ignore", "pipe", "pipe"] });
4681
+ this.child = spawn2("ngrok", args2, { stdio: ["ignore", "pipe", "pipe"], detached: true });
4683
4682
  } catch {
4684
4683
  clearTimeout(timeout);
4685
4684
  settle(() => reject(new Error(
@@ -4719,11 +4718,16 @@ var init_ngrok = __esm({
4719
4718
  });
4720
4719
  });
4721
4720
  }
4722
- async stop() {
4721
+ async stop(force = false) {
4723
4722
  const child = this.child;
4724
4723
  if (!child) return;
4725
4724
  this.child = null;
4726
4725
  this.exitCallback = null;
4726
+ if (force) {
4727
+ child.kill("SIGKILL");
4728
+ log4.info("ngrok tunnel force-killed");
4729
+ return;
4730
+ }
4727
4731
  child.kill("SIGTERM");
4728
4732
  const exited = await Promise.race([
4729
4733
  new Promise((resolve8) => child.on("exit", () => resolve8(true))),
@@ -4784,7 +4788,7 @@ var init_bore = __esm({
4784
4788
  settle(() => reject(new Error("Bore tunnel timed out after 30s. Is bore installed?")));
4785
4789
  }, 3e4);
4786
4790
  try {
4787
- this.child = spawn3("bore", args2, { stdio: ["ignore", "pipe", "pipe"] });
4791
+ this.child = spawn3("bore", args2, { stdio: ["ignore", "pipe", "pipe"], detached: true });
4788
4792
  } catch {
4789
4793
  clearTimeout(timeout);
4790
4794
  settle(() => reject(new Error(
@@ -4824,11 +4828,16 @@ var init_bore = __esm({
4824
4828
  });
4825
4829
  });
4826
4830
  }
4827
- async stop() {
4831
+ async stop(force = false) {
4828
4832
  const child = this.child;
4829
4833
  if (!child) return;
4830
4834
  this.child = null;
4831
4835
  this.exitCallback = null;
4836
+ if (force) {
4837
+ child.kill("SIGKILL");
4838
+ log5.info("Bore tunnel force-killed");
4839
+ return;
4840
+ }
4832
4841
  child.kill("SIGTERM");
4833
4842
  const exited = await Promise.race([
4834
4843
  new Promise((resolve8) => child.on("exit", () => resolve8(true))),
@@ -4894,7 +4903,7 @@ var init_tailscale = __esm({
4894
4903
  settle(() => reject(new Error("Tailscale funnel timed out after 30s. Is tailscale installed?")));
4895
4904
  }, 3e4);
4896
4905
  try {
4897
- this.child = spawn4("tailscale", args2, { stdio: ["ignore", "pipe", "pipe"] });
4906
+ this.child = spawn4("tailscale", args2, { stdio: ["ignore", "pipe", "pipe"], detached: true });
4898
4907
  } catch {
4899
4908
  clearTimeout(timeout);
4900
4909
  settle(() => reject(new Error(
@@ -4941,11 +4950,16 @@ var init_tailscale = __esm({
4941
4950
  });
4942
4951
  });
4943
4952
  }
4944
- async stop() {
4953
+ async stop(force = false) {
4945
4954
  const child = this.child;
4946
4955
  if (!child) return;
4947
4956
  this.child = null;
4948
4957
  this.exitCallback = null;
4958
+ if (force) {
4959
+ child.kill("SIGKILL");
4960
+ log6.info("Tailscale funnel force-killed");
4961
+ return;
4962
+ }
4949
4963
  child.kill("SIGTERM");
4950
4964
  const exited = await Promise.race([
4951
4965
  new Promise((resolve8) => child.on("exit", () => resolve8(true))),
@@ -5076,6 +5090,7 @@ var init_tunnel_registry = __esm({
5076
5090
  const entry = await this.add(port, opts, false);
5077
5091
  entry.retryCount = retryCount;
5078
5092
  } catch (err) {
5093
+ if (this.shuttingDown) return;
5079
5094
  log7.error({ port, err: err.message, retry: retryCount }, "Tunnel retry failed");
5080
5095
  const failedEntry = {
5081
5096
  port,
@@ -5146,7 +5161,7 @@ var init_tunnel_registry = __esm({
5146
5161
  for (const [, live] of this.entries) {
5147
5162
  if (live.retryTimer) clearTimeout(live.retryTimer);
5148
5163
  if (live.process) {
5149
- stopPromises.push(live.process.stop().catch(() => {
5164
+ stopPromises.push(live.process.stop(true).catch(() => {
5150
5165
  }));
5151
5166
  }
5152
5167
  }
@@ -6651,6 +6666,7 @@ async function createApiServer(options) {
6651
6666
  return { port: Number(url.port), host: url.hostname };
6652
6667
  } catch (err) {
6653
6668
  if (err?.code === "EADDRINUSE" && attempt < maxRetries && port < 65535) {
6669
+ console.log(`[api-server] Port ${port} in use, trying ${port + 1}...`);
6654
6670
  port++;
6655
6671
  continue;
6656
6672
  }
@@ -6896,7 +6912,7 @@ var init_service = __esm({
6896
6912
 
6897
6913
  // src/plugins/api-server/schemas/sessions.ts
6898
6914
  import { z } from "zod";
6899
- var ListSessionsQuerySchema, CreateSessionBodySchema, AdoptSessionBodySchema, PromptBodySchema, PermissionResponseBodySchema, DangerousModeBodySchema, UpdateSessionBodySchema, SessionIdParamSchema;
6915
+ var ListSessionsQuerySchema, CreateSessionBodySchema, AdoptSessionBodySchema, PromptBodySchema, PermissionResponseBodySchema, DangerousModeBodySchema, UpdateSessionBodySchema, SessionIdParamSchema, ConfigIdParamSchema, SetConfigOptionBodySchema, SetClientOverridesBodySchema;
6900
6916
  var init_sessions = __esm({
6901
6917
  "src/plugins/api-server/schemas/sessions.ts"() {
6902
6918
  "use strict";
@@ -6935,6 +6951,16 @@ var init_sessions = __esm({
6935
6951
  SessionIdParamSchema = z.object({
6936
6952
  sessionId: z.string().min(1)
6937
6953
  });
6954
+ ConfigIdParamSchema = z.object({
6955
+ sessionId: z.string().min(1),
6956
+ configId: z.string().min(1)
6957
+ });
6958
+ SetConfigOptionBodySchema = z.object({
6959
+ value: z.string()
6960
+ });
6961
+ SetClientOverridesBodySchema = z.object({
6962
+ bypassPermissions: z.boolean().optional()
6963
+ });
6938
6964
  }
6939
6965
  });
6940
6966
 
@@ -6954,15 +6980,12 @@ async function sessionRoutes(app, deps) {
6954
6980
  name: s.name ?? null,
6955
6981
  workspace: s.workingDirectory,
6956
6982
  createdAt: s.createdAt.toISOString(),
6957
- dangerousMode: s.dangerousMode,
6983
+ dangerousMode: s.clientOverrides.bypassPermissions ?? false,
6958
6984
  queueDepth: s.queueDepth,
6959
6985
  promptRunning: s.promptRunning,
6960
6986
  lastActiveAt: deps.core.sessionManager.getSessionRecord(s.id)?.lastActiveAt ?? null,
6961
6987
  // ACP state
6962
- currentMode: s.currentMode ?? null,
6963
- currentModel: s.currentModel ?? null,
6964
- availableModes: s.availableModes?.length ? s.availableModes : void 0,
6965
- availableModels: s.availableModels?.length ? s.availableModels : void 0,
6988
+ configOptions: s.configOptions?.length ? s.configOptions : void 0,
6966
6989
  capabilities: s.agentCapabilities ?? null
6967
6990
  }))
6968
6991
  };
@@ -6989,17 +7012,13 @@ async function sessionRoutes(app, deps) {
6989
7012
  name: session.name ?? null,
6990
7013
  workspace: session.workingDirectory,
6991
7014
  createdAt: session.createdAt.toISOString(),
6992
- dangerousMode: session.dangerousMode,
7015
+ dangerousMode: session.clientOverrides.bypassPermissions ?? false,
6993
7016
  queueDepth: session.queueDepth,
6994
7017
  promptRunning: session.promptRunning,
6995
7018
  threadId: session.threadId,
6996
7019
  channelId: session.channelId,
6997
7020
  agentSessionId: session.agentSessionId,
6998
7021
  // ACP state
6999
- currentMode: session.currentMode ?? null,
7000
- currentModel: session.currentModel ?? null,
7001
- availableModes: session.availableModes?.length ? session.availableModes : void 0,
7002
- availableModels: session.availableModels?.length ? session.availableModels : void 0,
7003
7022
  configOptions: session.configOptions?.length ? session.configOptions : void 0,
7004
7023
  capabilities: session.agentCapabilities ?? null
7005
7024
  }
@@ -7173,9 +7192,9 @@ async function sessionRoutes(app, deps) {
7173
7192
  changes.voiceMode = body.voiceMode;
7174
7193
  }
7175
7194
  if (body.dangerousMode !== void 0) {
7176
- session.dangerousMode = body.dangerousMode;
7195
+ session.clientOverrides.bypassPermissions = body.dangerousMode;
7177
7196
  await deps.core.sessionManager.patchRecord(sessionId, {
7178
- dangerousMode: body.dangerousMode
7197
+ clientOverrides: session.clientOverrides
7179
7198
  });
7180
7199
  changes.dangerousMode = body.dangerousMode;
7181
7200
  }
@@ -7196,13 +7215,101 @@ async function sessionRoutes(app, deps) {
7196
7215
  );
7197
7216
  }
7198
7217
  const body = DangerousModeBodySchema.parse(request.body);
7199
- session.dangerousMode = body.enabled;
7218
+ session.clientOverrides.bypassPermissions = body.enabled;
7200
7219
  await deps.core.sessionManager.patchRecord(sessionId, {
7201
- dangerousMode: body.enabled
7220
+ clientOverrides: session.clientOverrides
7202
7221
  });
7203
7222
  return { ok: true, dangerousMode: body.enabled };
7204
7223
  }
7205
7224
  );
7225
+ app.get(
7226
+ "/:sessionId/config",
7227
+ { preHandler: requireScopes("sessions:read") },
7228
+ async (request) => {
7229
+ const { sessionId: rawId } = SessionIdParamSchema.parse(request.params);
7230
+ const sessionId = decodeURIComponent(rawId);
7231
+ const session = deps.core.sessionManager.getSession(sessionId);
7232
+ if (!session) {
7233
+ throw new NotFoundError(
7234
+ "SESSION_NOT_FOUND",
7235
+ `Session "${sessionId}" not found`
7236
+ );
7237
+ }
7238
+ return {
7239
+ configOptions: session.configOptions,
7240
+ clientOverrides: session.clientOverrides
7241
+ };
7242
+ }
7243
+ );
7244
+ app.put(
7245
+ "/:sessionId/config/:configId",
7246
+ { preHandler: requireScopes("sessions:write") },
7247
+ async (request) => {
7248
+ const { sessionId: rawId, configId } = ConfigIdParamSchema.parse(request.params);
7249
+ const sessionId = decodeURIComponent(rawId);
7250
+ const session = deps.core.sessionManager.getSession(sessionId);
7251
+ if (!session) {
7252
+ throw new NotFoundError(
7253
+ "SESSION_NOT_FOUND",
7254
+ `Session "${sessionId}" not found`
7255
+ );
7256
+ }
7257
+ const body = SetConfigOptionBodySchema.parse(request.body);
7258
+ const response = await session.agentInstance.setConfigOption(configId, {
7259
+ type: "select",
7260
+ value: body.value
7261
+ });
7262
+ if (response.configOptions) {
7263
+ await session.updateConfigOptions(response.configOptions);
7264
+ }
7265
+ await deps.core.sessionManager.patchRecord(sessionId, {
7266
+ acpState: session.toAcpStateSnapshot()
7267
+ });
7268
+ return {
7269
+ configOptions: session.configOptions,
7270
+ clientOverrides: session.clientOverrides
7271
+ };
7272
+ }
7273
+ );
7274
+ app.get(
7275
+ "/:sessionId/config/overrides",
7276
+ { preHandler: requireScopes("sessions:read") },
7277
+ async (request) => {
7278
+ const { sessionId: rawId } = SessionIdParamSchema.parse(request.params);
7279
+ const sessionId = decodeURIComponent(rawId);
7280
+ const session = deps.core.sessionManager.getSession(sessionId);
7281
+ if (!session) {
7282
+ throw new NotFoundError(
7283
+ "SESSION_NOT_FOUND",
7284
+ `Session "${sessionId}" not found`
7285
+ );
7286
+ }
7287
+ return { clientOverrides: session.clientOverrides };
7288
+ }
7289
+ );
7290
+ app.put(
7291
+ "/:sessionId/config/overrides",
7292
+ { preHandler: requireScopes("sessions:write") },
7293
+ async (request) => {
7294
+ const { sessionId: rawId } = SessionIdParamSchema.parse(request.params);
7295
+ const sessionId = decodeURIComponent(rawId);
7296
+ const session = deps.core.sessionManager.getSession(sessionId);
7297
+ if (!session) {
7298
+ throw new NotFoundError(
7299
+ "SESSION_NOT_FOUND",
7300
+ `Session "${sessionId}" not found`
7301
+ );
7302
+ }
7303
+ const body = SetClientOverridesBodySchema.parse(request.body);
7304
+ if (body.bypassPermissions !== void 0) {
7305
+ session.clientOverrides.bypassPermissions = body.bypassPermissions;
7306
+ }
7307
+ await deps.core.sessionManager.patchRecord(sessionId, {
7308
+ clientOverrides: session.clientOverrides
7309
+ });
7310
+ return { clientOverrides: session.clientOverrides };
7311
+ }
7312
+ );
7206
7313
  app.post(
7207
7314
  "/:sessionId/archive",
7208
7315
  { preHandler: requireScopes("sessions:write") },
@@ -7349,28 +7456,28 @@ import path21 from "path";
7349
7456
  import fs17 from "fs";
7350
7457
  import os10 from "os";
7351
7458
  function createInstanceContext(opts) {
7352
- const { id, root: root2, isGlobal } = opts;
7459
+ const { id, root, isGlobal } = opts;
7353
7460
  return {
7354
7461
  id,
7355
- root: root2,
7462
+ root,
7356
7463
  isGlobal,
7357
7464
  paths: {
7358
- config: path21.join(root2, "config.json"),
7359
- sessions: path21.join(root2, "sessions.json"),
7360
- agents: path21.join(root2, "agents.json"),
7361
- registryCache: path21.join(root2, "registry-cache.json"),
7362
- plugins: path21.join(root2, "plugins"),
7363
- pluginsData: path21.join(root2, "plugins", "data"),
7364
- pluginRegistry: path21.join(root2, "plugins.json"),
7365
- logs: path21.join(root2, "logs"),
7366
- pid: path21.join(root2, "openacp.pid"),
7367
- running: path21.join(root2, "running"),
7368
- apiPort: path21.join(root2, "api.port"),
7369
- apiSecret: path21.join(root2, "api-secret"),
7370
- bin: path21.join(root2, "bin"),
7371
- cache: path21.join(root2, "cache"),
7372
- tunnels: path21.join(root2, "tunnels.json"),
7373
- agentsDir: path21.join(root2, "agents")
7465
+ config: path21.join(root, "config.json"),
7466
+ sessions: path21.join(root, "sessions.json"),
7467
+ agents: path21.join(root, "agents.json"),
7468
+ registryCache: path21.join(root, "registry-cache.json"),
7469
+ plugins: path21.join(root, "plugins"),
7470
+ pluginsData: path21.join(root, "plugins", "data"),
7471
+ pluginRegistry: path21.join(root, "plugins.json"),
7472
+ logs: path21.join(root, "logs"),
7473
+ pid: path21.join(root, "openacp.pid"),
7474
+ running: path21.join(root, "running"),
7475
+ apiPort: path21.join(root, "api.port"),
7476
+ apiSecret: path21.join(root, "api-secret"),
7477
+ bin: path21.join(root, "bin"),
7478
+ cache: path21.join(root, "cache"),
7479
+ tunnels: path21.join(root, "tunnels.json"),
7480
+ agentsDir: path21.join(root, "agents")
7374
7481
  }
7375
7482
  };
7376
7483
  }
@@ -7412,22 +7519,22 @@ __export(config_registry_exports, {
7412
7519
  });
7413
7520
  import * as fs18 from "fs";
7414
7521
  import * as path22 from "path";
7415
- function getFieldDef(path63) {
7416
- return CONFIG_REGISTRY.find((f) => f.path === path63);
7522
+ function getFieldDef(path65) {
7523
+ return CONFIG_REGISTRY.find((f) => f.path === path65);
7417
7524
  }
7418
7525
  function getSafeFields() {
7419
7526
  return CONFIG_REGISTRY.filter((f) => f.scope === "safe");
7420
7527
  }
7421
- function isHotReloadable(path63) {
7422
- const def = getFieldDef(path63);
7528
+ function isHotReloadable(path65) {
7529
+ const def = getFieldDef(path65);
7423
7530
  return def?.hotReload ?? false;
7424
7531
  }
7425
7532
  function resolveOptions(def, config) {
7426
7533
  if (!def.options) return void 0;
7427
7534
  return typeof def.options === "function" ? def.options(config) : def.options;
7428
7535
  }
7429
- function getConfigValue(config, path63) {
7430
- const parts = path63.split(".");
7536
+ function getConfigValue(config, path65) {
7537
+ const parts = path65.split(".");
7431
7538
  let current = config;
7432
7539
  for (const part of parts) {
7433
7540
  if (current && typeof current === "object" && part in current) {
@@ -8392,14 +8499,14 @@ async function commandRoutes(app, deps) {
8392
8499
  if (!deps.commandRegistry) {
8393
8500
  return { commands: [] };
8394
8501
  }
8395
- const commands2 = deps.commandRegistry.getAll().map((cmd) => ({
8502
+ const commands = deps.commandRegistry.getAll().map((cmd) => ({
8396
8503
  name: cmd.name,
8397
8504
  description: cmd.description,
8398
8505
  usage: cmd.usage ?? null,
8399
8506
  category: cmd.category,
8400
8507
  pluginName: cmd.pluginName ?? null
8401
8508
  }));
8402
- return { commands: commands2 };
8509
+ return { commands };
8403
8510
  });
8404
8511
  app.post("/execute", { preHandler: requireScopes("commands:execute") }, async (request, reply) => {
8405
8512
  if (!deps.commandRegistry) {
@@ -8628,7 +8735,7 @@ function createApiServerPlugin() {
8628
8735
  version: "1.0.0",
8629
8736
  description: "REST API + SSE streaming server",
8630
8737
  essential: false,
8631
- permissions: ["services:register", "kernel:access", "events:read"],
8738
+ permissions: ["services:register", "services:use", "kernel:access", "events:read"],
8632
8739
  async install(ctx) {
8633
8740
  const { settings, legacyConfig, terminal } = ctx;
8634
8741
  if (legacyConfig) {
@@ -8695,6 +8802,11 @@ function createApiServerPlugin() {
8695
8802
  port: config.port ?? 0,
8696
8803
  host: config.host ?? "127.0.0.1"
8697
8804
  };
8805
+ console.log(`[api-server] setup() called \u2014 port=${apiConfig.port}, host=${apiConfig.host}, instanceRoot=${instanceRoot}`);
8806
+ log13.info(
8807
+ { port: apiConfig.port, host: apiConfig.host, instanceRoot },
8808
+ "API server plugin setup \u2014 config loaded"
8809
+ );
8698
8810
  portFilePath = path25.join(instanceRoot, "api.port");
8699
8811
  const secretFilePath = path25.join(instanceRoot, "api-secret");
8700
8812
  const jwtSecretFilePath = path25.join(instanceRoot, "jwt-secret");
@@ -8791,14 +8903,21 @@ function createApiServerPlugin() {
8791
8903
  ctx.registerService("api-server", apiService);
8792
8904
  cleanupInterval = setInterval(() => tokenStore.cleanup(), 60 * 60 * 1e3);
8793
8905
  ctx.on("system:ready", async () => {
8906
+ console.log(`[api-server] system:ready fired \u2014 starting server on ${apiConfig.host}:${apiConfig.port}`);
8907
+ log13.info(
8908
+ { configPort: apiConfig.port, configHost: apiConfig.host },
8909
+ "API server starting..."
8910
+ );
8794
8911
  try {
8795
8912
  const addr = await server.start();
8796
8913
  actualPort = addr.port;
8797
8914
  writePortFile(portFilePath, actualPort);
8798
8915
  sseManager.setup();
8799
8916
  log13.info(
8800
- { host: addr.host, port: addr.port },
8801
- "API server listening"
8917
+ { host: addr.host, port: addr.port, portFile: portFilePath },
8918
+ "API server listening on http://%s:%d",
8919
+ addr.host,
8920
+ addr.port
8802
8921
  );
8803
8922
  if (apiConfig.host !== "127.0.0.1" && apiConfig.host !== "localhost") {
8804
8923
  log13.warn(
@@ -8806,7 +8925,7 @@ function createApiServerPlugin() {
8806
8925
  );
8807
8926
  }
8808
8927
  } catch (err) {
8809
- ctx.log.error(`API server failed to start: ${err}`);
8928
+ log13.error({ err, configPort: apiConfig.port, configHost: apiConfig.host }, "API server failed to start");
8810
8929
  }
8811
8930
  });
8812
8931
  },
@@ -9266,7 +9385,7 @@ var init_sse_adapter = __esm({
9266
9385
  "@openacp/security": "^1.0.0",
9267
9386
  "@openacp/notifications": "^1.0.0"
9268
9387
  },
9269
- permissions: ["services:register", "kernel:access", "events:read"],
9388
+ permissions: ["services:register", "services:use", "kernel:access", "events:read"],
9270
9389
  async setup(ctx) {
9271
9390
  const core = ctx.core;
9272
9391
  const apiServer = ctx.getService("api-server");
@@ -9627,8 +9746,8 @@ function formatToolSummary(name, rawInput, displaySummary) {
9627
9746
  }
9628
9747
  if (lowerName === "grep") {
9629
9748
  const pattern = args2.pattern ?? "";
9630
- const path63 = args2.path ?? "";
9631
- return pattern ? `\u{1F50D} Grep "${pattern}"${path63 ? ` in ${path63}` : ""}` : `\u{1F527} ${name}`;
9749
+ const path65 = args2.path ?? "";
9750
+ return pattern ? `\u{1F50D} Grep "${pattern}"${path65 ? ` in ${path65}` : ""}` : `\u{1F527} ${name}`;
9632
9751
  }
9633
9752
  if (lowerName === "glob") {
9634
9753
  const pattern = args2.pattern ?? "";
@@ -9662,8 +9781,8 @@ function formatToolTitle(name, rawInput, displayTitle) {
9662
9781
  }
9663
9782
  if (lowerName === "grep") {
9664
9783
  const pattern = args2.pattern ?? "";
9665
- const path63 = args2.path ?? "";
9666
- return pattern ? `"${pattern}"${path63 ? ` in ${path63}` : ""}` : name;
9784
+ const path65 = args2.path ?? "";
9785
+ return pattern ? `"${pattern}"${path65 ? ` in ${path65}` : ""}` : name;
9667
9786
  }
9668
9787
  if (lowerName === "glob") {
9669
9788
  return String(args2.pattern ?? name);
@@ -9951,14 +10070,15 @@ function setupDangerousModeCallbacks(bot, core) {
9951
10070
  const sessionId = ctx.callbackQuery.data.slice(2);
9952
10071
  const session = core.sessionManager.getSession(sessionId);
9953
10072
  if (session) {
9954
- session.dangerousMode = !session.dangerousMode;
10073
+ const newDangerousMode2 = !session.clientOverrides.bypassPermissions;
10074
+ session.clientOverrides.bypassPermissions = newDangerousMode2;
9955
10075
  log14.info(
9956
- { sessionId, dangerousMode: session.dangerousMode },
10076
+ { sessionId, dangerousMode: newDangerousMode2 },
9957
10077
  "Dangerous mode toggled via button"
9958
10078
  );
9959
- core.sessionManager.patchRecord(sessionId, { dangerousMode: session.dangerousMode }).catch(() => {
10079
+ core.sessionManager.patchRecord(sessionId, { clientOverrides: session.clientOverrides }).catch(() => {
9960
10080
  });
9961
- const toastText2 = session.dangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
10081
+ const toastText2 = newDangerousMode2 ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
9962
10082
  try {
9963
10083
  await ctx.answerCallbackQuery({ text: toastText2 });
9964
10084
  } catch {
@@ -9967,7 +10087,7 @@ function setupDangerousModeCallbacks(bot, core) {
9967
10087
  await ctx.editMessageReplyMarkup({
9968
10088
  reply_markup: buildSessionControlKeyboard(
9969
10089
  sessionId,
9970
- session.dangerousMode,
10090
+ newDangerousMode2,
9971
10091
  session.voiceMode === "on"
9972
10092
  )
9973
10093
  });
@@ -9985,8 +10105,8 @@ function setupDangerousModeCallbacks(bot, core) {
9985
10105
  }
9986
10106
  return;
9987
10107
  }
9988
- const newDangerousMode = !(record.dangerousMode ?? false);
9989
- core.sessionManager.patchRecord(sessionId, { dangerousMode: newDangerousMode }).catch(() => {
10108
+ const newDangerousMode = !(record.clientOverrides?.bypassPermissions ?? record.dangerousMode ?? false);
10109
+ core.sessionManager.patchRecord(sessionId, { clientOverrides: { bypassPermissions: newDangerousMode } }).catch(() => {
9990
10110
  });
9991
10111
  log14.info(
9992
10112
  { sessionId, dangerousMode: newDangerousMode },
@@ -10017,19 +10137,19 @@ async function handleEnableDangerous(ctx, core) {
10017
10137
  });
10018
10138
  return;
10019
10139
  }
10020
- const session = core.sessionManager.getSessionByThread(
10140
+ const session = await core.getOrResumeSession(
10021
10141
  "telegram",
10022
10142
  String(threadId)
10023
10143
  );
10024
10144
  if (session) {
10025
- if (session.dangerousMode) {
10145
+ if (session.clientOverrides.bypassPermissions) {
10026
10146
  await ctx.reply("\u2620\uFE0F Dangerous mode is already enabled.", {
10027
10147
  parse_mode: "HTML"
10028
10148
  });
10029
10149
  return;
10030
10150
  }
10031
- session.dangerousMode = true;
10032
- core.sessionManager.patchRecord(session.id, { dangerousMode: true }).catch(() => {
10151
+ session.clientOverrides.bypassPermissions = true;
10152
+ core.sessionManager.patchRecord(session.id, { clientOverrides: session.clientOverrides }).catch(() => {
10033
10153
  });
10034
10154
  } else {
10035
10155
  const record = core.sessionManager.getRecordByThread(
@@ -10042,13 +10162,13 @@ async function handleEnableDangerous(ctx, core) {
10042
10162
  });
10043
10163
  return;
10044
10164
  }
10045
- if (record.dangerousMode) {
10165
+ if (record.clientOverrides?.bypassPermissions ?? record.dangerousMode) {
10046
10166
  await ctx.reply("\u2620\uFE0F Dangerous mode is already enabled.", {
10047
10167
  parse_mode: "HTML"
10048
10168
  });
10049
10169
  return;
10050
10170
  }
10051
- core.sessionManager.patchRecord(record.sessionId, { dangerousMode: true }).catch(() => {
10171
+ core.sessionManager.patchRecord(record.sessionId, { clientOverrides: { bypassPermissions: true } }).catch(() => {
10052
10172
  });
10053
10173
  }
10054
10174
  await ctx.reply(
@@ -10068,19 +10188,19 @@ async function handleDisableDangerous(ctx, core) {
10068
10188
  });
10069
10189
  return;
10070
10190
  }
10071
- const session = core.sessionManager.getSessionByThread(
10191
+ const session = await core.getOrResumeSession(
10072
10192
  "telegram",
10073
10193
  String(threadId)
10074
10194
  );
10075
10195
  if (session) {
10076
- if (!session.dangerousMode) {
10196
+ if (!session.clientOverrides.bypassPermissions) {
10077
10197
  await ctx.reply("\u{1F510} Dangerous mode is already disabled.", {
10078
10198
  parse_mode: "HTML"
10079
10199
  });
10080
10200
  return;
10081
10201
  }
10082
- session.dangerousMode = false;
10083
- core.sessionManager.patchRecord(session.id, { dangerousMode: false }).catch(() => {
10202
+ session.clientOverrides.bypassPermissions = false;
10203
+ core.sessionManager.patchRecord(session.id, { clientOverrides: session.clientOverrides }).catch(() => {
10084
10204
  });
10085
10205
  } else {
10086
10206
  const record = core.sessionManager.getRecordByThread(
@@ -10093,13 +10213,13 @@ async function handleDisableDangerous(ctx, core) {
10093
10213
  });
10094
10214
  return;
10095
10215
  }
10096
- if (!record.dangerousMode) {
10216
+ if (!(record.clientOverrides?.bypassPermissions ?? record.dangerousMode)) {
10097
10217
  await ctx.reply("\u{1F510} Dangerous mode is already disabled.", {
10098
10218
  parse_mode: "HTML"
10099
10219
  });
10100
10220
  return;
10101
10221
  }
10102
- core.sessionManager.patchRecord(record.sessionId, { dangerousMode: false }).catch(() => {
10222
+ core.sessionManager.patchRecord(record.sessionId, { clientOverrides: { bypassPermissions: false } }).catch(() => {
10103
10223
  });
10104
10224
  }
10105
10225
  await ctx.reply(
@@ -10149,7 +10269,7 @@ function setupTTSCallbacks(bot, core) {
10149
10269
  await ctx.editMessageReplyMarkup({
10150
10270
  reply_markup: buildSessionControlKeyboard(
10151
10271
  sessionId,
10152
- session.dangerousMode,
10272
+ session.clientOverrides.bypassPermissions ?? false,
10153
10273
  newMode === "on"
10154
10274
  )
10155
10275
  });
@@ -10213,7 +10333,7 @@ async function handleOutputMode(ctx, core) {
10213
10333
  await ctx.reply("\u26A0\uFE0F This command must be used in a session topic.", { parse_mode: "HTML" });
10214
10334
  return;
10215
10335
  }
10216
- const session = core.sessionManager.getSessionByThread(
10336
+ const session = await core.getOrResumeSession(
10217
10337
  "telegram",
10218
10338
  String(threadId)
10219
10339
  );
@@ -10522,7 +10642,7 @@ async function handleNewChat(ctx, core, chatId) {
10522
10642
  );
10523
10643
  return;
10524
10644
  }
10525
- const currentSession = core.sessionManager.getSessionByThread(
10645
+ const currentSession = await core.getOrResumeSession(
10526
10646
  "telegram",
10527
10647
  String(threadId)
10528
10648
  );
@@ -10802,7 +10922,7 @@ async function handleCancel(ctx, core, assistant) {
10802
10922
  async function handleStatus(ctx, core) {
10803
10923
  const threadId = ctx.message?.message_thread_id;
10804
10924
  if (threadId) {
10805
- const session = core.sessionManager.getSessionByThread(
10925
+ const session = await core.getOrResumeSession(
10806
10926
  "telegram",
10807
10927
  String(threadId)
10808
10928
  );
@@ -11188,8 +11308,8 @@ async function handleClear(ctx, assistant) {
11188
11308
  await ctx.reply(`\u274C Failed to clear: <code>${message}</code>`, { parse_mode: "HTML" });
11189
11309
  }
11190
11310
  }
11191
- function buildSkillMessages(commands2) {
11192
- const sorted = [...commands2].sort((a, b) => a.name.localeCompare(b.name));
11311
+ function buildSkillMessages(commands) {
11312
+ const sorted = [...commands].sort((a, b) => a.name.localeCompare(b.name));
11193
11313
  const header2 = "\u{1F6E0} <b>Available Skills</b>\n";
11194
11314
  const lines = sorted.map((c3) => `<code>/${c3.name}</code>`);
11195
11315
  const messages = [];
@@ -15272,8 +15392,12 @@ var init_send_queue = __esm({
15272
15392
  const result = await item.fn();
15273
15393
  item.resolve(result);
15274
15394
  } catch (err) {
15275
- this.config.onError?.(err instanceof Error ? err : new Error(String(err)));
15276
- item.reject(err);
15395
+ if (err instanceof Error && "description" in err && typeof err.description === "string" && err.description.includes("message is not modified")) {
15396
+ item.resolve(void 0);
15397
+ } else {
15398
+ this.config.onError?.(err instanceof Error ? err : new Error(String(err)));
15399
+ item.reject(err);
15400
+ }
15277
15401
  } finally {
15278
15402
  const now = Date.now();
15279
15403
  this.lastExec = now;
@@ -15474,6 +15598,7 @@ var init_streaming = __esm({
15474
15598
  flushTimer;
15475
15599
  flushPromise = Promise.resolve();
15476
15600
  lastSentBuffer = "";
15601
+ lastSentHtml = "";
15477
15602
  displayTruncated = false;
15478
15603
  tracer;
15479
15604
  append(text6) {
@@ -15533,6 +15658,8 @@ var init_streaming = __esm({
15533
15658
  this.firstFlushPending = false;
15534
15659
  }
15535
15660
  } else {
15661
+ if (html === this.lastSentHtml) return;
15662
+ this.lastSentHtml = html;
15536
15663
  try {
15537
15664
  const result = await this.sendQueue.enqueue(
15538
15665
  () => this.bot.api.editMessageText(this.chatId, this.messageId, html, {
@@ -15767,7 +15894,7 @@ var init_skill_command_manager = __esm({
15767
15894
  this.sessionManager = sessionManager;
15768
15895
  }
15769
15896
  messages = /* @__PURE__ */ new Map();
15770
- async send(sessionId, threadId, commands2) {
15897
+ async send(sessionId, threadId, commands) {
15771
15898
  if (!this.messages.has(sessionId)) {
15772
15899
  const record = this.sessionManager.getSessionRecord(sessionId);
15773
15900
  const platform2 = record?.platform;
@@ -15775,11 +15902,11 @@ var init_skill_command_manager = __esm({
15775
15902
  this.messages.set(sessionId, platform2.skillMsgId);
15776
15903
  }
15777
15904
  }
15778
- if (commands2.length === 0) {
15905
+ if (commands.length === 0) {
15779
15906
  await this.cleanup(sessionId);
15780
15907
  return;
15781
15908
  }
15782
- const messages = buildSkillMessages(commands2);
15909
+ const messages = buildSkillMessages(commands);
15783
15910
  const existingMsgId = this.messages.get(sessionId);
15784
15911
  if (existingMsgId) {
15785
15912
  try {
@@ -16417,10 +16544,10 @@ var init_adapter2 = __esm({
16417
16544
  const chatId = ctx.chat.id;
16418
16545
  const topicId = ctx.message.message_thread_id;
16419
16546
  try {
16420
- const sessionId = topicId != null ? this.core.sessionManager.getSessionByThread(
16547
+ const sessionId = topicId != null ? (await this.core.getOrResumeSession(
16421
16548
  "telegram",
16422
16549
  String(topicId)
16423
- )?.id ?? null : null;
16550
+ ))?.id ?? null : null;
16424
16551
  const response = await registry.execute(text6, {
16425
16552
  raw: "",
16426
16553
  sessionId,
@@ -16456,10 +16583,10 @@ var init_adapter2 = __esm({
16456
16583
  const chatId = ctx.chat.id;
16457
16584
  const topicId = ctx.callbackQuery.message?.message_thread_id;
16458
16585
  try {
16459
- const sessionId = topicId != null ? this.core.sessionManager.getSessionByThread(
16586
+ const sessionId = topicId != null ? (await this.core.getOrResumeSession(
16460
16587
  "telegram",
16461
16588
  String(topicId)
16462
- )?.id ?? null : null;
16589
+ ))?.id ?? null : null;
16463
16590
  const response = await registry.execute(command2, {
16464
16591
  raw: "",
16465
16592
  sessionId,
@@ -16541,7 +16668,7 @@ var init_adapter2 = __esm({
16541
16668
  });
16542
16669
  return;
16543
16670
  }
16544
- const session = this.core.sessionManager.getSessionByThread(
16671
+ const session = await this.core.getOrResumeSession(
16545
16672
  "telegram",
16546
16673
  String(threadId)
16547
16674
  );
@@ -16813,10 +16940,10 @@ ${lines.join("\n")}`;
16813
16940
  );
16814
16941
  return;
16815
16942
  }
16816
- const sessionId = this.core.sessionManager.getSessionByThread(
16943
+ const sessionId = (await this.core.getOrResumeSession(
16817
16944
  "telegram",
16818
16945
  String(threadId)
16819
- )?.id;
16946
+ ))?.id;
16820
16947
  if (sessionId) {
16821
16948
  this.getTracer(sessionId)?.log("telegram", { action: "incoming:message", sessionId, userId: String(ctx.from?.id), text: ctx.message?.text });
16822
16949
  await this.draftManager.finalize(sessionId, this.assistantSession?.id);
@@ -17282,33 +17409,33 @@ Task completed.
17282
17409
  );
17283
17410
  }
17284
17411
  }
17285
- async sendSkillCommands(sessionId, commands2) {
17412
+ async sendSkillCommands(sessionId, commands) {
17286
17413
  if (sessionId === this.assistantSession?.id) return;
17287
17414
  const session = this.core.sessionManager.getSession(sessionId);
17288
17415
  if (!session) return;
17289
17416
  const threadId = Number(session.threadId);
17290
17417
  if (!threadId) {
17291
- this._pendingSkillCommands.set(sessionId, commands2);
17418
+ this._pendingSkillCommands.set(sessionId, commands);
17292
17419
  return;
17293
17420
  }
17294
- await this.skillManager.send(sessionId, threadId, commands2);
17421
+ await this.skillManager.send(sessionId, threadId, commands);
17295
17422
  }
17296
17423
  /** Flush any skill commands that were queued before threadId was available */
17297
17424
  async flushPendingSkillCommands(sessionId) {
17298
- const commands2 = this._pendingSkillCommands.get(sessionId);
17299
- if (!commands2) return;
17425
+ const commands = this._pendingSkillCommands.get(sessionId);
17426
+ if (!commands) return;
17300
17427
  this._pendingSkillCommands.delete(sessionId);
17301
17428
  const session = this.core.sessionManager.getSession(sessionId);
17302
17429
  if (!session) return;
17303
17430
  const threadId = Number(session.threadId);
17304
17431
  if (!threadId) return;
17305
- await this.skillManager.send(sessionId, threadId, commands2);
17432
+ await this.skillManager.send(sessionId, threadId, commands);
17306
17433
  }
17307
- resolveSessionId(threadId) {
17308
- return this.core.sessionManager.getSessionByThread(
17434
+ async resolveSessionId(threadId) {
17435
+ return (await this.core.getOrResumeSession(
17309
17436
  "telegram",
17310
17437
  String(threadId)
17311
- )?.id;
17438
+ ))?.id;
17312
17439
  }
17313
17440
  async downloadTelegramFile(fileId) {
17314
17441
  try {
@@ -17329,7 +17456,7 @@ Task completed.
17329
17456
  if (!downloaded) return;
17330
17457
  let buffer = downloaded.buffer;
17331
17458
  let originalFilePath;
17332
- const sessionId = this.resolveSessionId(threadId) || "unknown";
17459
+ const sessionId = await this.resolveSessionId(threadId) || "unknown";
17333
17460
  if (convertOggToWav) {
17334
17461
  const oggAtt = await this.fileService.saveFile(
17335
17462
  sessionId,
@@ -17364,7 +17491,7 @@ Task completed.
17364
17491
  }
17365
17492
  return;
17366
17493
  }
17367
- const sid = this.resolveSessionId(threadId);
17494
+ const sid = await this.resolveSessionId(threadId);
17368
17495
  if (sid) await this.draftManager.finalize(sid, this.assistantSession?.id);
17369
17496
  this.core.handleMessage({
17370
17497
  channelId: "telegram",
@@ -17893,11 +18020,11 @@ __export(api_client_exports, {
17893
18020
  import * as fs32 from "fs";
17894
18021
  import * as path33 from "path";
17895
18022
  import * as os14 from "os";
17896
- function defaultPortFile(root2) {
17897
- return path33.join(root2 ?? DEFAULT_ROOT, "api.port");
18023
+ function defaultPortFile(root) {
18024
+ return path33.join(root ?? DEFAULT_ROOT, "api.port");
17898
18025
  }
17899
- function defaultSecretFile(root2) {
17900
- return path33.join(root2 ?? DEFAULT_ROOT, "api-secret");
18026
+ function defaultSecretFile(root) {
18027
+ return path33.join(root ?? DEFAULT_ROOT, "api-secret");
17901
18028
  }
17902
18029
  function readApiPort(portFilePath, instanceRoot) {
17903
18030
  const filePath = portFilePath ?? defaultPortFile(instanceRoot);
@@ -17987,17 +18114,16 @@ var init_suggest = __esm({
17987
18114
  import fs33 from "fs";
17988
18115
  import path34 from "path";
17989
18116
  import os15 from "os";
17990
- function printInstanceHint(root2) {
18117
+ function printInstanceHint(root) {
17991
18118
  const globalRoot = getGlobalRoot();
17992
- const isGlobal = root2 === globalRoot;
17993
- const displayPath = root2.replace(os15.homedir(), "~");
18119
+ const isGlobal = root === globalRoot;
18120
+ const displayPath = root.replace(os15.homedir(), "~");
17994
18121
  const label = isGlobal ? "global" : "local";
17995
- console.log(` Instance: ${displayPath} (${label})`);
18122
+ console.log(` Workspace: ${label} \u2014 ${displayPath}`);
17996
18123
  if (isGlobal) {
17997
18124
  const localRoot = path34.join(process.cwd(), ".openacp");
17998
18125
  if (fs33.existsSync(localRoot)) {
17999
- const localDisplay = localRoot.replace(os15.homedir(), "~");
18000
- console.log(` \x1B[2mhint: local instance found at ${localDisplay} \u2014 use --local to use it\x1B[0m`);
18126
+ console.log(` \x1B[2mhint: local workspace exists in current directory \u2014 use --local to use it\x1B[0m`);
18001
18127
  }
18002
18128
  }
18003
18129
  }
@@ -18029,16 +18155,16 @@ import { spawn as spawn5 } from "child_process";
18029
18155
  import * as fs34 from "fs";
18030
18156
  import * as path35 from "path";
18031
18157
  import * as os16 from "os";
18032
- function getPidPath(root2) {
18033
- const base = root2 ?? DEFAULT_ROOT2;
18158
+ function getPidPath(root) {
18159
+ const base = root ?? DEFAULT_ROOT2;
18034
18160
  return path35.join(base, "openacp.pid");
18035
18161
  }
18036
- function getLogDir(root2) {
18037
- const base = root2 ?? DEFAULT_ROOT2;
18162
+ function getLogDir(root) {
18163
+ const base = root ?? DEFAULT_ROOT2;
18038
18164
  return path35.join(base, "logs");
18039
18165
  }
18040
- function getRunningMarker(root2) {
18041
- const base = root2 ?? DEFAULT_ROOT2;
18166
+ function getRunningMarker(root) {
18167
+ const base = root ?? DEFAULT_ROOT2;
18042
18168
  return path35.join(base, "running");
18043
18169
  }
18044
18170
  function writePidFile(pidPath, pid) {
@@ -18174,19 +18300,19 @@ async function stopDaemon(pidPath = getPidPath(), instanceRoot) {
18174
18300
  }
18175
18301
  return { stopped: false, pid, error: "Process did not exit after SIGKILL (possible uninterruptible I/O). PID file retained." };
18176
18302
  }
18177
- function markRunning(root2) {
18178
- const marker = getRunningMarker(root2);
18303
+ function markRunning(root) {
18304
+ const marker = getRunningMarker(root);
18179
18305
  fs34.mkdirSync(path35.dirname(marker), { recursive: true });
18180
18306
  fs34.writeFileSync(marker, "");
18181
18307
  }
18182
- function clearRunning(root2) {
18308
+ function clearRunning(root) {
18183
18309
  try {
18184
- fs34.unlinkSync(getRunningMarker(root2));
18310
+ fs34.unlinkSync(getRunningMarker(root));
18185
18311
  } catch {
18186
18312
  }
18187
18313
  }
18188
- function shouldAutoStart(root2) {
18189
- return fs34.existsSync(getRunningMarker(root2));
18314
+ function shouldAutoStart(root) {
18315
+ return fs34.existsSync(getRunningMarker(root));
18190
18316
  }
18191
18317
  var DEFAULT_ROOT2;
18192
18318
  var init_daemon2 = __esm({
@@ -18205,7 +18331,7 @@ __export(stop_exports, {
18205
18331
  import path37 from "path";
18206
18332
  import os18 from "os";
18207
18333
  async function cmdStop(args2 = [], instanceRoot) {
18208
- const root2 = instanceRoot ?? path37.join(os18.homedir(), ".openacp");
18334
+ const root = instanceRoot ?? path37.join(os18.homedir(), ".openacp");
18209
18335
  if (wantsHelp(args2)) {
18210
18336
  console.log(`
18211
18337
  \x1B[1mopenacp stop\x1B[0m \u2014 Stop the background daemon
@@ -18218,7 +18344,7 @@ Sends a stop signal to the running OpenACP daemon process.
18218
18344
  return;
18219
18345
  }
18220
18346
  const { stopDaemon: stopDaemon2, getPidPath: getPidPath2 } = await Promise.resolve().then(() => (init_daemon2(), daemon_exports));
18221
- const result = await stopDaemon2(getPidPath2(root2), root2);
18347
+ const result = await stopDaemon2(getPidPath2(root), root);
18222
18348
  if (result.stopped) {
18223
18349
  console.log(`OpenACP daemon stopped (was PID ${result.pid})`);
18224
18350
  } else {
@@ -18600,10 +18726,10 @@ function resolveAgentCommand(cmd) {
18600
18726
  if (ownDir !== process.cwd()) {
18601
18727
  searchRoots.push(ownDir);
18602
18728
  }
18603
- for (const root2 of searchRoots) {
18729
+ for (const root of searchRoots) {
18604
18730
  const packageDirs = [
18605
- path39.resolve(root2, "node_modules", "@zed-industries", cmd, "dist", "index.js"),
18606
- path39.resolve(root2, "node_modules", cmd, "dist", "index.js")
18731
+ path39.resolve(root, "node_modules", "@zed-industries", cmd, "dist", "index.js"),
18732
+ path39.resolve(root, "node_modules", cmd, "dist", "index.js")
18607
18733
  ];
18608
18734
  for (const jsPath of packageDirs) {
18609
18735
  if (fs36.existsSync(jsPath)) {
@@ -18611,8 +18737,8 @@ function resolveAgentCommand(cmd) {
18611
18737
  }
18612
18738
  }
18613
18739
  }
18614
- for (const root2 of searchRoots) {
18615
- const localBin = path39.resolve(root2, "node_modules", ".bin", cmd);
18740
+ for (const root of searchRoots) {
18741
+ const localBin = path39.resolve(root, "node_modules", ".bin", cmd);
18616
18742
  if (fs36.existsSync(localBin)) {
18617
18743
  const content = fs36.readFileSync(localBin, "utf-8");
18618
18744
  if (content.startsWith("#!/usr/bin/env node")) {
@@ -18953,14 +19079,6 @@ ${stderr}`
18953
19079
  };
18954
19080
  break;
18955
19081
  }
18956
- case "current_mode_update": {
18957
- const cm = update;
18958
- event = {
18959
- type: "current_mode_update",
18960
- modeId: cm.currentModeId
18961
- };
18962
- break;
18963
- }
18964
19082
  case "config_option_update": {
18965
19083
  const co = update;
18966
19084
  event = {
@@ -18978,10 +19096,10 @@ ${stderr}`
18978
19096
  break;
18979
19097
  }
18980
19098
  // NOTE: model_update is NOT a session update type in the ACP SDK schema.
18981
- // Model changes are applied via the unstable_setSessionModel() method and
18982
- // the response is synchronous — the SDK does not push a model_update
18983
- // notification to the client. Therefore AgentEvent "model_update" cannot
18984
- // originate from sessionUpdate and must be emitted by callers of setModel()
19099
+ // Model changes are applied via setSessionConfigOption() and the response
19100
+ // is synchronous — the SDK does not push a model_update notification to
19101
+ // the client. Therefore AgentEvent "model_update" cannot originate from
19102
+ // sessionUpdate and must be emitted by callers of setConfigOption()
18985
19103
  // if they need to propagate the change downstream.
18986
19104
  default:
18987
19105
  return;
@@ -19063,9 +19181,6 @@ ${stderr}`
19063
19181
  };
19064
19182
  }
19065
19183
  // ── New ACP methods ──────────────────────────────────────────────────
19066
- async setMode(modeId) {
19067
- await this.connection.setSessionMode({ sessionId: this.sessionId, modeId });
19068
- }
19069
19184
  async setConfigOption(configId, value) {
19070
19185
  return await this.connection.setSessionConfigOption({
19071
19186
  sessionId: this.sessionId,
@@ -19073,12 +19188,6 @@ ${stderr}`
19073
19188
  ...value
19074
19189
  });
19075
19190
  }
19076
- async setModel(modelId) {
19077
- await this.connection.unstable_setSessionModel({
19078
- sessionId: this.sessionId,
19079
- modelId
19080
- });
19081
- }
19082
19191
  async listSessions(cwd, cursor) {
19083
19192
  return await this.connection.listSessions({
19084
19193
  cwd: cwd ?? null,
@@ -19376,12 +19485,8 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
19376
19485
  name;
19377
19486
  createdAt = /* @__PURE__ */ new Date();
19378
19487
  voiceMode = "off";
19379
- dangerousMode = false;
19380
- currentMode;
19381
- availableModes = [];
19382
19488
  configOptions = [];
19383
- currentModel;
19384
- availableModels = [];
19489
+ clientOverrides = {};
19385
19490
  agentCapabilities;
19386
19491
  archiving = false;
19387
19492
  promptCount = 0;
@@ -19675,34 +19780,28 @@ ${result.text}` : result.text;
19675
19780
  }
19676
19781
  }
19677
19782
  // --- ACP Mode / Config / Model State ---
19678
- setInitialAcpState(state) {
19679
- if (state.modes) {
19680
- this.currentMode = state.modes.currentModeId;
19681
- this.availableModes = state.modes.availableModes;
19682
- }
19683
- if (state.configOptions) {
19684
- this.configOptions = state.configOptions;
19685
- }
19686
- if (state.models) {
19687
- this.currentModel = state.models.currentModelId;
19688
- this.availableModels = state.models.availableModels;
19689
- }
19690
- if (state.agentCapabilities) {
19691
- this.agentCapabilities = state.agentCapabilities;
19692
- }
19783
+ setInitialConfigOptions(options) {
19784
+ this.configOptions = options ?? [];
19785
+ }
19786
+ setAgentCapabilities(caps) {
19787
+ this.agentCapabilities = caps;
19788
+ }
19789
+ getConfigOption(id) {
19790
+ return this.configOptions.find((o) => o.id === id);
19791
+ }
19792
+ getConfigByCategory(category) {
19793
+ return this.configOptions.find((o) => o.category === category);
19794
+ }
19795
+ getConfigValue(id) {
19796
+ const option = this.getConfigOption(id);
19797
+ if (!option) return void 0;
19798
+ return String(option.currentValue);
19693
19799
  }
19694
19800
  /** Set session name explicitly and emit 'named' event */
19695
19801
  setName(name) {
19696
19802
  this.name = name;
19697
19803
  this.emit("named", name);
19698
19804
  }
19699
- async updateMode(modeId) {
19700
- if (this.middlewareChain) {
19701
- const result = await this.middlewareChain.execute("mode:beforeChange", { sessionId: this.id, fromMode: this.currentMode, toMode: modeId }, async (p2) => p2);
19702
- if (!result) return;
19703
- }
19704
- this.currentMode = modeId;
19705
- }
19706
19805
  async updateConfigOptions(options) {
19707
19806
  if (this.middlewareChain) {
19708
19807
  const result = await this.middlewareChain.execute("config:beforeChange", { sessionId: this.id, configId: "options", oldValue: this.configOptions, newValue: options }, async (p2) => p2);
@@ -19710,21 +19809,10 @@ ${result.text}` : result.text;
19710
19809
  }
19711
19810
  this.configOptions = options;
19712
19811
  }
19713
- async updateModel(modelId) {
19714
- if (this.middlewareChain) {
19715
- const result = await this.middlewareChain.execute("model:beforeChange", { sessionId: this.id, fromModel: this.currentModel, toModel: modelId }, async (p2) => p2);
19716
- if (!result) return;
19717
- }
19718
- this.currentModel = modelId;
19719
- }
19720
19812
  /** Snapshot of current ACP state for persistence */
19721
19813
  toAcpStateSnapshot() {
19722
19814
  return {
19723
- currentMode: this.currentMode,
19724
- availableModes: this.availableModes.length > 0 ? this.availableModes : void 0,
19725
19815
  configOptions: this.configOptions.length > 0 ? this.configOptions : void 0,
19726
- currentModel: this.currentModel,
19727
- availableModels: this.availableModels.length > 0 ? this.availableModels : void 0,
19728
19816
  agentCapabilities: this.agentCapabilities
19729
19817
  };
19730
19818
  }
@@ -19774,10 +19862,6 @@ ${result.text}` : result.text;
19774
19862
  this.agentSessionId = newAgent.sessionId;
19775
19863
  this.promptCount = 0;
19776
19864
  this.agentCapabilities = void 0;
19777
- this.currentMode = void 0;
19778
- this.availableModes = [];
19779
- this.currentModel = void 0;
19780
- this.availableModels = [];
19781
19865
  this.configOptions = [];
19782
19866
  this.log.info({ from: this.agentSwitchHistory.at(-1).agentName, to: agentName }, "Agent switched");
19783
19867
  }
@@ -19832,7 +19916,7 @@ var init_session_manager = __esm({
19832
19916
  createdAt: session.createdAt.toISOString(),
19833
19917
  lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
19834
19918
  name: session.name,
19835
- dangerousMode: false,
19919
+ clientOverrides: {},
19836
19920
  platform: {}
19837
19921
  });
19838
19922
  }
@@ -19964,12 +20048,26 @@ var init_session_manager = __esm({
19964
20048
  }
19965
20049
  });
19966
20050
 
20051
+ // src/core/utils/bypass-detection.ts
20052
+ function isPermissionBypass(value) {
20053
+ const lower = value.toLowerCase();
20054
+ return BYPASS_KEYWORDS.some((kw) => lower.includes(kw));
20055
+ }
20056
+ var BYPASS_KEYWORDS;
20057
+ var init_bypass_detection = __esm({
20058
+ "src/core/utils/bypass-detection.ts"() {
20059
+ "use strict";
20060
+ BYPASS_KEYWORDS = ["bypass", "dangerous", "skip", "dontask", "dont_ask", "auto_accept"];
20061
+ }
20062
+ });
20063
+
19967
20064
  // src/core/sessions/session-bridge.ts
19968
20065
  var log29, SessionBridge;
19969
20066
  var init_session_bridge = __esm({
19970
20067
  "src/core/sessions/session-bridge.ts"() {
19971
20068
  "use strict";
19972
20069
  init_log();
20070
+ init_bypass_detection();
19973
20071
  log29 = createChildLogger({ module: "session-bridge" });
19974
20072
  SessionBridge = class {
19975
20073
  constructor(session, adapter, deps) {
@@ -20128,12 +20226,12 @@ var init_session_bridge = __esm({
20128
20226
  break;
20129
20227
  case "image_content": {
20130
20228
  if (this.deps.fileService) {
20131
- const fs51 = this.deps.fileService;
20229
+ const fs53 = this.deps.fileService;
20132
20230
  const sid = this.session.id;
20133
20231
  const { data, mimeType } = event;
20134
20232
  const buffer = Buffer.from(data, "base64");
20135
- const ext = fs51.extensionFromMime(mimeType);
20136
- fs51.saveFile(sid, `agent-image${ext}`, buffer, mimeType).then((att) => {
20233
+ const ext = fs53.extensionFromMime(mimeType);
20234
+ fs53.saveFile(sid, `agent-image${ext}`, buffer, mimeType).then((att) => {
20137
20235
  this.sendMessage(sid, {
20138
20236
  type: "attachment",
20139
20237
  text: "",
@@ -20145,12 +20243,12 @@ var init_session_bridge = __esm({
20145
20243
  }
20146
20244
  case "audio_content": {
20147
20245
  if (this.deps.fileService) {
20148
- const fs51 = this.deps.fileService;
20246
+ const fs53 = this.deps.fileService;
20149
20247
  const sid = this.session.id;
20150
20248
  const { data, mimeType } = event;
20151
20249
  const buffer = Buffer.from(data, "base64");
20152
- const ext = fs51.extensionFromMime(mimeType);
20153
- fs51.saveFile(sid, `agent-audio${ext}`, buffer, mimeType).then((att) => {
20250
+ const ext = fs53.extensionFromMime(mimeType);
20251
+ fs53.saveFile(sid, `agent-audio${ext}`, buffer, mimeType).then((att) => {
20154
20252
  this.sendMessage(sid, {
20155
20253
  type: "attachment",
20156
20254
  text: "",
@@ -20175,24 +20273,12 @@ var init_session_bridge = __esm({
20175
20273
  outgoing = this.deps.messageTransformer.transform(event);
20176
20274
  this.sendMessage(this.session.id, outgoing);
20177
20275
  break;
20178
- case "current_mode_update":
20179
- this.session.updateMode(event.modeId);
20180
- this.persistAcpState();
20181
- outgoing = this.deps.messageTransformer.transform(event);
20182
- this.sendMessage(this.session.id, outgoing);
20183
- break;
20184
20276
  case "config_option_update":
20185
20277
  this.session.updateConfigOptions(event.options);
20186
20278
  this.persistAcpState();
20187
20279
  outgoing = this.deps.messageTransformer.transform(event);
20188
20280
  this.sendMessage(this.session.id, outgoing);
20189
20281
  break;
20190
- case "model_update":
20191
- this.session.updateModel(event.modelId);
20192
- this.persistAcpState();
20193
- outgoing = this.deps.messageTransformer.transform(event);
20194
- this.sendMessage(this.session.id, outgoing);
20195
- break;
20196
20282
  case "user_message_chunk":
20197
20283
  outgoing = this.deps.messageTransformer.transform(event);
20198
20284
  this.sendMessage(this.session.id, outgoing);
@@ -20212,7 +20298,7 @@ var init_session_bridge = __esm({
20212
20298
  });
20213
20299
  return outgoing;
20214
20300
  }
20215
- /** Persist current ACP state (mode, config, model) to session store as cache */
20301
+ /** Persist current ACP state (configOptions, agentCapabilities) to session store as cache */
20216
20302
  persistAcpState() {
20217
20303
  this.deps.sessionManager.patchRecord(this.session.id, {
20218
20304
  acpState: this.session.toAcpStateSnapshot()
@@ -20267,12 +20353,17 @@ var init_session_bridge = __esm({
20267
20353
  return allowOption.id;
20268
20354
  }
20269
20355
  }
20270
- if (this.session.dangerousMode) {
20356
+ const modeOption = this.session.getConfigByCategory("mode");
20357
+ const isAgentBypass = modeOption && isPermissionBypass(
20358
+ typeof modeOption.currentValue === "string" ? modeOption.currentValue : ""
20359
+ );
20360
+ const isClientBypass = this.session.clientOverrides.bypassPermissions;
20361
+ if (isAgentBypass || isClientBypass) {
20271
20362
  const allowOption = permReq.options.find((o) => o.isAllow);
20272
20363
  if (allowOption) {
20273
20364
  log29.info(
20274
- { sessionId: this.session.id, requestId: permReq.id, optionId: allowOption.id },
20275
- "Dangerous mode: auto-approving permission"
20365
+ { sessionId: this.session.id, requestId: permReq.id, optionId: allowOption.id, agentBypass: !!isAgentBypass, clientBypass: !!isClientBypass },
20366
+ "Bypass mode: auto-approving permission"
20276
20367
  );
20277
20368
  if (mw) {
20278
20369
  mw.execute("permission:afterResolve", {
@@ -20596,24 +20687,12 @@ var init_message_transformer = __esm({
20596
20687
  text: `Session updated: ${event.title ?? ""}`.trim(),
20597
20688
  metadata: { title: event.title, updatedAt: event.updatedAt }
20598
20689
  };
20599
- case "current_mode_update":
20600
- return {
20601
- type: "mode_change",
20602
- text: `Mode: ${event.modeId}`,
20603
- metadata: { modeId: event.modeId }
20604
- };
20605
20690
  case "config_option_update":
20606
20691
  return {
20607
20692
  type: "config_update",
20608
20693
  text: "Config updated",
20609
20694
  metadata: { options: event.options }
20610
20695
  };
20611
- case "model_update":
20612
- return {
20613
- type: "model_update",
20614
- text: `Model: ${event.modelId}`,
20615
- metadata: { modelId: event.modelId }
20616
- };
20617
20696
  case "user_message_chunk":
20618
20697
  return {
20619
20698
  type: "user_replay",
@@ -21009,16 +21088,14 @@ var init_session_factory = __esm({
21009
21088
  }
21010
21089
  const resp = agentInstance.initialSessionResponse;
21011
21090
  if (resp) {
21012
- session.setInitialAcpState({
21013
- modes: resp.modes ?? null,
21014
- configOptions: resp.configOptions ?? null,
21015
- models: resp.models ?? null,
21016
- agentCapabilities: agentInstance.agentCapabilities ?? null
21017
- });
21091
+ if (resp.configOptions) {
21092
+ session.setInitialConfigOptions(resp.configOptions);
21093
+ }
21094
+ if (agentInstance.agentCapabilities) {
21095
+ session.setAgentCapabilities(agentInstance.agentCapabilities);
21096
+ }
21018
21097
  } else if (agentInstance.agentCapabilities) {
21019
- session.setInitialAcpState({
21020
- agentCapabilities: agentInstance.agentCapabilities
21021
- });
21098
+ session.setAgentCapabilities(agentInstance.agentCapabilities);
21022
21099
  }
21023
21100
  this.sessionManager.registerSession(session);
21024
21101
  this.eventBus.emit("session:created", {
@@ -21068,18 +21145,21 @@ var init_session_factory = __esm({
21068
21145
  threadId
21069
21146
  });
21070
21147
  session.activate();
21071
- session.dangerousMode = record.dangerousMode ?? false;
21148
+ if (record.clientOverrides) {
21149
+ session.clientOverrides = record.clientOverrides;
21150
+ } else if (record.dangerousMode) {
21151
+ session.clientOverrides = { bypassPermissions: true };
21152
+ }
21072
21153
  if (record.firstAgent) session.firstAgent = record.firstAgent;
21073
21154
  if (record.agentSwitchHistory) session.agentSwitchHistory = record.agentSwitchHistory;
21074
21155
  if (record.currentPromptCount != null) session.promptCount = record.currentPromptCount;
21075
21156
  if (record.acpState) {
21076
- const s = record.acpState;
21077
- session.setInitialAcpState({
21078
- modes: s.currentMode && s.availableModes ? { currentModeId: s.currentMode, availableModes: s.availableModes } : null,
21079
- configOptions: s.configOptions ?? null,
21080
- models: s.currentModel && s.availableModels ? { currentModelId: s.currentModel, availableModels: s.availableModels } : null,
21081
- agentCapabilities: s.agentCapabilities ?? null
21082
- });
21157
+ if (record.acpState.configOptions) {
21158
+ session.setInitialConfigOptions(record.acpState.configOptions);
21159
+ }
21160
+ if (record.acpState.agentCapabilities) {
21161
+ session.setAgentCapabilities(record.acpState.agentCapabilities);
21162
+ }
21083
21163
  }
21084
21164
  log32.info({ sessionId: session.id, threadId }, "Lazy resume successful");
21085
21165
  return session;
@@ -22655,6 +22735,7 @@ var init_lifecycle_manager = __esm({
22655
22735
  } catch (err) {
22656
22736
  this._failed.add(plugin2.name);
22657
22737
  ctx.cleanup();
22738
+ console.error(`[lifecycle] Plugin ${plugin2.name} setup() FAILED:`, err);
22658
22739
  this.getPluginLogger(plugin2.name).error(`setup() failed: ${err}`);
22659
22740
  this.eventBus?.emit("plugin:failed", { name: plugin2.name, error: String(err) });
22660
22741
  }
@@ -22951,6 +23032,13 @@ var init_core = __esm({
22951
23032
  { channelId: message.channelId, threadId: message.threadId },
22952
23033
  "No session found for thread (in-memory miss + lazy resume returned null)"
22953
23034
  );
23035
+ const adapter = this.adapters.get(message.channelId);
23036
+ if (adapter) {
23037
+ await adapter.sendMessage(message.threadId, {
23038
+ type: "error",
23039
+ text: "\u26A0\uFE0F No active session in this topic. Use /new to start one."
23040
+ });
23041
+ }
22954
23042
  return;
22955
23043
  }
22956
23044
  this.sessionManager.patchRecord(session.id, {
@@ -23638,6 +23726,162 @@ var init_switch2 = __esm({
23638
23726
  }
23639
23727
  });
23640
23728
 
23729
+ // src/core/commands/config.ts
23730
+ function flattenChoices(options) {
23731
+ const result = [];
23732
+ for (const item of options) {
23733
+ if ("group" in item && "options" in item) {
23734
+ result.push(...item.options);
23735
+ } else {
23736
+ result.push(item);
23737
+ }
23738
+ }
23739
+ return result;
23740
+ }
23741
+ function registerCategoryCommand(registry, core, category, commandName, notSupportedMsg) {
23742
+ registry.register({
23743
+ name: commandName,
23744
+ description: `Set ${commandName} for this session`,
23745
+ usage: `[value]`,
23746
+ category: "system",
23747
+ handler: async (args2) => {
23748
+ if (!args2.sessionId) {
23749
+ return { type: "error", message: "\u26A0\uFE0F No active session." };
23750
+ }
23751
+ const session = core.sessionManager.getSession(args2.sessionId);
23752
+ if (!session) {
23753
+ return { type: "error", message: "\u26A0\uFE0F Session not found." };
23754
+ }
23755
+ const configOption = session.getConfigByCategory(category);
23756
+ if (!configOption || configOption.type !== "select") {
23757
+ return { type: "error", message: `\u26A0\uFE0F ${notSupportedMsg}` };
23758
+ }
23759
+ const choices = flattenChoices(configOption.options);
23760
+ const raw = args2.raw.trim();
23761
+ if (!raw) {
23762
+ return {
23763
+ type: "menu",
23764
+ title: configOption.name,
23765
+ options: choices.map((c3) => ({
23766
+ label: c3.value === configOption.currentValue ? `\u2705 ${c3.label}` : c3.label,
23767
+ command: `/${commandName} ${c3.value}`,
23768
+ hint: c3.description
23769
+ }))
23770
+ };
23771
+ }
23772
+ const match = choices.find((c3) => c3.value === raw);
23773
+ if (!match) {
23774
+ const valid = choices.map((c3) => c3.value).join(", ");
23775
+ return { type: "error", message: `\u26A0\uFE0F Invalid value "${raw}". Valid: ${valid}` };
23776
+ }
23777
+ if (session.middlewareChain) {
23778
+ const result = await session.middlewareChain.execute("config:beforeChange", {
23779
+ sessionId: session.id,
23780
+ configId: configOption.id,
23781
+ oldValue: configOption.currentValue,
23782
+ newValue: raw
23783
+ }, async (p2) => p2);
23784
+ if (!result) return { type: "error", message: `Config change blocked by middleware.` };
23785
+ }
23786
+ try {
23787
+ const response = await session.agentInstance.setConfigOption(
23788
+ configOption.id,
23789
+ { type: "select", value: raw }
23790
+ );
23791
+ if (response.configOptions) {
23792
+ session.configOptions = response.configOptions;
23793
+ }
23794
+ return { type: "text", text: `${configOption.name} set to ${match.label}.` };
23795
+ } catch (err) {
23796
+ const msg = err instanceof Error ? err.message : String(err);
23797
+ return { type: "error", message: `\u26A0\uFE0F Failed to set ${commandName}: ${msg}` };
23798
+ }
23799
+ }
23800
+ });
23801
+ }
23802
+ function registerDangerousCommand(registry, core) {
23803
+ registry.register({
23804
+ name: "dangerous",
23805
+ description: "Toggle dangerous mode (bypass permissions)",
23806
+ usage: "[on|off]",
23807
+ category: "system",
23808
+ handler: async (args2) => {
23809
+ if (!args2.sessionId) {
23810
+ return { type: "error", message: "\u26A0\uFE0F No active session." };
23811
+ }
23812
+ const session = core.sessionManager.getSession(args2.sessionId);
23813
+ if (!session) {
23814
+ return { type: "error", message: "\u26A0\uFE0F Session not found." };
23815
+ }
23816
+ const raw = args2.raw.trim().toLowerCase();
23817
+ const modeConfig = session.getConfigByCategory("mode");
23818
+ let bypassValue;
23819
+ let nonBypassDefault;
23820
+ if (modeConfig && modeConfig.type === "select") {
23821
+ const choices = flattenChoices(modeConfig.options);
23822
+ bypassValue = choices.find((c3) => isPermissionBypass(c3.value))?.value;
23823
+ nonBypassDefault = choices.find((c3) => !isPermissionBypass(c3.value))?.value;
23824
+ }
23825
+ const isCurrentlyDangerous = bypassValue ? modeConfig.type === "select" && isPermissionBypass(modeConfig.currentValue) : !!session.clientOverrides.bypassPermissions;
23826
+ if (!raw) {
23827
+ const status = isCurrentlyDangerous ? "on" : "off";
23828
+ const toggleCmd = isCurrentlyDangerous ? "/dangerous off" : "/dangerous on";
23829
+ const toggleLabel = isCurrentlyDangerous ? "Turn off" : "Turn on";
23830
+ return {
23831
+ type: "menu",
23832
+ title: `Dangerous mode: ${status}`,
23833
+ options: [{ label: toggleLabel, command: toggleCmd }]
23834
+ };
23835
+ }
23836
+ if (raw !== "on" && raw !== "off") {
23837
+ return { type: "error", message: "\u26A0\uFE0F Usage: /dangerous [on|off]" };
23838
+ }
23839
+ const wantOn = raw === "on";
23840
+ if (bypassValue && modeConfig) {
23841
+ try {
23842
+ const targetValue = wantOn ? bypassValue : nonBypassDefault;
23843
+ const response = await session.agentInstance.setConfigOption(
23844
+ modeConfig.id,
23845
+ { type: "select", value: targetValue }
23846
+ );
23847
+ if (response.configOptions) {
23848
+ session.configOptions = response.configOptions;
23849
+ }
23850
+ return {
23851
+ type: "text",
23852
+ text: `Dangerous mode ${wantOn ? "enabled" : "disabled"}.`
23853
+ };
23854
+ } catch (err) {
23855
+ const msg = err instanceof Error ? err.message : String(err);
23856
+ return { type: "error", message: `\u26A0\uFE0F Failed: ${msg}` };
23857
+ }
23858
+ }
23859
+ session.clientOverrides.bypassPermissions = wantOn;
23860
+ await core.sessionManager.patchRecord(session.id, {
23861
+ clientOverrides: { ...session.clientOverrides }
23862
+ });
23863
+ return {
23864
+ type: "text",
23865
+ text: `Dangerous mode ${wantOn ? "enabled" : "disabled"} (client-side bypass).`
23866
+ };
23867
+ }
23868
+ });
23869
+ }
23870
+ function registerConfigCommands(registry, _core) {
23871
+ const core = _core;
23872
+ registerCategoryCommand(registry, core, "mode", "mode", "Agent does not support mode selection.");
23873
+ registerCategoryCommand(registry, core, "model", "model", "Agent does not support model selection.");
23874
+ registerCategoryCommand(registry, core, "thought_level", "thought", "Agent does not support thought level.");
23875
+ registerDangerousCommand(registry, core);
23876
+ }
23877
+ var init_config5 = __esm({
23878
+ "src/core/commands/config.ts"() {
23879
+ "use strict";
23880
+ init_bypass_detection();
23881
+ init_bypass_detection();
23882
+ }
23883
+ });
23884
+
23641
23885
  // src/core/commands/index.ts
23642
23886
  function registerSystemCommands(registry, core) {
23643
23887
  registerSessionCommands(registry, core);
@@ -23646,6 +23890,7 @@ function registerSystemCommands(registry, core) {
23646
23890
  registerHelpCommand(registry, core);
23647
23891
  registerMenuCommand(registry, core);
23648
23892
  registerSwitchCommands(registry);
23893
+ registerConfigCommands(registry, core);
23649
23894
  }
23650
23895
  var init_commands4 = __esm({
23651
23896
  "src/core/commands/index.ts"() {
@@ -23656,6 +23901,7 @@ var init_commands4 = __esm({
23656
23901
  init_help();
23657
23902
  init_menu2();
23658
23903
  init_switch2();
23904
+ init_config5();
23659
23905
  }
23660
23906
  });
23661
23907
 
@@ -23681,17 +23927,34 @@ var init_instance_registry = __esm({
23681
23927
  const parsed = JSON.parse(raw);
23682
23928
  if (parsed.version === 1 && parsed.instances) {
23683
23929
  this.data = parsed;
23930
+ this.deduplicate();
23684
23931
  }
23685
23932
  } catch {
23686
23933
  }
23687
23934
  }
23935
+ /** Remove duplicate entries that point to the same root, keeping the first one */
23936
+ deduplicate() {
23937
+ const seen = /* @__PURE__ */ new Set();
23938
+ const toRemove = [];
23939
+ for (const [id, entry] of Object.entries(this.data.instances)) {
23940
+ if (seen.has(entry.root)) {
23941
+ toRemove.push(id);
23942
+ } else {
23943
+ seen.add(entry.root);
23944
+ }
23945
+ }
23946
+ if (toRemove.length > 0) {
23947
+ for (const id of toRemove) delete this.data.instances[id];
23948
+ this.save();
23949
+ }
23950
+ }
23688
23951
  save() {
23689
23952
  const dir = path47.dirname(this.registryPath);
23690
23953
  fs43.mkdirSync(dir, { recursive: true });
23691
23954
  fs43.writeFileSync(this.registryPath, JSON.stringify(this.data, null, 2));
23692
23955
  }
23693
- register(id, root2) {
23694
- this.data.instances[id] = { id, root: root2 };
23956
+ register(id, root) {
23957
+ this.data.instances[id] = { id, root };
23695
23958
  }
23696
23959
  remove(id) {
23697
23960
  delete this.data.instances[id];
@@ -23699,8 +23962,8 @@ var init_instance_registry = __esm({
23699
23962
  get(id) {
23700
23963
  return this.data.instances[id];
23701
23964
  }
23702
- getByRoot(root2) {
23703
- return Object.values(this.data.instances).find((e) => e.root === root2);
23965
+ getByRoot(root) {
23966
+ return Object.values(this.data.instances).find((e) => e.root === root);
23704
23967
  }
23705
23968
  list() {
23706
23969
  return Object.values(this.data.instances);
@@ -23958,14 +24221,15 @@ var init_setup_agents = __esm({
23958
24221
  // src/core/setup/setup-workspace.ts
23959
24222
  import * as clack4 from "@clack/prompts";
23960
24223
  async function setupWorkspace(opts) {
23961
- const { existing, stepNum, totalSteps } = opts ?? {};
24224
+ const { existing, stepNum, totalSteps, isGlobal } = opts ?? {};
23962
24225
  if (stepNum != null && totalSteps != null) {
23963
24226
  console.log(step(stepNum, totalSteps, "Workspace"));
23964
24227
  }
24228
+ const defaultDir = isGlobal === false ? process.cwd() : "~/openacp-workspace";
23965
24229
  const baseDir = guardCancel2(
23966
24230
  await clack4.text({
23967
- message: "Base directory for workspaces:",
23968
- initialValue: existing ?? "~/openacp-workspace",
24231
+ message: "Base directory for agent workspaces:",
24232
+ initialValue: existing ?? defaultDir,
23969
24233
  validate: (val) => (val ?? "").toString().trim().length > 0 ? void 0 : "Path cannot be empty"
23970
24234
  })
23971
24235
  );
@@ -24491,9 +24755,81 @@ var init_instance_copy = __esm({
24491
24755
  }
24492
24756
  });
24493
24757
 
24758
+ // src/core/setup/git-protect.ts
24759
+ import fs46 from "fs";
24760
+ import path51 from "path";
24761
+ function protectLocalInstance(projectDir) {
24762
+ ensureGitignore(projectDir);
24763
+ ensureClaudeMd(projectDir);
24764
+ printSecurityWarning();
24765
+ }
24766
+ function ensureGitignore(projectDir) {
24767
+ const gitignorePath = path51.join(projectDir, ".gitignore");
24768
+ const entry = ".openacp";
24769
+ if (fs46.existsSync(gitignorePath)) {
24770
+ const content = fs46.readFileSync(gitignorePath, "utf-8");
24771
+ const lines = content.split("\n").map((l) => l.trim());
24772
+ if (lines.includes(entry) || lines.includes(".openacp")) {
24773
+ return;
24774
+ }
24775
+ const separator = content.endsWith("\n") ? "" : "\n";
24776
+ fs46.appendFileSync(gitignorePath, `${separator}
24777
+ # OpenACP local workspace (contains secrets)
24778
+ ${entry}
24779
+ `);
24780
+ } else {
24781
+ fs46.writeFileSync(gitignorePath, `# OpenACP local workspace (contains secrets)
24782
+ ${entry}
24783
+ `);
24784
+ }
24785
+ }
24786
+ function ensureClaudeMd(projectDir) {
24787
+ const claudeMdPath = path51.join(projectDir, "CLAUDE.md");
24788
+ const marker = "## Local OpenACP Workspace";
24789
+ if (fs46.existsSync(claudeMdPath)) {
24790
+ const content = fs46.readFileSync(claudeMdPath, "utf-8");
24791
+ if (content.includes(marker)) {
24792
+ return;
24793
+ }
24794
+ const separator = content.endsWith("\n") ? "" : "\n";
24795
+ fs46.appendFileSync(claudeMdPath, `${separator}
24796
+ ## Local OpenACP Workspace
24797
+
24798
+ The \`.openacp/\` directory contains a local OpenACP workspace with secrets (bot tokens, API keys). Do not read, commit, or reference files inside it.
24799
+ `);
24800
+ } else {
24801
+ fs46.writeFileSync(claudeMdPath, `# CLAUDE.md
24802
+
24803
+ ## Local OpenACP Workspace
24804
+
24805
+ The \`.openacp/\` directory contains a local OpenACP workspace with secrets (bot tokens, API keys). Do not read, commit, or reference files inside it.
24806
+ `);
24807
+ }
24808
+ }
24809
+ function printSecurityWarning() {
24810
+ const red = "\x1B[1;91m";
24811
+ const yellow = "\x1B[1;33m";
24812
+ const reset = "\x1B[0m";
24813
+ const dim3 = "\x1B[2m";
24814
+ console.log("");
24815
+ console.log(`${red} \u26A0 SECURITY WARNING${reset}`);
24816
+ console.log(`${red} \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501${reset}`);
24817
+ console.log(`${yellow} .openacp/ contains bot tokens and API secrets.${reset}`);
24818
+ console.log(`${yellow} It has been added to .gitignore automatically.${reset}`);
24819
+ console.log(`${dim3} Verify before committing: git status${reset}`);
24820
+ console.log(`${red} \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501${reset}`);
24821
+ console.log("");
24822
+ }
24823
+ var init_git_protect = __esm({
24824
+ "src/core/setup/git-protect.ts"() {
24825
+ "use strict";
24826
+ }
24827
+ });
24828
+
24494
24829
  // src/core/setup/wizard.ts
24495
- import * as path51 from "path";
24496
- import * as fs46 from "fs";
24830
+ import * as path52 from "path";
24831
+ import * as fs47 from "fs";
24832
+ import * as os25 from "os";
24497
24833
  import * as clack8 from "@clack/prompts";
24498
24834
  async function fetchCommunityAdapters() {
24499
24835
  try {
@@ -24522,23 +24858,24 @@ async function runSetup(configManager, opts) {
24522
24858
  const isGlobal = instanceRoot === getGlobalRoot();
24523
24859
  let instanceName = opts?.instanceName;
24524
24860
  if (!instanceName) {
24525
- const defaultName = isGlobal ? "Main" : `openacp-${Date.now()}`;
24861
+ const defaultName = isGlobal ? "Global workspace" : path52.basename(path52.dirname(instanceRoot));
24862
+ const locationHint = isGlobal ? "global (~/.openacp)" : `local (${instanceRoot.replace(/\/.openacp$/, "").replace(os25.homedir(), "~")})`;
24526
24863
  const nameResult = await clack8.text({
24527
- message: "Give this setup a name",
24528
- defaultValue: defaultName,
24864
+ message: `Name for this workspace (${locationHint})`,
24865
+ initialValue: defaultName,
24529
24866
  validate: (v) => !v?.trim() ? "Name cannot be empty" : void 0
24530
24867
  });
24531
24868
  if (clack8.isCancel(nameResult)) return false;
24532
24869
  instanceName = nameResult.trim();
24533
24870
  }
24534
24871
  const globalRoot = getGlobalRoot();
24535
- const registryPath = path51.join(globalRoot, "instances.json");
24872
+ const registryPath = path52.join(globalRoot, "instances.json");
24536
24873
  const instanceRegistry = new InstanceRegistry(registryPath);
24537
24874
  await instanceRegistry.load();
24538
24875
  let didCopy = false;
24539
24876
  if (opts?.from) {
24540
- const fromRoot = path51.join(opts.from, ".openacp");
24541
- if (fs46.existsSync(path51.join(fromRoot, "config.json"))) {
24877
+ const fromRoot = path52.join(opts.from, ".openacp");
24878
+ if (fs47.existsSync(path52.join(fromRoot, "config.json"))) {
24542
24879
  const inheritableMap = buildInheritableKeysMap();
24543
24880
  await copyInstance(fromRoot, instanceRoot, { inheritableKeys: inheritableMap });
24544
24881
  didCopy = true;
@@ -24549,11 +24886,26 @@ async function runSetup(configManager, opts) {
24549
24886
  }
24550
24887
  if (!didCopy) {
24551
24888
  const existingInstances = instanceRegistry.list().filter(
24552
- (e) => fs46.existsSync(path51.join(e.root, "config.json")) && e.root !== instanceRoot
24889
+ (e) => fs47.existsSync(path52.join(e.root, "config.json")) && e.root !== instanceRoot
24553
24890
  );
24554
24891
  if (existingInstances.length > 0) {
24892
+ let singleLabel;
24893
+ if (existingInstances.length === 1) {
24894
+ const e = existingInstances[0];
24895
+ let name = e.id;
24896
+ try {
24897
+ const cfg = JSON.parse(fs47.readFileSync(path52.join(e.root, "config.json"), "utf-8"));
24898
+ if (cfg.instanceName) name = cfg.instanceName;
24899
+ } catch {
24900
+ }
24901
+ const isGlobalEntry = e.root === getGlobalRoot();
24902
+ const displayPath = e.root.replace(os25.homedir(), "~");
24903
+ const type = isGlobalEntry ? "global" : "local";
24904
+ singleLabel = `${name} workspace (${type} \u2014 ${displayPath})`;
24905
+ }
24906
+ const confirmMsg = singleLabel ? `Copy config from ${singleLabel}?` : "Copy config from an existing workspace?";
24555
24907
  const shouldCopy = await clack8.confirm({
24556
- message: "Use settings from an existing setup as a starting point?",
24908
+ message: confirmMsg,
24557
24909
  initialValue: true
24558
24910
  });
24559
24911
  if (clack8.isCancel(shouldCopy)) return false;
@@ -24563,16 +24915,18 @@ async function runSetup(configManager, opts) {
24563
24915
  sourceRoot = existingInstances[0].root;
24564
24916
  } else {
24565
24917
  const choice = await clack8.select({
24566
- message: "Which setup to copy from?",
24918
+ message: "Which workspace to copy from?",
24567
24919
  options: existingInstances.map((e) => {
24568
24920
  let name = e.id;
24569
24921
  try {
24570
- const cfg = JSON.parse(fs46.readFileSync(path51.join(e.root, "config.json"), "utf-8"));
24922
+ const cfg = JSON.parse(fs47.readFileSync(path52.join(e.root, "config.json"), "utf-8"));
24571
24923
  if (cfg.instanceName) name = cfg.instanceName;
24572
24924
  } catch {
24573
24925
  }
24574
- const displayPath = e.root.replace(/\/.openacp$/, "");
24575
- return { value: e.root, label: `${name} (${displayPath})` };
24926
+ const isGlobalEntry = e.root === getGlobalRoot();
24927
+ const displayPath = e.root.replace(os25.homedir(), "~");
24928
+ const type = isGlobalEntry ? "global" : "local";
24929
+ return { value: e.root, label: `${name} workspace (${type} \u2014 ${displayPath})` };
24576
24930
  })
24577
24931
  });
24578
24932
  if (clack8.isCancel(choice)) return false;
@@ -24642,10 +24996,10 @@ async function runSetup(configManager, opts) {
24642
24996
  if (channelId.startsWith("official:")) {
24643
24997
  const npmPackage = channelId.slice("official:".length);
24644
24998
  const { execFileSync: execFileSync8 } = await import("child_process");
24645
- const pluginsDir = path51.join(instanceRoot, "plugins");
24646
- const nodeModulesDir = path51.join(pluginsDir, "node_modules");
24647
- const installedPath = path51.join(nodeModulesDir, npmPackage);
24648
- if (!fs46.existsSync(installedPath)) {
24999
+ const pluginsDir = path52.join(instanceRoot, "plugins");
25000
+ const nodeModulesDir = path52.join(pluginsDir, "node_modules");
25001
+ const installedPath = path52.join(nodeModulesDir, npmPackage);
25002
+ if (!fs47.existsSync(installedPath)) {
24649
25003
  try {
24650
25004
  clack8.log.step(`Installing ${npmPackage}...`);
24651
25005
  execFileSync8("npm", ["install", npmPackage, "--prefix", pluginsDir, "--save"], {
@@ -24658,9 +25012,9 @@ async function runSetup(configManager, opts) {
24658
25012
  }
24659
25013
  }
24660
25014
  try {
24661
- const installedPkgPath = path51.join(nodeModulesDir, npmPackage, "package.json");
24662
- const installedPkg = JSON.parse(fs46.readFileSync(installedPkgPath, "utf-8"));
24663
- const pluginModule = await import(path51.join(nodeModulesDir, npmPackage, installedPkg.main ?? "dist/index.js"));
25015
+ const installedPkgPath = path52.join(nodeModulesDir, npmPackage, "package.json");
25016
+ const installedPkg = JSON.parse(fs47.readFileSync(installedPkgPath, "utf-8"));
25017
+ const pluginModule = await import(path52.join(nodeModulesDir, npmPackage, installedPkg.main ?? "dist/index.js"));
24664
25018
  const plugin2 = pluginModule.default;
24665
25019
  if (plugin2?.install) {
24666
25020
  const installCtx = createInstallContext2({
@@ -24690,8 +25044,8 @@ async function runSetup(configManager, opts) {
24690
25044
  if (channelId.startsWith("community:")) {
24691
25045
  const npmPackage = channelId.slice("community:".length);
24692
25046
  const { execFileSync: execFileSync8 } = await import("child_process");
24693
- const pluginsDir = path51.join(instanceRoot, "plugins");
24694
- const nodeModulesDir = path51.join(pluginsDir, "node_modules");
25047
+ const pluginsDir = path52.join(instanceRoot, "plugins");
25048
+ const nodeModulesDir = path52.join(pluginsDir, "node_modules");
24695
25049
  try {
24696
25050
  execFileSync8("npm", ["install", npmPackage, "--prefix", pluginsDir, "--save"], {
24697
25051
  stdio: "inherit",
@@ -24703,9 +25057,9 @@ async function runSetup(configManager, opts) {
24703
25057
  }
24704
25058
  try {
24705
25059
  const { readFileSync: readFileSync18 } = await import("fs");
24706
- const installedPkgPath = path51.join(nodeModulesDir, npmPackage, "package.json");
25060
+ const installedPkgPath = path52.join(nodeModulesDir, npmPackage, "package.json");
24707
25061
  const installedPkg = JSON.parse(readFileSync18(installedPkgPath, "utf-8"));
24708
- const pluginModule = await import(path51.join(nodeModulesDir, npmPackage, installedPkg.main ?? "dist/index.js"));
25062
+ const pluginModule = await import(path52.join(nodeModulesDir, npmPackage, installedPkg.main ?? "dist/index.js"));
24709
25063
  const plugin2 = pluginModule.default;
24710
25064
  if (plugin2?.install) {
24711
25065
  const installCtx = createInstallContext2({
@@ -24737,7 +25091,7 @@ async function runSetup(configManager, opts) {
24737
25091
  const { defaultAgent } = await setupAgents();
24738
25092
  await setupIntegrations();
24739
25093
  currentStep++;
24740
- const workspace = await setupWorkspace({ stepNum: currentStep, totalSteps });
25094
+ const workspace = await setupWorkspace({ stepNum: currentStep, totalSteps, isGlobal });
24741
25095
  let runMode = "foreground";
24742
25096
  let autoStart = false;
24743
25097
  if (!opts?.skipRunMode) {
@@ -24760,7 +25114,7 @@ async function runSetup(configManager, opts) {
24760
25114
  security,
24761
25115
  logging: {
24762
25116
  level: "info",
24763
- logDir: path51.join(instanceRoot, "logs"),
25117
+ logDir: path52.join(instanceRoot, "logs"),
24764
25118
  maxFileSize: "10m",
24765
25119
  maxFiles: 7,
24766
25120
  sessionLogRetentionDays: 30
@@ -24806,9 +25160,17 @@ async function runSetup(configManager, opts) {
24806
25160
  await registerBuiltinPlugins(settingsManager, pluginRegistry);
24807
25161
  await pluginRegistry.save();
24808
25162
  }
24809
- const id = instanceRegistry.uniqueId(generateSlug(instanceName));
24810
- instanceRegistry.register(id, instanceRoot);
24811
- await instanceRegistry.save();
25163
+ const existingEntry = instanceRegistry.getByRoot(instanceRoot);
25164
+ if (!existingEntry) {
25165
+ const id = instanceRegistry.uniqueId(generateSlug(instanceName));
25166
+ instanceRegistry.register(id, instanceRoot);
25167
+ await instanceRegistry.save();
25168
+ }
25169
+ const isLocal = instanceRoot !== path52.join(getGlobalRoot());
25170
+ if (isLocal) {
25171
+ const projectDir = path52.dirname(instanceRoot);
25172
+ protectLocalInstance(projectDir);
25173
+ }
24812
25174
  clack8.outro(`Config saved to ${configManager.getConfigPath()}`);
24813
25175
  if (!opts?.skipRunMode) {
24814
25176
  console.log(ok("Starting OpenACP..."));
@@ -24942,6 +25304,7 @@ var init_wizard = __esm({
24942
25304
  init_instance_registry();
24943
25305
  init_instance_context();
24944
25306
  init_instance_copy();
25307
+ init_git_protect();
24945
25308
  }
24946
25309
  });
24947
25310
 
@@ -25092,8 +25455,8 @@ var dev_loader_exports = {};
25092
25455
  __export(dev_loader_exports, {
25093
25456
  DevPluginLoader: () => DevPluginLoader
25094
25457
  });
25095
- import fs47 from "fs";
25096
- import path52 from "path";
25458
+ import fs48 from "fs";
25459
+ import path53 from "path";
25097
25460
  var loadCounter, DevPluginLoader;
25098
25461
  var init_dev_loader = __esm({
25099
25462
  "src/core/plugin/dev-loader.ts"() {
@@ -25102,15 +25465,15 @@ var init_dev_loader = __esm({
25102
25465
  DevPluginLoader = class {
25103
25466
  pluginPath;
25104
25467
  constructor(pluginPath) {
25105
- this.pluginPath = path52.resolve(pluginPath);
25468
+ this.pluginPath = path53.resolve(pluginPath);
25106
25469
  }
25107
25470
  async load() {
25108
- const distIndex = path52.join(this.pluginPath, "dist", "index.js");
25109
- const srcIndex = path52.join(this.pluginPath, "src", "index.ts");
25110
- if (!fs47.existsSync(distIndex) && !fs47.existsSync(srcIndex)) {
25471
+ const distIndex = path53.join(this.pluginPath, "dist", "index.js");
25472
+ const srcIndex = path53.join(this.pluginPath, "src", "index.ts");
25473
+ if (!fs48.existsSync(distIndex) && !fs48.existsSync(srcIndex)) {
25111
25474
  throw new Error(`Plugin not found at ${this.pluginPath}. Expected dist/index.js or src/index.ts`);
25112
25475
  }
25113
- if (!fs47.existsSync(distIndex)) {
25476
+ if (!fs48.existsSync(distIndex)) {
25114
25477
  throw new Error(`Built plugin not found at ${distIndex}. Run 'npm run build' first.`);
25115
25478
  }
25116
25479
  const cacheBuster = `v=${Date.now()}-${++loadCounter}`;
@@ -25125,7 +25488,7 @@ var init_dev_loader = __esm({
25125
25488
  return this.pluginPath;
25126
25489
  }
25127
25490
  getDistPath() {
25128
- return path52.join(this.pluginPath, "dist");
25491
+ return path53.join(this.pluginPath, "dist");
25129
25492
  }
25130
25493
  };
25131
25494
  }
@@ -25137,8 +25500,8 @@ __export(main_exports, {
25137
25500
  RESTART_EXIT_CODE: () => RESTART_EXIT_CODE,
25138
25501
  startServer: () => startServer
25139
25502
  });
25140
- import path53 from "path";
25141
- import fs48 from "fs";
25503
+ import path54 from "path";
25504
+ import fs49 from "fs";
25142
25505
  async function startServer(opts) {
25143
25506
  const ctx = opts?.instanceContext ?? createInstanceContext({
25144
25507
  id: "main",
@@ -25219,25 +25582,25 @@ async function startServer(opts) {
25219
25582
  try {
25220
25583
  let modulePath;
25221
25584
  if (name.startsWith("/") || name.startsWith(".")) {
25222
- const resolved = path53.resolve(name);
25223
- const pkgPath = path53.join(resolved, "package.json");
25224
- const pkg = JSON.parse(await fs48.promises.readFile(pkgPath, "utf-8"));
25225
- modulePath = path53.join(resolved, pkg.main || "dist/index.js");
25585
+ const resolved = path54.resolve(name);
25586
+ const pkgPath = path54.join(resolved, "package.json");
25587
+ const pkg = JSON.parse(await fs49.promises.readFile(pkgPath, "utf-8"));
25588
+ modulePath = path54.join(resolved, pkg.main || "dist/index.js");
25226
25589
  } else {
25227
- const nodeModulesDir = path53.join(ctx.paths.plugins, "node_modules");
25228
- let pkgDir = path53.join(nodeModulesDir, name);
25229
- if (!fs48.existsSync(path53.join(pkgDir, "package.json"))) {
25590
+ const nodeModulesDir = path54.join(ctx.paths.plugins, "node_modules");
25591
+ let pkgDir = path54.join(nodeModulesDir, name);
25592
+ if (!fs49.existsSync(path54.join(pkgDir, "package.json"))) {
25230
25593
  let found = false;
25231
- const scopes = fs48.existsSync(nodeModulesDir) ? fs48.readdirSync(nodeModulesDir).filter((d) => d.startsWith("@")) : [];
25594
+ const scopes = fs49.existsSync(nodeModulesDir) ? fs49.readdirSync(nodeModulesDir).filter((d) => d.startsWith("@")) : [];
25232
25595
  for (const scope of scopes) {
25233
- const scopeDir = path53.join(nodeModulesDir, scope);
25234
- const pkgs = fs48.readdirSync(scopeDir);
25596
+ const scopeDir = path54.join(nodeModulesDir, scope);
25597
+ const pkgs = fs49.readdirSync(scopeDir);
25235
25598
  for (const pkg2 of pkgs) {
25236
- const candidateDir = path53.join(scopeDir, pkg2);
25237
- const candidatePkgPath = path53.join(candidateDir, "package.json");
25238
- if (fs48.existsSync(candidatePkgPath)) {
25599
+ const candidateDir = path54.join(scopeDir, pkg2);
25600
+ const candidatePkgPath = path54.join(candidateDir, "package.json");
25601
+ if (fs49.existsSync(candidatePkgPath)) {
25239
25602
  try {
25240
- const candidatePkg = JSON.parse(fs48.readFileSync(candidatePkgPath, "utf-8"));
25603
+ const candidatePkg = JSON.parse(fs49.readFileSync(candidatePkgPath, "utf-8"));
25241
25604
  const pluginName = candidatePkg.openacp?.pluginName;
25242
25605
  if (pluginName === name) {
25243
25606
  pkgDir = candidateDir;
@@ -25251,9 +25614,9 @@ async function startServer(opts) {
25251
25614
  if (found) break;
25252
25615
  }
25253
25616
  }
25254
- const pkgJsonPath = path53.join(pkgDir, "package.json");
25255
- const pkg = JSON.parse(await fs48.promises.readFile(pkgJsonPath, "utf-8"));
25256
- modulePath = path53.join(pkgDir, pkg.main || "dist/index.js");
25617
+ const pkgJsonPath = path54.join(pkgDir, "package.json");
25618
+ const pkg = JSON.parse(await fs49.promises.readFile(pkgJsonPath, "utf-8"));
25619
+ modulePath = path54.join(pkgDir, pkg.main || "dist/index.js");
25257
25620
  }
25258
25621
  log.debug({ plugin: name, modulePath }, "Loading community plugin");
25259
25622
  const mod = await import(modulePath);
@@ -25284,7 +25647,7 @@ async function startServer(opts) {
25284
25647
  if (!opts.noWatch) {
25285
25648
  const distPath = devLoader.getDistPath();
25286
25649
  let reloadTimer = null;
25287
- fs48.watch(distPath, { recursive: true }, (_eventType, filename) => {
25650
+ fs49.watch(distPath, { recursive: true }, (_eventType, filename) => {
25288
25651
  if (!filename?.endsWith(".js")) return;
25289
25652
  if (reloadTimer) clearTimeout(reloadTimer);
25290
25653
  reloadTimer = setTimeout(async () => {
@@ -25362,21 +25725,21 @@ async function startServer(opts) {
25362
25725
  if (isDaemon) {
25363
25726
  const { spawn: spawnChild } = await import("child_process");
25364
25727
  const { expandHome: expandHome4 } = await Promise.resolve().then(() => (init_config2(), config_exports));
25365
- const fs51 = await import("fs");
25728
+ const fs53 = await import("fs");
25366
25729
  const pathMod2 = await import("path");
25367
25730
  const cliPath = pathMod2.resolve(process.argv[1]);
25368
25731
  const resolvedLogDir = expandHome4(config.logging.logDir);
25369
- fs51.mkdirSync(resolvedLogDir, { recursive: true });
25732
+ fs53.mkdirSync(resolvedLogDir, { recursive: true });
25370
25733
  const logFile = pathMod2.join(resolvedLogDir, "openacp.log");
25371
- const out = fs51.openSync(logFile, "a");
25372
- const err = fs51.openSync(logFile, "a");
25734
+ const out = fs53.openSync(logFile, "a");
25735
+ const err = fs53.openSync(logFile, "a");
25373
25736
  const child = spawnChild(process.execPath, [cliPath, "--daemon-child"], {
25374
25737
  detached: true,
25375
25738
  stdio: ["ignore", out, err],
25376
25739
  env: { ...process.env, OPENACP_SKIP_UPDATE_CHECK: "1" }
25377
25740
  });
25378
- fs51.closeSync(out);
25379
- fs51.closeSync(err);
25741
+ fs53.closeSync(out);
25742
+ fs53.closeSync(err);
25380
25743
  child.unref();
25381
25744
  log.info({ newPid: child.pid }, "Respawned daemon for restart");
25382
25745
  } else if (!process.env.OPENACP_DEV_LOOP) {
@@ -25405,7 +25768,7 @@ async function startServer(opts) {
25405
25768
  await core.start();
25406
25769
  try {
25407
25770
  const globalRoot = getGlobalRoot();
25408
- const registryPath = path53.join(globalRoot, "instances.json");
25771
+ const registryPath = path54.join(globalRoot, "instances.json");
25409
25772
  const instanceReg = new InstanceRegistry(registryPath);
25410
25773
  await instanceReg.load();
25411
25774
  if (!instanceReg.getByRoot(ctx.root)) {
@@ -25553,10 +25916,10 @@ var restart_exports = {};
25553
25916
  __export(restart_exports, {
25554
25917
  cmdRestart: () => cmdRestart
25555
25918
  });
25556
- import path54 from "path";
25557
- import os25 from "os";
25919
+ import path55 from "path";
25920
+ import os26 from "os";
25558
25921
  async function cmdRestart(args2 = [], instanceRoot) {
25559
- const root2 = instanceRoot ?? path54.join(os25.homedir(), ".openacp");
25922
+ const root = instanceRoot ?? path55.join(os26.homedir(), ".openacp");
25560
25923
  if (wantsHelp(args2)) {
25561
25924
  console.log(`
25562
25925
  \x1B[1mopenacp restart\x1B[0m \u2014 Restart the background daemon
@@ -25581,8 +25944,8 @@ Stops the running daemon (if any) and starts a new one.
25581
25944
  const { ConfigManager: ConfigManager2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
25582
25945
  const { checkAndPromptUpdate: checkAndPromptUpdate2 } = await Promise.resolve().then(() => (init_version(), version_exports));
25583
25946
  await checkAndPromptUpdate2();
25584
- const pidPath = getPidPath2(root2);
25585
- const stopResult = await stopDaemon2(pidPath, root2);
25947
+ const pidPath = getPidPath2(root);
25948
+ const stopResult = await stopDaemon2(pidPath, root);
25586
25949
  if (stopResult.stopped) {
25587
25950
  console.log(`Stopped daemon (was PID ${stopResult.pid})`);
25588
25951
  }
@@ -25595,23 +25958,23 @@ Stops the running daemon (if any) and starts a new one.
25595
25958
  const config = cm.get();
25596
25959
  const useForeground = forceForeground || !forceDaemon && config.runMode !== "daemon";
25597
25960
  if (useForeground) {
25598
- markRunning2(root2);
25599
- printInstanceHint(root2);
25961
+ markRunning2(root);
25962
+ printInstanceHint(root);
25600
25963
  console.log("Starting in foreground mode...");
25601
25964
  const { startServer: startServer2 } = await Promise.resolve().then(() => (init_main(), main_exports));
25602
25965
  const ctx = createInstanceContext({
25603
25966
  id: "default",
25604
- root: root2,
25605
- isGlobal: root2 === getGlobalRoot()
25967
+ root,
25968
+ isGlobal: root === getGlobalRoot()
25606
25969
  });
25607
25970
  await startServer2({ instanceContext: ctx });
25608
25971
  } else {
25609
- const result = startDaemon2(pidPath, config.logging.logDir, root2);
25972
+ const result = startDaemon2(pidPath, config.logging.logDir, root);
25610
25973
  if ("error" in result) {
25611
25974
  console.error(result.error);
25612
25975
  process.exit(1);
25613
25976
  }
25614
- printInstanceHint(root2);
25977
+ printInstanceHint(root);
25615
25978
  console.log(`OpenACP daemon started (PID ${result.pid})`);
25616
25979
  }
25617
25980
  }
@@ -25631,9 +25994,9 @@ __export(status_exports, {
25631
25994
  formatInstanceStatus: () => formatInstanceStatus,
25632
25995
  readInstanceInfo: () => readInstanceInfo
25633
25996
  });
25634
- import fs49 from "fs";
25635
- import path55 from "path";
25636
- import os26 from "os";
25997
+ import fs50 from "fs";
25998
+ import path56 from "path";
25999
+ import os27 from "os";
25637
26000
  async function cmdStatus(args2 = [], instanceRoot) {
25638
26001
  if (args2.includes("--all")) {
25639
26002
  await showAllInstances();
@@ -25644,16 +26007,16 @@ async function cmdStatus(args2 = [], instanceRoot) {
25644
26007
  await showInstanceById(args2[idIdx + 1]);
25645
26008
  return;
25646
26009
  }
25647
- const root2 = instanceRoot ?? getGlobalRoot();
25648
- await showSingleInstance(root2);
26010
+ const root = instanceRoot ?? getGlobalRoot();
26011
+ await showSingleInstance(root);
25649
26012
  }
25650
26013
  async function showAllInstances() {
25651
- const registryPath = path55.join(getGlobalRoot(), "instances.json");
26014
+ const registryPath = path56.join(getGlobalRoot(), "instances.json");
25652
26015
  const registry = new InstanceRegistry(registryPath);
25653
26016
  await registry.load();
25654
26017
  const instances = registry.list();
25655
26018
  if (instances.length === 0) {
25656
- console.log("No instances registered.");
26019
+ console.log("No workspaces registered.");
25657
26020
  return;
25658
26021
  }
25659
26022
  console.log("");
@@ -25665,7 +26028,7 @@ async function showAllInstances() {
25665
26028
  const mode = info.pid ? info.runMode === "daemon" ? "daemon" : "fg" : "\u2014";
25666
26029
  const api = info.apiPort ? String(info.apiPort) : "\u2014";
25667
26030
  const tunnel = info.tunnelPort ? String(info.tunnelPort) : "\u2014";
25668
- const dir = entry.root.replace(/\/.openacp$/, "").replace(os26.homedir(), "~");
26031
+ const dir = entry.root.replace(/\/.openacp$/, "").replace(os27.homedir(), "~");
25669
26032
  const channels = info.channels.join(", ") || "\u2014";
25670
26033
  const name = info.name ?? entry.id;
25671
26034
  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}`);
@@ -25673,18 +26036,18 @@ async function showAllInstances() {
25673
26036
  console.log("");
25674
26037
  }
25675
26038
  async function showInstanceById(id) {
25676
- const registryPath = path55.join(getGlobalRoot(), "instances.json");
26039
+ const registryPath = path56.join(getGlobalRoot(), "instances.json");
25677
26040
  const registry = new InstanceRegistry(registryPath);
25678
26041
  await registry.load();
25679
26042
  const entry = registry.get(id);
25680
26043
  if (!entry) {
25681
- console.error(`Instance "${id}" not found.`);
26044
+ console.error(`Workspace "${id}" not found.`);
25682
26045
  process.exit(1);
25683
26046
  }
25684
26047
  await showSingleInstance(entry.root);
25685
26048
  }
25686
- async function showSingleInstance(root2) {
25687
- const info = readInstanceInfo(root2);
26049
+ async function showSingleInstance(root) {
26050
+ const info = readInstanceInfo(root);
25688
26051
  if (info.pid) {
25689
26052
  console.log(`OpenACP is running (PID ${info.pid})`);
25690
26053
  if (info.name) console.log(` Name: ${info.name}`);
@@ -25695,7 +26058,7 @@ async function showSingleInstance(root2) {
25695
26058
  console.log("OpenACP is not running.");
25696
26059
  }
25697
26060
  }
25698
- function readInstanceInfo(root2) {
26061
+ function readInstanceInfo(root) {
25699
26062
  const result = {
25700
26063
  name: null,
25701
26064
  pid: null,
@@ -25705,13 +26068,13 @@ function readInstanceInfo(root2) {
25705
26068
  channels: []
25706
26069
  };
25707
26070
  try {
25708
- const config = JSON.parse(fs49.readFileSync(path55.join(root2, "config.json"), "utf-8"));
26071
+ const config = JSON.parse(fs50.readFileSync(path56.join(root, "config.json"), "utf-8"));
25709
26072
  result.name = config.instanceName ?? null;
25710
26073
  result.runMode = config.runMode ?? null;
25711
26074
  } catch {
25712
26075
  }
25713
26076
  try {
25714
- const pid = parseInt(fs49.readFileSync(path55.join(root2, "openacp.pid"), "utf-8").trim());
26077
+ const pid = parseInt(fs50.readFileSync(path56.join(root, "openacp.pid"), "utf-8").trim());
25715
26078
  if (!isNaN(pid)) {
25716
26079
  process.kill(pid, 0);
25717
26080
  result.pid = pid;
@@ -25719,19 +26082,19 @@ function readInstanceInfo(root2) {
25719
26082
  } catch {
25720
26083
  }
25721
26084
  try {
25722
- const port = parseInt(fs49.readFileSync(path55.join(root2, "api.port"), "utf-8").trim());
26085
+ const port = parseInt(fs50.readFileSync(path56.join(root, "api.port"), "utf-8").trim());
25723
26086
  if (!isNaN(port)) result.apiPort = port;
25724
26087
  } catch {
25725
26088
  }
25726
26089
  try {
25727
- const tunnels = JSON.parse(fs49.readFileSync(path55.join(root2, "tunnels.json"), "utf-8"));
26090
+ const tunnels = JSON.parse(fs50.readFileSync(path56.join(root, "tunnels.json"), "utf-8"));
25728
26091
  const entries = Object.values(tunnels);
25729
26092
  const systemEntry = entries.find((t) => t.type === "system");
25730
26093
  if (systemEntry?.port) result.tunnelPort = systemEntry.port;
25731
26094
  } catch {
25732
26095
  }
25733
26096
  try {
25734
- const plugins = JSON.parse(fs49.readFileSync(path55.join(root2, "plugins.json"), "utf-8"));
26097
+ const plugins = JSON.parse(fs50.readFileSync(path56.join(root, "plugins.json"), "utf-8"));
25735
26098
  const adapters = ["@openacp/telegram", "@openacp/discord", "@openacp/slack"];
25736
26099
  for (const name of adapters) {
25737
26100
  if (plugins.installed?.[name] && plugins.installed[name].enabled !== false) {
@@ -25742,15 +26105,15 @@ function readInstanceInfo(root2) {
25742
26105
  }
25743
26106
  return result;
25744
26107
  }
25745
- function formatInstanceStatus(root2) {
25746
- const info = readInstanceInfo(root2);
26108
+ function formatInstanceStatus(root) {
26109
+ const info = readInstanceInfo(root);
25747
26110
  if (!info.pid) return null;
25748
- const displayRoot = root2.replace(os26.homedir(), "~");
25749
- const isGlobal = root2 === getGlobalRoot();
26111
+ const isGlobal = root === getGlobalRoot();
26112
+ const displayPath = root.replace(os27.homedir(), "~");
25750
26113
  const label = isGlobal ? "global" : "local";
25751
26114
  const lines = [];
25752
26115
  lines.push(` PID: ${info.pid}`);
25753
- lines.push(` Instance: ${displayRoot} (${label})`);
26116
+ lines.push(` Workspace: ${info.name ?? "unknown"} (${label} \u2014 ${displayPath})`);
25754
26117
  lines.push(` Mode: ${info.runMode ?? "unknown"}`);
25755
26118
  if (info.channels.length > 0) lines.push(` Channels: ${info.channels.join(", ")}`);
25756
26119
  if (info.apiPort) lines.push(` API: port ${info.apiPort}`);
@@ -25814,7 +26177,7 @@ var config_editor_exports = {};
25814
26177
  __export(config_editor_exports, {
25815
26178
  runConfigEditor: () => runConfigEditor
25816
26179
  });
25817
- import * as path56 from "path";
26180
+ import * as path57 from "path";
25818
26181
  import * as clack9 from "@clack/prompts";
25819
26182
  async function select8(opts) {
25820
26183
  const result = await clack9.select({
@@ -25974,14 +26337,14 @@ async function editDiscord(_config, _updates) {
25974
26337
  const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
25975
26338
  const { createInstallContext: createInstallContext2 } = await Promise.resolve().then(() => (init_install_context(), install_context_exports));
25976
26339
  const { getGlobalRoot: getGlobalRoot2 } = await Promise.resolve().then(() => (init_instance_context(), instance_context_exports));
25977
- const root2 = getGlobalRoot2();
25978
- const basePath = path56.join(root2, "plugins");
26340
+ const root = getGlobalRoot2();
26341
+ const basePath = path57.join(root, "plugins");
25979
26342
  const settingsManager = new SettingsManager2(basePath);
25980
26343
  const ctx = createInstallContext2({
25981
26344
  pluginName: plugin2.name,
25982
26345
  settingsManager,
25983
26346
  basePath,
25984
- instanceRoot: root2
26347
+ instanceRoot: root
25985
26348
  });
25986
26349
  await plugin2.configure(ctx);
25987
26350
  } else {
@@ -26438,17 +26801,17 @@ ${c2.cyan}${c2.bold}OpenACP Config Editor${c2.reset}`);
26438
26801
  async function sendConfigViaApi(port, updates) {
26439
26802
  const { apiCall: call } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
26440
26803
  const paths = flattenToPaths(updates);
26441
- for (const { path: path63, value } of paths) {
26804
+ for (const { path: path65, value } of paths) {
26442
26805
  const res = await call(port, "/api/config", {
26443
26806
  method: "PATCH",
26444
26807
  headers: { "Content-Type": "application/json" },
26445
- body: JSON.stringify({ path: path63, value })
26808
+ body: JSON.stringify({ path: path65, value })
26446
26809
  });
26447
26810
  const data = await res.json();
26448
26811
  if (!res.ok) {
26449
- console.log(warn2(`Failed to update ${path63}: ${data.error}`));
26812
+ console.log(warn2(`Failed to update ${path65}: ${data.error}`));
26450
26813
  } else if (data.needsRestart) {
26451
- console.log(warn2(`${path63} updated \u2014 restart required`));
26814
+ console.log(warn2(`${path65} updated \u2014 restart required`));
26452
26815
  }
26453
26816
  }
26454
26817
  }
@@ -26551,9 +26914,78 @@ var init_interactive_menu = __esm({
26551
26914
  }
26552
26915
  });
26553
26916
 
26917
+ // src/cli/instance-prompt.ts
26918
+ var instance_prompt_exports = {};
26919
+ __export(instance_prompt_exports, {
26920
+ promptForInstance: () => promptForInstance
26921
+ });
26922
+ import fs52 from "fs";
26923
+ import path63 from "path";
26924
+ import os31 from "os";
26925
+ async function promptForInstance(opts) {
26926
+ const globalRoot = getGlobalRoot();
26927
+ const globalConfigExists = fs52.existsSync(path63.join(globalRoot, "config.json"));
26928
+ const cwd = process.cwd();
26929
+ const localRoot = path63.join(cwd, ".openacp");
26930
+ if (!globalConfigExists) return globalRoot;
26931
+ const isTTY = process.stdin.isTTY && process.stdout.isTTY;
26932
+ if (!isTTY) return globalRoot;
26933
+ const registryPath = path63.join(globalRoot, "instances.json");
26934
+ const registry = new InstanceRegistry(registryPath);
26935
+ registry.load();
26936
+ const instances = registry.list().filter((e) => fs52.existsSync(e.root));
26937
+ const instanceOptions = instances.map((e) => {
26938
+ let name = e.id;
26939
+ try {
26940
+ const raw = fs52.readFileSync(path63.join(e.root, "config.json"), "utf-8");
26941
+ const parsed = JSON.parse(raw);
26942
+ if (parsed.instanceName) name = parsed.instanceName;
26943
+ } catch {
26944
+ }
26945
+ const isGlobal = e.root === globalRoot;
26946
+ const displayPath = e.root.replace(os31.homedir(), "~");
26947
+ const type = isGlobal ? "global" : "local";
26948
+ return { value: e.root, label: `${name} workspace (${type} \u2014 ${displayPath})` };
26949
+ });
26950
+ if (instanceOptions.length === 0) {
26951
+ const globalDisplay = globalRoot.replace(os31.homedir(), "~");
26952
+ instanceOptions.push({ value: globalRoot, label: `Global workspace (${globalDisplay})` });
26953
+ }
26954
+ if (instanceOptions.length === 1 && !opts.allowCreate) {
26955
+ return instanceOptions[0].value;
26956
+ }
26957
+ const options = instanceOptions.map((o) => ({
26958
+ value: o.value,
26959
+ label: o.label
26960
+ }));
26961
+ if (opts.allowCreate) {
26962
+ const localDisplay = localRoot.replace(os31.homedir(), "~");
26963
+ options.push({ value: localRoot, label: `New local workspace (${localDisplay})` });
26964
+ }
26965
+ const clack10 = await import("@clack/prompts");
26966
+ const choice = await clack10.select({
26967
+ message: "How would you like to run OpenACP?",
26968
+ options
26969
+ });
26970
+ if (clack10.isCancel(choice)) {
26971
+ process.exit(0);
26972
+ }
26973
+ if (choice === localRoot) {
26974
+ console.log(`\x1B[2mTip: next time use \`openacp --local\`\x1B[0m`);
26975
+ }
26976
+ return choice;
26977
+ }
26978
+ var init_instance_prompt = __esm({
26979
+ "src/cli/instance-prompt.ts"() {
26980
+ "use strict";
26981
+ init_instance_context();
26982
+ init_instance_registry();
26983
+ }
26984
+ });
26985
+
26554
26986
  // src/cli.ts
26555
26987
  import { setDefaultAutoSelectFamily } from "net";
26556
- import path62 from "path";
26988
+ import path64 from "path";
26557
26989
 
26558
26990
  // src/cli/commands/help.ts
26559
26991
  function printHelp() {
@@ -26633,12 +27065,12 @@ Connect messaging platforms (Telegram, Discord) to 28+ AI coding agents via ACP
26633
27065
  openacp api health System health check
26634
27066
  openacp api restart Restart daemon
26635
27067
 
26636
- \x1B[1mInstance Flags:\x1B[0m
26637
- --local Use setup in current directory
26638
- --global Use main setup (~/.openacp)
26639
- --dir <path> Use setup in specified directory
26640
- --from <path> Copy settings from existing setup (on create)
26641
- --name <name> Set instance name (on create)
27068
+ \x1B[1mWorkspace Flags:\x1B[0m
27069
+ --local Use workspace in current directory
27070
+ --global Use global workspace (~/.openacp)
27071
+ --dir <path> Use workspace at specified directory
27072
+ --from <path> Copy settings from existing workspace (on create)
27073
+ --name <name> Set workspace name (on create)
26642
27074
 
26643
27075
  \x1B[2mMore info: https://github.com/Open-ACP/OpenACP\x1B[0m
26644
27076
  `);
@@ -26657,8 +27089,8 @@ import * as fs from "fs";
26657
27089
  import * as path from "path";
26658
27090
  import * as os from "os";
26659
27091
  async function cmdInstall(args2, instanceRoot) {
26660
- const root2 = instanceRoot ?? path.join(os.homedir(), ".openacp");
26661
- const pluginsDir = path.join(root2, "plugins");
27092
+ const root = instanceRoot ?? path.join(os.homedir(), ".openacp");
27093
+ const pluginsDir = path.join(root, "plugins");
26662
27094
  if (wantsHelp(args2)) {
26663
27095
  console.log(`
26664
27096
  \x1B[1mopenacp install\x1B[0m \u2014 Install a plugin adapter
@@ -26698,8 +27130,8 @@ import * as fs2 from "fs";
26698
27130
  import * as path2 from "path";
26699
27131
  import * as os2 from "os";
26700
27132
  async function cmdUninstall(args2, instanceRoot) {
26701
- const root2 = instanceRoot ?? path2.join(os2.homedir(), ".openacp");
26702
- const pluginsDir = path2.join(root2, "plugins");
27133
+ const root = instanceRoot ?? path2.join(os2.homedir(), ".openacp");
27134
+ const pluginsDir = path2.join(root, "plugins");
26703
27135
  if (wantsHelp(args2)) {
26704
27136
  console.log(`
26705
27137
  \x1B[1mopenacp uninstall\x1B[0m \u2014 Remove a plugin adapter
@@ -26744,11 +27176,11 @@ Shows all plugins registered in the plugin registry.
26744
27176
  `);
26745
27177
  return;
26746
27178
  }
26747
- const os30 = await import("os");
26748
- const path63 = await import("path");
27179
+ const os32 = await import("os");
27180
+ const path65 = await import("path");
26749
27181
  const { PluginRegistry: PluginRegistry2 } = await Promise.resolve().then(() => (init_plugin_registry(), plugin_registry_exports));
26750
- const root2 = instanceRoot ?? path63.join(os30.homedir(), ".openacp");
26751
- const registryPath = path63.join(root2, "plugins.json");
27182
+ const root = instanceRoot ?? path65.join(os32.homedir(), ".openacp");
27183
+ const registryPath = path65.join(root, "plugins.json");
26752
27184
  const registry = new PluginRegistry2(registryPath);
26753
27185
  await registry.load();
26754
27186
  const plugins = registry.list();
@@ -26859,11 +27291,11 @@ async function cmdPlugin(args2 = [], instanceRoot) {
26859
27291
  }
26860
27292
  }
26861
27293
  async function setPluginEnabled(name, enabled, instanceRoot) {
26862
- const os30 = await import("os");
26863
- const path63 = await import("path");
27294
+ const os32 = await import("os");
27295
+ const path65 = await import("path");
26864
27296
  const { PluginRegistry: PluginRegistry2 } = await Promise.resolve().then(() => (init_plugin_registry(), plugin_registry_exports));
26865
- const root2 = instanceRoot ?? path63.join(os30.homedir(), ".openacp");
26866
- const registryPath = path63.join(root2, "plugins.json");
27297
+ const root = instanceRoot ?? path65.join(os32.homedir(), ".openacp");
27298
+ const registryPath = path65.join(root, "plugins.json");
26867
27299
  const registry = new PluginRegistry2(registryPath);
26868
27300
  await registry.load();
26869
27301
  const entry = registry.get(name);
@@ -26876,8 +27308,8 @@ async function setPluginEnabled(name, enabled, instanceRoot) {
26876
27308
  console.log(`Plugin ${name} ${enabled ? "enabled" : "disabled"}. Restart to apply.`);
26877
27309
  }
26878
27310
  async function configurePlugin(name, instanceRoot) {
26879
- const os30 = await import("os");
26880
- const path63 = await import("path");
27311
+ const os32 = await import("os");
27312
+ const path65 = await import("path");
26881
27313
  const { corePlugins: corePlugins2 } = await Promise.resolve().then(() => (init_core_plugins(), core_plugins_exports));
26882
27314
  const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
26883
27315
  const { createInstallContext: createInstallContext2 } = await Promise.resolve().then(() => (init_install_context(), install_context_exports));
@@ -26886,8 +27318,8 @@ async function configurePlugin(name, instanceRoot) {
26886
27318
  console.error(`Plugin "${name}" not found.`);
26887
27319
  process.exit(1);
26888
27320
  }
26889
- const root2 = instanceRoot ?? path63.join(os30.homedir(), ".openacp");
26890
- const basePath = path63.join(root2, "plugins", "data");
27321
+ const root = instanceRoot ?? path65.join(os32.homedir(), ".openacp");
27322
+ const basePath = path65.join(root, "plugins", "data");
26891
27323
  const settingsManager = new SettingsManager2(basePath);
26892
27324
  const ctx = createInstallContext2({ pluginName: name, settingsManager, basePath });
26893
27325
  if (plugin2.configure) {
@@ -26899,14 +27331,14 @@ async function configurePlugin(name, instanceRoot) {
26899
27331
  }
26900
27332
  }
26901
27333
  async function installPlugin(input2, instanceRoot) {
26902
- const os30 = await import("os");
26903
- const path63 = await import("path");
27334
+ const os32 = await import("os");
27335
+ const path65 = await import("path");
26904
27336
  const { execFileSync: execFileSync8 } = await import("child_process");
26905
27337
  const { getCurrentVersion: getCurrentVersion2 } = await Promise.resolve().then(() => (init_version(), version_exports));
26906
27338
  const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
26907
27339
  const { createInstallContext: createInstallContext2 } = await Promise.resolve().then(() => (init_install_context(), install_context_exports));
26908
27340
  const { PluginRegistry: PluginRegistry2 } = await Promise.resolve().then(() => (init_plugin_registry(), plugin_registry_exports));
26909
- const root2 = instanceRoot ?? path63.join(os30.homedir(), ".openacp");
27341
+ const root = instanceRoot ?? path65.join(os32.homedir(), ".openacp");
26910
27342
  let pkgName;
26911
27343
  let pkgVersion;
26912
27344
  if (input2.startsWith("@")) {
@@ -26951,9 +27383,9 @@ async function installPlugin(input2, instanceRoot) {
26951
27383
  console.log(`Installing ${installSpec}...`);
26952
27384
  const { corePlugins: corePlugins2 } = await Promise.resolve().then(() => (init_core_plugins(), core_plugins_exports));
26953
27385
  const builtinPlugin = corePlugins2.find((p2) => p2.name === pkgName);
26954
- const basePath = path63.join(root2, "plugins", "data");
27386
+ const basePath = path65.join(root, "plugins", "data");
26955
27387
  const settingsManager = new SettingsManager2(basePath);
26956
- const registryPath = path63.join(root2, "plugins.json");
27388
+ const registryPath = path65.join(root, "plugins.json");
26957
27389
  const pluginRegistry = new PluginRegistry2(registryPath);
26958
27390
  await pluginRegistry.load();
26959
27391
  if (builtinPlugin) {
@@ -26972,8 +27404,8 @@ async function installPlugin(input2, instanceRoot) {
26972
27404
  console.log(`\u2713 ${builtinPlugin.name} installed! Restart to activate.`);
26973
27405
  return;
26974
27406
  }
26975
- const pluginsDir = path63.join(root2, "plugins");
26976
- const nodeModulesDir = path63.join(pluginsDir, "node_modules");
27407
+ const pluginsDir = path65.join(root, "plugins");
27408
+ const nodeModulesDir = path65.join(pluginsDir, "node_modules");
26977
27409
  try {
26978
27410
  execFileSync8("npm", ["install", installSpec, "--prefix", pluginsDir, "--save"], {
26979
27411
  stdio: "inherit",
@@ -26986,8 +27418,8 @@ async function installPlugin(input2, instanceRoot) {
26986
27418
  const cliVersion = getCurrentVersion2();
26987
27419
  const isLocalPath = pkgName.startsWith("/") || pkgName.startsWith(".");
26988
27420
  try {
26989
- const pluginRoot = isLocalPath ? path63.resolve(pkgName) : path63.join(nodeModulesDir, pkgName);
26990
- const installedPkgPath = path63.join(pluginRoot, "package.json");
27421
+ const pluginRoot = isLocalPath ? path65.resolve(pkgName) : path65.join(nodeModulesDir, pkgName);
27422
+ const installedPkgPath = path65.join(pluginRoot, "package.json");
26991
27423
  const { readFileSync: readFileSync18 } = await import("fs");
26992
27424
  const installedPkg = JSON.parse(readFileSync18(installedPkgPath, "utf-8"));
26993
27425
  const minVersion = installedPkg.engines?.openacp?.replace(/[>=^~\s]/g, "");
@@ -27000,7 +27432,7 @@ async function installPlugin(input2, instanceRoot) {
27000
27432
  `);
27001
27433
  }
27002
27434
  }
27003
- const pluginModule = await import(path63.join(pluginRoot, installedPkg.main ?? "dist/index.js"));
27435
+ const pluginModule = await import(path65.join(pluginRoot, installedPkg.main ?? "dist/index.js"));
27004
27436
  const plugin2 = pluginModule.default;
27005
27437
  if (plugin2?.install) {
27006
27438
  const ctx = createInstallContext2({ pluginName: plugin2.name ?? pkgName, settingsManager, basePath });
@@ -27027,12 +27459,12 @@ async function installPlugin(input2, instanceRoot) {
27027
27459
  }
27028
27460
  }
27029
27461
  async function uninstallPlugin(name, purge, instanceRoot) {
27030
- const os30 = await import("os");
27031
- const path63 = await import("path");
27032
- const fs51 = await import("fs");
27462
+ const os32 = await import("os");
27463
+ const path65 = await import("path");
27464
+ const fs53 = await import("fs");
27033
27465
  const { PluginRegistry: PluginRegistry2 } = await Promise.resolve().then(() => (init_plugin_registry(), plugin_registry_exports));
27034
- const root2 = instanceRoot ?? path63.join(os30.homedir(), ".openacp");
27035
- const registryPath = path63.join(root2, "plugins.json");
27466
+ const root = instanceRoot ?? path65.join(os32.homedir(), ".openacp");
27467
+ const registryPath = path65.join(root, "plugins.json");
27036
27468
  const registry = new PluginRegistry2(registryPath);
27037
27469
  await registry.load();
27038
27470
  const entry = registry.get(name);
@@ -27050,7 +27482,7 @@ async function uninstallPlugin(name, purge, instanceRoot) {
27050
27482
  if (plugin2?.uninstall) {
27051
27483
  const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
27052
27484
  const { createInstallContext: createInstallContext2 } = await Promise.resolve().then(() => (init_install_context(), install_context_exports));
27053
- const basePath = path63.join(root2, "plugins", "data");
27485
+ const basePath = path65.join(root, "plugins", "data");
27054
27486
  const settingsManager = new SettingsManager2(basePath);
27055
27487
  const ctx = createInstallContext2({ pluginName: name, settingsManager, basePath });
27056
27488
  await plugin2.uninstall(ctx, { purge });
@@ -27058,8 +27490,8 @@ async function uninstallPlugin(name, purge, instanceRoot) {
27058
27490
  } catch {
27059
27491
  }
27060
27492
  if (purge) {
27061
- const pluginDir = path63.join(root2, "plugins", name);
27062
- fs51.rmSync(pluginDir, { recursive: true, force: true });
27493
+ const pluginDir = path65.join(root, "plugins", name);
27494
+ fs53.rmSync(pluginDir, { recursive: true, force: true });
27063
27495
  }
27064
27496
  registry.remove(name);
27065
27497
  await registry.save();
@@ -27087,6 +27519,12 @@ function printApiHelp() {
27087
27519
  openacp api cancel <id> Cancel a session
27088
27520
  openacp api dangerous <id> on|off Toggle dangerous mode
27089
27521
 
27522
+ \x1B[1mSession Config Commands:\x1B[0m
27523
+ openacp api session-config <id> List all config options
27524
+ openacp api session-config <id> set <opt> <value> Set a config option
27525
+ openacp api session-config <id> overrides Show clientOverrides
27526
+ openacp api session-config <id> dangerous [on|off] Toggle bypassPermissions
27527
+
27090
27528
  \x1B[1mTopic Commands:\x1B[0m
27091
27529
  openacp api topics [--status s1,s2] List topics
27092
27530
  openacp api delete-topic <id> [--force] Delete a topic
@@ -27298,6 +27736,26 @@ Sends a restart signal to the running daemon.
27298
27736
  openacp api version
27299
27737
 
27300
27738
  Shows the version of the currently running daemon process.
27739
+ `,
27740
+ "session-config": `
27741
+ \x1B[1mopenacp api session-config\x1B[0m \u2014 Manage session config options
27742
+
27743
+ \x1B[1mUsage:\x1B[0m
27744
+ openacp api session-config <id> List all config options
27745
+ openacp api session-config <id> set <opt> <value> Set a config option
27746
+ openacp api session-config <id> overrides Show clientOverrides
27747
+ openacp api session-config <id> dangerous [on|off] Toggle bypassPermissions
27748
+
27749
+ \x1B[1mArguments:\x1B[0m
27750
+ <id> Session ID
27751
+ <opt> Config option ID (from list)
27752
+ <value> New value for the config option
27753
+
27754
+ \x1B[1mExamples:\x1B[0m
27755
+ openacp api session-config abc123
27756
+ openacp api session-config abc123 set model claude-opus-4-5
27757
+ openacp api session-config abc123 overrides
27758
+ openacp api session-config abc123 dangerous on
27301
27759
  `
27302
27760
  };
27303
27761
  const help = apiSubHelp[subCmd];
@@ -27627,6 +28085,126 @@ Shows the version of the currently running daemon process.
27627
28085
  process.exit(1);
27628
28086
  }
27629
28087
  console.log(`Daemon version: ${data.version}`);
28088
+ } else if (subCmd === "session-config") {
28089
+ const sessionId = args2[2];
28090
+ if (!sessionId) {
28091
+ console.error("Usage: openacp api session-config <session-id> [set <opt> <value> | overrides | dangerous [on|off]]");
28092
+ process.exit(1);
28093
+ }
28094
+ const configSubCmd = args2[3];
28095
+ if (!configSubCmd || configSubCmd === "list") {
28096
+ const res = await call(`/api/sessions/${encodeURIComponent(sessionId)}/config`);
28097
+ const data = await res.json();
28098
+ if (!res.ok) {
28099
+ console.error(`Error: ${data.error}`);
28100
+ process.exit(1);
28101
+ }
28102
+ const configOptions = data.configOptions;
28103
+ const clientOverrides = data.clientOverrides;
28104
+ if (!configOptions || configOptions.length === 0) {
28105
+ console.log("No config options available for this session.");
28106
+ } else {
28107
+ console.log(`Config options for session ${sessionId}:
28108
+ `);
28109
+ for (const opt of configOptions) {
28110
+ const desc = opt.description ? ` ${opt.description}` : "";
28111
+ console.log(` [${opt.id}] ${opt.name} (${opt.type}) current: ${opt.currentValue}${desc}`);
28112
+ if (opt.type === "select") {
28113
+ const options = opt.options;
28114
+ if (options && options.length > 0) {
28115
+ const choices = options.flatMap((o) => {
28116
+ if ("group" in o) {
28117
+ const groupOpts = o.options;
28118
+ return groupOpts?.map((c3) => `${c3.value} (${c3.label})`) ?? [];
28119
+ }
28120
+ return [`${o.value} (${o.label})`];
28121
+ });
28122
+ console.log(` choices: ${choices.join(", ")}`);
28123
+ }
28124
+ }
28125
+ }
28126
+ }
28127
+ if (clientOverrides && Object.keys(clientOverrides).length > 0) {
28128
+ console.log(`
28129
+ Client overrides: ${JSON.stringify(clientOverrides)}`);
28130
+ }
28131
+ } else if (configSubCmd === "set") {
28132
+ const configId = args2[4];
28133
+ const value = args2[5];
28134
+ if (!configId || value === void 0) {
28135
+ console.error("Usage: openacp api session-config <session-id> set <config-id> <value>");
28136
+ process.exit(1);
28137
+ }
28138
+ const res = await call(`/api/sessions/${encodeURIComponent(sessionId)}/config/${encodeURIComponent(configId)}`, {
28139
+ method: "PUT",
28140
+ headers: { "Content-Type": "application/json" },
28141
+ body: JSON.stringify({ value })
28142
+ });
28143
+ const data = await res.json();
28144
+ if (!res.ok) {
28145
+ console.error(`Error: ${data.error}`);
28146
+ process.exit(1);
28147
+ }
28148
+ console.log(`Config option "${configId}" updated to "${value}"`);
28149
+ const configOptions = data.configOptions;
28150
+ const updated = configOptions?.find((o) => o.id === configId);
28151
+ if (updated) {
28152
+ console.log(` Current value: ${updated.currentValue}`);
28153
+ }
28154
+ } else if (configSubCmd === "overrides") {
28155
+ const res = await call(`/api/sessions/${encodeURIComponent(sessionId)}/config/overrides`);
28156
+ const data = await res.json();
28157
+ if (!res.ok) {
28158
+ console.error(`Error: ${data.error}`);
28159
+ process.exit(1);
28160
+ }
28161
+ const overrides = data.clientOverrides;
28162
+ if (!overrides || Object.keys(overrides).length === 0) {
28163
+ console.log("No client overrides set.");
28164
+ } else {
28165
+ console.log(`Client overrides for session ${sessionId}:`);
28166
+ for (const [key, val] of Object.entries(overrides)) {
28167
+ console.log(` ${key}: ${val}`);
28168
+ }
28169
+ }
28170
+ } else if (configSubCmd === "dangerous") {
28171
+ const toggle = args2[4];
28172
+ if (toggle && toggle !== "on" && toggle !== "off") {
28173
+ console.error("Usage: openacp api session-config <session-id> dangerous [on|off]");
28174
+ process.exit(1);
28175
+ }
28176
+ if (toggle) {
28177
+ const res = await call(`/api/sessions/${encodeURIComponent(sessionId)}/config/overrides`, {
28178
+ method: "PUT",
28179
+ headers: { "Content-Type": "application/json" },
28180
+ body: JSON.stringify({ bypassPermissions: toggle === "on" })
28181
+ });
28182
+ const data = await res.json();
28183
+ if (!res.ok) {
28184
+ console.error(`Error: ${data.error}`);
28185
+ process.exit(1);
28186
+ }
28187
+ const state = toggle === "on" ? "enabled" : "disabled";
28188
+ console.log(`bypassPermissions ${state} for session ${sessionId}`);
28189
+ } else {
28190
+ const res = await call(`/api/sessions/${encodeURIComponent(sessionId)}/config/overrides`);
28191
+ const data = await res.json();
28192
+ if (!res.ok) {
28193
+ console.error(`Error: ${data.error}`);
28194
+ process.exit(1);
28195
+ }
28196
+ const overrides = data.clientOverrides;
28197
+ const bypass = overrides?.bypassPermissions;
28198
+ console.log(`bypassPermissions: ${bypass ?? false}`);
28199
+ }
28200
+ } else {
28201
+ console.error(`Unknown session-config subcommand: ${configSubCmd}`);
28202
+ console.log(" openacp api session-config <id> List config options");
28203
+ console.log(" openacp api session-config <id> set <opt> <value> Set a config option");
28204
+ console.log(" openacp api session-config <id> overrides Show clientOverrides");
28205
+ console.log(" openacp api session-config <id> dangerous [on|off] Toggle bypassPermissions");
28206
+ process.exit(1);
28207
+ }
27630
28208
  } else {
27631
28209
  const { suggestMatch: suggestMatch2 } = await Promise.resolve().then(() => (init_suggest(), suggest_exports));
27632
28210
  const apiSubcommands = [
@@ -27646,7 +28224,8 @@ Shows the version of the currently running daemon process.
27646
28224
  "adapters",
27647
28225
  "tunnel",
27648
28226
  "notify",
27649
- "version"
28227
+ "version",
28228
+ "session-config"
27650
28229
  ];
27651
28230
  const suggestion = suggestMatch2(subCmd ?? "", apiSubcommands);
27652
28231
  console.error(`Unknown api command: ${subCmd || "(none)"}
@@ -27673,7 +28252,7 @@ init_instance_hint();
27673
28252
  import path36 from "path";
27674
28253
  import os17 from "os";
27675
28254
  async function cmdStart(args2 = [], instanceRoot) {
27676
- const root2 = instanceRoot ?? path36.join(os17.homedir(), ".openacp");
28255
+ const root = instanceRoot ?? path36.join(os17.homedir(), ".openacp");
27677
28256
  if (wantsHelp(args2)) {
27678
28257
  console.log(`
27679
28258
  \x1B[1mopenacp start\x1B[0m \u2014 Start OpenACP as a background daemon
@@ -27695,16 +28274,16 @@ Requires an existing config \u2014 run 'openacp' first to set up.
27695
28274
  await checkAndPromptUpdate();
27696
28275
  const { startDaemon: startDaemon2, getPidPath: getPidPath2 } = await Promise.resolve().then(() => (init_daemon2(), daemon_exports));
27697
28276
  const { ConfigManager: ConfigManager2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
27698
- const cm = new ConfigManager2(path36.join(root2, "config.json"));
28277
+ const cm = new ConfigManager2(path36.join(root, "config.json"));
27699
28278
  if (await cm.exists()) {
27700
28279
  await cm.load();
27701
28280
  const config = cm.get();
27702
- const result = startDaemon2(getPidPath2(root2), config.logging.logDir, root2);
28281
+ const result = startDaemon2(getPidPath2(root), config.logging.logDir, root);
27703
28282
  if ("error" in result) {
27704
28283
  console.error(result.error);
27705
28284
  process.exit(1);
27706
28285
  }
27707
- printInstanceHint(root2);
28286
+ printInstanceHint(root);
27708
28287
  console.log(`OpenACP daemon started (PID ${result.pid})`);
27709
28288
  } else {
27710
28289
  console.error('No config found. Run "openacp" first to set up.');
@@ -27854,18 +28433,18 @@ start fresh with the setup wizard. The daemon must be stopped first.
27854
28433
  `);
27855
28434
  return;
27856
28435
  }
27857
- const os30 = await import("os");
27858
- const path63 = await import("path");
27859
- const root2 = instanceRoot ?? path63.join(os30.homedir(), ".openacp");
28436
+ const os32 = await import("os");
28437
+ const path65 = await import("path");
28438
+ const root = instanceRoot ?? path65.join(os32.homedir(), ".openacp");
27860
28439
  const { getStatus: getStatus2, getPidPath: getPidPath2 } = await Promise.resolve().then(() => (init_daemon2(), daemon_exports));
27861
- const status = getStatus2(getPidPath2(root2));
28440
+ const status = getStatus2(getPidPath2(root));
27862
28441
  if (status.running) {
27863
28442
  console.error("OpenACP is running. Stop it first: openacp stop");
27864
28443
  process.exit(1);
27865
28444
  }
27866
28445
  const clack10 = await import("@clack/prompts");
27867
28446
  const yes = await clack10.confirm({
27868
- message: `This will delete all OpenACP data (${root2}). You will need to set up again. Continue?`,
28447
+ message: `This will delete all OpenACP data (${root}). You will need to set up again. Continue?`,
27869
28448
  initialValue: false
27870
28449
  });
27871
28450
  if (clack10.isCancel(yes) || !yes) {
@@ -27874,8 +28453,8 @@ start fresh with the setup wizard. The daemon must be stopped first.
27874
28453
  }
27875
28454
  const { uninstallAutoStart: uninstallAutoStart2 } = await Promise.resolve().then(() => (init_autostart(), autostart_exports));
27876
28455
  uninstallAutoStart2();
27877
- const fs51 = await import("fs");
27878
- fs51.rmSync(root2, { recursive: true, force: true });
28456
+ const fs53 = await import("fs");
28457
+ fs53.rmSync(root, { recursive: true, force: true });
27879
28458
  console.log("Reset complete. Run `openacp` to set up again.");
27880
28459
  }
27881
28460
 
@@ -28620,16 +29199,16 @@ Tunnel Management:
28620
29199
  }
28621
29200
 
28622
29201
  // src/cli/commands/onboard.ts
28623
- import * as path57 from "path";
29202
+ import * as path58 from "path";
28624
29203
  async function cmdOnboard(instanceRoot) {
28625
29204
  const { ConfigManager: ConfigManager2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
28626
29205
  const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
28627
29206
  const { PluginRegistry: PluginRegistry2 } = await Promise.resolve().then(() => (init_plugin_registry(), plugin_registry_exports));
28628
29207
  const { getGlobalRoot: getGlobalRoot2 } = await Promise.resolve().then(() => (init_instance_context(), instance_context_exports));
28629
29208
  const OPENACP_DIR = instanceRoot ?? getGlobalRoot2();
28630
- const PLUGINS_DATA_DIR = path57.join(OPENACP_DIR, "plugins", "data");
28631
- const REGISTRY_PATH = path57.join(OPENACP_DIR, "plugins.json");
28632
- const cm = new ConfigManager2(path57.join(OPENACP_DIR, "config.json"));
29209
+ const PLUGINS_DATA_DIR = path58.join(OPENACP_DIR, "plugins", "data");
29210
+ const REGISTRY_PATH = path58.join(OPENACP_DIR, "plugins.json");
29211
+ const cm = new ConfigManager2(path58.join(OPENACP_DIR, "config.json"));
28633
29212
  const settingsManager = new SettingsManager2(PLUGINS_DATA_DIR);
28634
29213
  const pluginRegistry = new PluginRegistry2(REGISTRY_PATH);
28635
29214
  await pluginRegistry.load();
@@ -28646,12 +29225,12 @@ async function cmdOnboard(instanceRoot) {
28646
29225
  init_version();
28647
29226
  init_instance_context();
28648
29227
  init_instance_hint();
28649
- import path58 from "path";
28650
- import os27 from "os";
29228
+ import path59 from "path";
29229
+ import os28 from "os";
28651
29230
  async function cmdDefault(command2, instanceRoot) {
28652
- const root2 = instanceRoot ?? path58.join(os27.homedir(), ".openacp");
28653
- const pluginsDataDir = path58.join(root2, "plugins", "data");
28654
- const registryPath = path58.join(root2, "plugins.json");
29231
+ const root = instanceRoot ?? path59.join(os28.homedir(), ".openacp");
29232
+ const pluginsDataDir = path59.join(root, "plugins", "data");
29233
+ const registryPath = path59.join(root, "plugins.json");
28655
29234
  const forceForeground = command2 === "--foreground";
28656
29235
  if (command2 && !command2.startsWith("-")) {
28657
29236
  const { suggestMatch: suggestMatch2 } = await Promise.resolve().then(() => (init_suggest(), suggest_exports));
@@ -28683,7 +29262,7 @@ async function cmdDefault(command2, instanceRoot) {
28683
29262
  }
28684
29263
  await checkAndPromptUpdate();
28685
29264
  const { ConfigManager: ConfigManager2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
28686
- const configPath = path58.join(root2, "config.json");
29265
+ const configPath = path59.join(root, "config.json");
28687
29266
  const cm = new ConfigManager2(configPath);
28688
29267
  if (!await cm.exists()) {
28689
29268
  const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
@@ -28692,44 +29271,44 @@ async function cmdDefault(command2, instanceRoot) {
28692
29271
  const pluginRegistry = new PluginRegistry2(registryPath);
28693
29272
  await pluginRegistry.load();
28694
29273
  const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
28695
- const shouldStart = await runSetup2(cm, { settingsManager, pluginRegistry, instanceRoot: root2 });
29274
+ const shouldStart = await runSetup2(cm, { settingsManager, pluginRegistry, instanceRoot: root });
28696
29275
  if (!shouldStart) process.exit(0);
28697
29276
  }
28698
29277
  await cm.load();
28699
29278
  const config = cm.get();
28700
29279
  if (!forceForeground && config.runMode === "daemon") {
28701
29280
  const { isProcessRunning: isProcessRunning2, getPidPath: getPidPath2, startDaemon: startDaemon2 } = await Promise.resolve().then(() => (init_daemon2(), daemon_exports));
28702
- const pidPath = getPidPath2(root2);
29281
+ const pidPath = getPidPath2(root);
28703
29282
  if (isProcessRunning2(pidPath)) {
28704
- await showAlreadyRunningMenu(root2);
29283
+ await showAlreadyRunningMenu(root);
28705
29284
  return;
28706
29285
  }
28707
- const result = startDaemon2(pidPath, config.logging.logDir, root2);
29286
+ const result = startDaemon2(pidPath, config.logging.logDir, root);
28708
29287
  if ("error" in result) {
28709
29288
  console.error(result.error);
28710
29289
  process.exit(1);
28711
29290
  }
28712
- printInstanceHint(root2);
29291
+ printInstanceHint(root);
28713
29292
  console.log(`OpenACP daemon started (PID ${result.pid})`);
28714
29293
  return;
28715
29294
  }
28716
29295
  const { markRunning: markRunning2 } = await Promise.resolve().then(() => (init_daemon2(), daemon_exports));
28717
- markRunning2(root2);
28718
- printInstanceHint(root2);
29296
+ markRunning2(root);
29297
+ printInstanceHint(root);
28719
29298
  const { startServer: startServer2 } = await Promise.resolve().then(() => (init_main(), main_exports));
28720
29299
  const ctx = createInstanceContext({
28721
29300
  id: "default",
28722
- root: root2,
28723
- isGlobal: root2 === getGlobalRoot()
29301
+ root,
29302
+ isGlobal: root === getGlobalRoot()
28724
29303
  });
28725
29304
  await startServer2({ instanceContext: ctx });
28726
29305
  }
28727
- async function showAlreadyRunningMenu(root2) {
29306
+ async function showAlreadyRunningMenu(root) {
28728
29307
  const { formatInstanceStatus: formatInstanceStatus2 } = await Promise.resolve().then(() => (init_status(), status_exports));
28729
29308
  console.log("");
28730
29309
  console.log("\x1B[1mOpenACP is already running\x1B[0m");
28731
29310
  console.log("");
28732
- const status = formatInstanceStatus2(root2);
29311
+ const status = formatInstanceStatus2(root);
28733
29312
  if (status) {
28734
29313
  for (const line of status.lines) {
28735
29314
  console.log(line);
@@ -28743,7 +29322,7 @@ async function showAlreadyRunningMenu(root2) {
28743
29322
  label: "Restart",
28744
29323
  action: async () => {
28745
29324
  const { cmdRestart: cmdRestart2 } = await Promise.resolve().then(() => (init_restart(), restart_exports));
28746
- await cmdRestart2([], root2);
29325
+ await cmdRestart2([], root);
28747
29326
  }
28748
29327
  },
28749
29328
  {
@@ -28751,7 +29330,7 @@ async function showAlreadyRunningMenu(root2) {
28751
29330
  label: "Stop",
28752
29331
  action: async () => {
28753
29332
  const { cmdStop: cmdStop2 } = await Promise.resolve().then(() => (init_stop(), stop_exports));
28754
- await cmdStop2([], root2);
29333
+ await cmdStop2([], root);
28755
29334
  }
28756
29335
  },
28757
29336
  {
@@ -28765,7 +29344,7 @@ async function showAlreadyRunningMenu(root2) {
28765
29344
  label: "Restart in foreground",
28766
29345
  action: async () => {
28767
29346
  const { cmdRestart: cmdRestart2 } = await Promise.resolve().then(() => (init_restart(), restart_exports));
28768
- await cmdRestart2(["--foreground"], root2);
29347
+ await cmdRestart2(["--foreground"], root);
28769
29348
  }
28770
29349
  },
28771
29350
  {
@@ -28773,7 +29352,7 @@ async function showAlreadyRunningMenu(root2) {
28773
29352
  label: "View logs",
28774
29353
  action: async () => {
28775
29354
  const { cmdLogs: cmdLogs2 } = await Promise.resolve().then(() => (init_logs(), logs_exports));
28776
- await cmdLogs2([], root2);
29355
+ await cmdLogs2([], root);
28777
29356
  }
28778
29357
  }
28779
29358
  ]);
@@ -28785,8 +29364,8 @@ async function showAlreadyRunningMenu(root2) {
28785
29364
 
28786
29365
  // src/cli/commands/dev.ts
28787
29366
  init_helpers();
28788
- import fs50 from "fs";
28789
- import path59 from "path";
29367
+ import fs51 from "fs";
29368
+ import path60 from "path";
28790
29369
  async function cmdDev(args2 = []) {
28791
29370
  if (wantsHelp(args2)) {
28792
29371
  console.log(`
@@ -28813,13 +29392,13 @@ async function cmdDev(args2 = []) {
28813
29392
  console.error("Error: missing plugin path. Usage: openacp dev <plugin-path>");
28814
29393
  process.exit(1);
28815
29394
  }
28816
- const pluginPath = path59.resolve(pluginPathArg);
28817
- if (!fs50.existsSync(pluginPath)) {
29395
+ const pluginPath = path60.resolve(pluginPathArg);
29396
+ if (!fs51.existsSync(pluginPath)) {
28818
29397
  console.error(`Error: plugin path does not exist: ${pluginPath}`);
28819
29398
  process.exit(1);
28820
29399
  }
28821
- const tsconfigPath = path59.join(pluginPath, "tsconfig.json");
28822
- const hasTsconfig = fs50.existsSync(tsconfigPath);
29400
+ const tsconfigPath = path60.join(pluginPath, "tsconfig.json");
29401
+ const hasTsconfig = fs51.existsSync(tsconfigPath);
28823
29402
  if (hasTsconfig) {
28824
29403
  console.log("Compiling plugin TypeScript...");
28825
29404
  const { execSync: execSync5 } = await import("child_process");
@@ -28860,10 +29439,10 @@ async function cmdDev(args2 = []) {
28860
29439
 
28861
29440
  // src/cli/commands/attach.ts
28862
29441
  init_helpers();
28863
- import path60 from "path";
28864
- import os28 from "os";
29442
+ import path61 from "path";
29443
+ import os29 from "os";
28865
29444
  async function cmdAttach(args2 = [], instanceRoot) {
28866
- const root2 = instanceRoot ?? path60.join(os28.homedir(), ".openacp");
29445
+ const root = instanceRoot ?? path61.join(os29.homedir(), ".openacp");
28867
29446
  if (wantsHelp(args2)) {
28868
29447
  console.log(`
28869
29448
  \x1B[1mopenacp attach\x1B[0m \u2014 Attach to a running daemon
@@ -28881,7 +29460,7 @@ Press Ctrl+C to detach.
28881
29460
  return;
28882
29461
  }
28883
29462
  const { formatInstanceStatus: formatInstanceStatus2 } = await Promise.resolve().then(() => (init_status(), status_exports));
28884
- const status = formatInstanceStatus2(root2);
29463
+ const status = formatInstanceStatus2(root);
28885
29464
  if (!status) {
28886
29465
  console.log("OpenACP is not running.");
28887
29466
  process.exit(1);
@@ -28897,15 +29476,15 @@ Press Ctrl+C to detach.
28897
29476
  console.log("");
28898
29477
  const { spawn: spawn8 } = await import("child_process");
28899
29478
  const { expandHome: expandHome4 } = await Promise.resolve().then(() => (init_config2(), config_exports));
28900
- let logDir2 = path60.join(root2, "logs");
29479
+ let logDir2 = path61.join(root, "logs");
28901
29480
  try {
28902
- const configPath = path60.join(root2, "config.json");
29481
+ const configPath = path61.join(root, "config.json");
28903
29482
  const { readFileSync: readFileSync18 } = await import("fs");
28904
29483
  const config = JSON.parse(readFileSync18(configPath, "utf-8"));
28905
29484
  if (config.logging?.logDir) logDir2 = expandHome4(config.logging.logDir);
28906
29485
  } catch {
28907
29486
  }
28908
- const logFile = path60.join(logDir2, "openacp.log");
29487
+ const logFile = path61.join(logDir2, "openacp.log");
28909
29488
  const tail = spawn8("tail", ["-f", "-n", "50", logFile], { stdio: "inherit" });
28910
29489
  tail.on("error", (err) => {
28911
29490
  console.error(`Cannot tail log file: ${err.message}`);
@@ -28916,8 +29495,8 @@ Press Ctrl+C to detach.
28916
29495
  // src/cli/commands/remote.ts
28917
29496
  init_api_client();
28918
29497
  init_instance_registry();
28919
- import path61 from "path";
28920
- import os29 from "os";
29498
+ import path62 from "path";
29499
+ import os30 from "os";
28921
29500
  import qrcode from "qrcode-terminal";
28922
29501
  async function cmdRemote(args2, instanceRoot) {
28923
29502
  const role = extractFlag(args2, "--role") ?? "admin";
@@ -28930,12 +29509,12 @@ async function cmdRemote(args2, instanceRoot) {
28930
29509
  const scopes = scopesRaw ? scopesRaw.split(",").map((s) => s.trim()) : void 0;
28931
29510
  let resolvedInstanceRoot2 = instanceRoot;
28932
29511
  if (instanceId) {
28933
- const registryPath = path61.join(os29.homedir(), ".openacp", "instances.json");
29512
+ const registryPath = path62.join(os30.homedir(), ".openacp", "instances.json");
28934
29513
  const registry = new InstanceRegistry(registryPath);
28935
29514
  await registry.load();
28936
29515
  const entry = registry.get(instanceId);
28937
29516
  if (!entry) {
28938
- console.error(`Instance "${instanceId}" not found. Run "openacp status" to see running instances.`);
29517
+ console.error(`Workspace "${instanceId}" not found. Run "openacp status" to see workspaces.`);
28939
29518
  process.exit(1);
28940
29519
  }
28941
29520
  resolvedInstanceRoot2 = entry.root;
@@ -29105,41 +29684,34 @@ resolvedInstanceRoot = resolveInstanceRoot({
29105
29684
  global: flags.global,
29106
29685
  cwd: process.cwd()
29107
29686
  });
29108
- var root = resolvedInstanceRoot ?? getGlobalRoot();
29109
- var commands = {
29687
+ var noInstanceCommands = {
29110
29688
  "--help": async () => printHelp(),
29111
29689
  "-h": async () => printHelp(),
29112
29690
  "--version": () => cmdVersion(),
29113
29691
  "-v": () => cmdVersion(),
29114
- "install": () => cmdInstall(args, root),
29115
- "uninstall": () => cmdUninstall(args, root),
29116
- "plugins": () => cmdPlugins(args, root),
29117
- "plugin": () => cmdPlugin(args, root),
29118
- "api": () => cmdApi(args, root),
29119
- "start": () => cmdStart(args, root),
29120
- "stop": () => cmdStop(args, root),
29121
- "restart": () => cmdRestart(args, root),
29122
- "status": () => cmdStatus(args, root),
29123
- "logs": () => cmdLogs(args, root),
29124
- "config": () => cmdConfig(args, root),
29125
- "reset": () => cmdReset(args, root),
29126
29692
  "update": () => cmdUpdate(args),
29127
29693
  "adopt": () => cmdAdopt(args),
29128
29694
  "integrate": () => cmdIntegrate(args),
29129
- "doctor": () => cmdDoctor(args, root),
29130
- "agents": () => cmdAgents(args, root),
29131
- "tunnel": () => cmdTunnel(args, root),
29132
- "onboard": () => cmdOnboard(root),
29133
- "dev": () => cmdDev(args),
29134
- "attach": () => cmdAttach(args, root),
29135
- "remote": () => cmdRemote(args, root),
29136
- "--daemon-child": async () => {
29695
+ "dev": () => cmdDev(args)
29696
+ };
29697
+ async function resolveRoot(allowCreate) {
29698
+ if (resolvedInstanceRoot) return resolvedInstanceRoot;
29699
+ const { promptForInstance: promptForInstance2 } = await Promise.resolve().then(() => (init_instance_prompt(), instance_prompt_exports));
29700
+ return promptForInstance2({ allowCreate });
29701
+ }
29702
+ async function main() {
29703
+ const noInstance = command ? noInstanceCommands[command] : void 0;
29704
+ if (noInstance) {
29705
+ await noInstance();
29706
+ return;
29707
+ }
29708
+ if (command === "--daemon-child") {
29137
29709
  const { startServer: startServer2 } = await Promise.resolve().then(() => (init_main(), main_exports));
29138
29710
  const envRoot = process.env.OPENACP_INSTANCE_ROOT;
29139
29711
  if (envRoot) {
29140
29712
  const { createInstanceContext: createInstanceContext2, getGlobalRoot: getGlobal } = await Promise.resolve().then(() => (init_instance_context(), instance_context_exports));
29141
29713
  const { InstanceRegistry: InstanceRegistry2 } = await Promise.resolve().then(() => (init_instance_registry(), instance_registry_exports));
29142
- const registry = new InstanceRegistry2(path62.join(getGlobal(), "instances.json"));
29714
+ const registry = new InstanceRegistry2(path64.join(getGlobal(), "instances.json"));
29143
29715
  await registry.load();
29144
29716
  const entry = registry.getByRoot(envRoot);
29145
29717
  const id = entry?.id ?? "unknown";
@@ -29152,13 +29724,34 @@ var commands = {
29152
29724
  } else {
29153
29725
  await startServer2();
29154
29726
  }
29727
+ return;
29155
29728
  }
29156
- };
29157
- async function main() {
29158
- const handler = command ? commands[command] : void 0;
29729
+ const instanceCommands = {
29730
+ "install": (r) => cmdInstall(args, r),
29731
+ "uninstall": (r) => cmdUninstall(args, r),
29732
+ "plugins": (r) => cmdPlugins(args, r),
29733
+ "plugin": (r) => cmdPlugin(args, r),
29734
+ "api": (r) => cmdApi(args, r),
29735
+ "start": (r) => cmdStart(args, r),
29736
+ "stop": (r) => cmdStop(args, r),
29737
+ "restart": (r) => cmdRestart(args, r),
29738
+ "status": (r) => cmdStatus(args, r),
29739
+ "logs": (r) => cmdLogs(args, r),
29740
+ "config": (r) => cmdConfig(args, r),
29741
+ "reset": (r) => cmdReset(args, r),
29742
+ "doctor": (r) => cmdDoctor(args, r),
29743
+ "agents": (r) => cmdAgents(args, r),
29744
+ "tunnel": (r) => cmdTunnel(args, r),
29745
+ "onboard": (r) => cmdOnboard(r),
29746
+ "attach": (r) => cmdAttach(args, r),
29747
+ "remote": (r) => cmdRemote(args, r)
29748
+ };
29749
+ const handler = command ? instanceCommands[command] : void 0;
29159
29750
  if (handler) {
29160
- await handler();
29751
+ const root = await resolveRoot(false);
29752
+ await handler(root);
29161
29753
  } else {
29754
+ const root = await resolveRoot(true);
29162
29755
  await cmdDefault(command, root);
29163
29756
  }
29164
29757
  }