@openacp/cli 2026.331.3 → 2026.331.4

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
@@ -9952,7 +9952,7 @@ async function handleArchive(ctx, core) {
9952
9952
  return;
9953
9953
  }
9954
9954
  await ctx.reply(
9955
- "\u26A0\uFE0F <b>Archive this session?</b>\n\nThis will:\n\u2022 Delete this topic and recreate it (clearing chat history)\n\u2022 The agent session will keep running\n\n<i>Chat history cannot be recovered.</i>",
9955
+ "\u26A0\uFE0F <b>Archive this session?</b>\n\nThis will:\n\u2022 Stop the agent session\n\u2022 Delete this topic permanently\n\n<i>This cannot be undone.</i>",
9956
9956
  {
9957
9957
  parse_mode: "HTML",
9958
9958
  reply_markup: new InlineKeyboard3().text("\u{1F5D1} Yes, archive", `ar:yes:${identifier}`).text("\u274C Cancel", `ar:no:${identifier}`)
@@ -9973,18 +9973,7 @@ async function handleArchiveConfirm(ctx, core, chatId) {
9973
9973
  return;
9974
9974
  }
9975
9975
  const result = await core.archiveSession(identifier);
9976
- if (result.ok) {
9977
- const adapter = core.adapters.get("telegram");
9978
- if (adapter) {
9979
- try {
9980
- await adapter.sendMessage(identifier, {
9981
- type: "text",
9982
- text: "Chat history cleared. Session is still active \u2014 send a message to continue."
9983
- });
9984
- } catch {
9985
- }
9986
- }
9987
- } else {
9976
+ if (!result.ok) {
9988
9977
  try {
9989
9978
  await ctx.editMessageText(`Failed to archive: <code>${escapeHtml4(result.error)}</code>`, { parse_mode: "HTML" });
9990
9979
  } catch {
@@ -16265,10 +16254,6 @@ Task completed.
16265
16254
  this.sessionTrackers.delete(session.id);
16266
16255
  }
16267
16256
  await deleteSessionTopic(this.bot, chatId, oldTopicId);
16268
- const topicName = session.name ?? `Session ${session.id.slice(0, 6)}`;
16269
- const newTopicId = await createSessionTopic(this.bot, chatId, topicName);
16270
- session.archiving = false;
16271
- return String(newTopicId);
16272
16257
  }
16273
16258
  };
16274
16259
  }
@@ -18857,7 +18842,7 @@ async function setupIntegrations(config) {
18857
18842
  if (integration) {
18858
18843
  for (const item of integration.items) {
18859
18844
  const result = await item.install();
18860
- for (const log39 of result.logs) console.log(` ${log39}`);
18845
+ for (const log40 of result.logs) console.log(` ${log40}`);
18861
18846
  }
18862
18847
  }
18863
18848
  console.log("Claude CLI integration installed.\n");
@@ -19220,6 +19205,10 @@ async function runSetup(configManager, opts) {
19220
19205
  const builtInOptions = [
19221
19206
  { label: "Telegram", value: "telegram" }
19222
19207
  ];
19208
+ const officialAdapters = [
19209
+ { label: "Discord", value: "official:@openacp/discord-adapter" },
19210
+ { label: "Slack", value: "official:@openacp/slack-adapter" }
19211
+ ];
19223
19212
  const communityOptions = communityAdapters.map((a) => ({
19224
19213
  label: `${a.icon} ${a.displayName}${a.verified ? " (verified)" : ""}`,
19225
19214
  value: `community:${a.name}`
@@ -19229,6 +19218,7 @@ async function runSetup(configManager, opts) {
19229
19218
  message: "Which channels do you want to set up?",
19230
19219
  options: [
19231
19220
  ...builtInOptions.map((o) => ({ value: o.value, label: o.label, hint: "built-in" })),
19221
+ ...officialAdapters.map((o) => ({ value: o.value, label: o.label, hint: "official" })),
19232
19222
  ...communityOptions.length > 0 ? communityOptions.map((o) => ({ value: o.value, label: o.label, hint: "from plugin registry" })) : []
19233
19223
  ],
19234
19224
  required: true,
@@ -19258,6 +19248,54 @@ async function runSetup(configManager, opts) {
19258
19248
  description: telegramPlugin.description
19259
19249
  });
19260
19250
  }
19251
+ if (channelId.startsWith("official:")) {
19252
+ const npmPackage = channelId.slice("official:".length);
19253
+ const { execFileSync: execFileSync8 } = await import("child_process");
19254
+ const pluginsDir = path47.join(getGlobalRoot(), "plugins");
19255
+ const nodeModulesDir = path47.join(pluginsDir, "node_modules");
19256
+ const installedPath = path47.join(nodeModulesDir, npmPackage);
19257
+ if (!fs41.existsSync(installedPath)) {
19258
+ try {
19259
+ clack9.log.step(`Installing ${npmPackage}...`);
19260
+ execFileSync8("npm", ["install", npmPackage, "--prefix", pluginsDir, "--save"], {
19261
+ stdio: "inherit",
19262
+ timeout: 6e4
19263
+ });
19264
+ } catch {
19265
+ console.log(fail(`Failed to install ${npmPackage}.`));
19266
+ continue;
19267
+ }
19268
+ }
19269
+ try {
19270
+ const installedPkgPath = path47.join(nodeModulesDir, npmPackage, "package.json");
19271
+ const installedPkg = JSON.parse(fs41.readFileSync(installedPkgPath, "utf-8"));
19272
+ const pluginModule = await import(path47.join(nodeModulesDir, npmPackage, installedPkg.main ?? "dist/index.js"));
19273
+ const plugin = pluginModule.default;
19274
+ if (plugin?.install) {
19275
+ const installCtx = createInstallContext2({
19276
+ pluginName: plugin.name ?? npmPackage,
19277
+ settingsManager,
19278
+ basePath: settingsManager.getBasePath()
19279
+ });
19280
+ await plugin.install(installCtx);
19281
+ }
19282
+ pluginRegistry.register(plugin?.name ?? npmPackage, {
19283
+ version: installedPkg.version,
19284
+ source: "npm",
19285
+ enabled: true,
19286
+ settingsPath: settingsManager.getSettingsPath(plugin?.name ?? npmPackage),
19287
+ description: plugin?.description ?? installedPkg.description
19288
+ });
19289
+ } catch (err) {
19290
+ console.log(fail(`Failed to load ${npmPackage}: ${err.message}`));
19291
+ pluginRegistry.register(npmPackage, {
19292
+ version: "unknown",
19293
+ source: "npm",
19294
+ enabled: false,
19295
+ settingsPath: settingsManager.getSettingsPath(npmPackage)
19296
+ });
19297
+ }
19298
+ }
19261
19299
  if (channelId.startsWith("community:")) {
19262
19300
  const npmPackage = channelId.slice("community:".length);
19263
19301
  const { execFileSync: execFileSync8 } = await import("child_process");
@@ -19950,7 +19988,7 @@ function resolveAgentCommand(cmd) {
19950
19988
  }
19951
19989
  return { command: cmd, args: [] };
19952
19990
  }
19953
- var log32, AgentInstance;
19991
+ var log33, AgentInstance;
19954
19992
  var init_agent_instance = __esm({
19955
19993
  "src/core/agents/agent-instance.ts"() {
19956
19994
  "use strict";
@@ -19962,7 +20000,7 @@ var init_agent_instance = __esm({
19962
20000
  init_mcp_manager();
19963
20001
  init_debug_tracer();
19964
20002
  init_log();
19965
- log32 = createChildLogger({ module: "agent-instance" });
20003
+ log33 = createChildLogger({ module: "agent-instance" });
19966
20004
  AgentInstance = class _AgentInstance extends TypedEmitter {
19967
20005
  connection;
19968
20006
  child;
@@ -19984,7 +20022,7 @@ var init_agent_instance = __esm({
19984
20022
  static async spawnSubprocess(agentDef, workingDirectory) {
19985
20023
  const instance = new _AgentInstance(agentDef.name);
19986
20024
  const resolved = resolveAgentCommand(agentDef.command);
19987
- log32.debug(
20025
+ log33.debug(
19988
20026
  {
19989
20027
  agentName: agentDef.name,
19990
20028
  command: resolved.command,
@@ -20058,13 +20096,13 @@ var init_agent_instance = __esm({
20058
20096
  }
20059
20097
  });
20060
20098
  if (initResponse.protocolVersion !== PROTOCOL_VERSION) {
20061
- log32.warn(
20099
+ log33.warn(
20062
20100
  { expected: PROTOCOL_VERSION, got: initResponse.protocolVersion },
20063
20101
  "ACP protocol version mismatch \u2014 some features may not work correctly"
20064
20102
  );
20065
20103
  }
20066
20104
  instance.promptCapabilities = initResponse.agentCapabilities?.promptCapabilities;
20067
- log32.info(
20105
+ log33.info(
20068
20106
  { promptCapabilities: instance.promptCapabilities ?? {} },
20069
20107
  "Agent prompt capabilities"
20070
20108
  );
@@ -20073,7 +20111,7 @@ var init_agent_instance = __esm({
20073
20111
  setupCrashDetection() {
20074
20112
  this.child.on("exit", (code, signal) => {
20075
20113
  if (this._destroying) return;
20076
- log32.info(
20114
+ log33.info(
20077
20115
  { sessionId: this.sessionId, exitCode: code, signal },
20078
20116
  "Agent process exited"
20079
20117
  );
@@ -20088,11 +20126,11 @@ ${stderr}`
20088
20126
  }
20089
20127
  });
20090
20128
  this.connection.closed.then(() => {
20091
- log32.debug({ sessionId: this.sessionId }, "ACP connection closed");
20129
+ log33.debug({ sessionId: this.sessionId }, "ACP connection closed");
20092
20130
  });
20093
20131
  }
20094
20132
  static async spawn(agentDef, workingDirectory, mcpServers) {
20095
- log32.debug(
20133
+ log33.debug(
20096
20134
  { agentName: agentDef.name, command: agentDef.command },
20097
20135
  "Spawning agent"
20098
20136
  );
@@ -20109,14 +20147,14 @@ ${stderr}`
20109
20147
  instance.sessionId = response.sessionId;
20110
20148
  instance.debugTracer = createDebugTracer(response.sessionId, workingDirectory);
20111
20149
  instance.setupCrashDetection();
20112
- log32.info(
20150
+ log33.info(
20113
20151
  { sessionId: response.sessionId, durationMs: Date.now() - spawnStart },
20114
20152
  "Agent spawn complete"
20115
20153
  );
20116
20154
  return instance;
20117
20155
  }
20118
20156
  static async resume(agentDef, workingDirectory, agentSessionId, mcpServers) {
20119
- log32.debug({ agentName: agentDef.name, agentSessionId }, "Resuming agent");
20157
+ log33.debug({ agentName: agentDef.name, agentSessionId }, "Resuming agent");
20120
20158
  const spawnStart = Date.now();
20121
20159
  const instance = await _AgentInstance.spawnSubprocess(
20122
20160
  agentDef,
@@ -20129,12 +20167,12 @@ ${stderr}`
20129
20167
  });
20130
20168
  instance.sessionId = response.sessionId;
20131
20169
  instance.debugTracer = createDebugTracer(response.sessionId, workingDirectory);
20132
- log32.info(
20170
+ log33.info(
20133
20171
  { sessionId: response.sessionId, durationMs: Date.now() - spawnStart },
20134
20172
  "Agent resume complete"
20135
20173
  );
20136
20174
  } catch (err) {
20137
- log32.warn(
20175
+ log33.warn(
20138
20176
  { err, agentSessionId },
20139
20177
  "Resume failed, falling back to new session"
20140
20178
  );
@@ -20145,7 +20183,7 @@ ${stderr}`
20145
20183
  });
20146
20184
  instance.sessionId = response.sessionId;
20147
20185
  instance.debugTracer = createDebugTracer(response.sessionId, workingDirectory);
20148
- log32.info(
20186
+ log33.info(
20149
20187
  { sessionId: response.sessionId, durationMs: Date.now() - spawnStart },
20150
20188
  "Agent fallback spawn complete"
20151
20189
  );
@@ -20425,7 +20463,7 @@ ${stderr}`
20425
20463
  contentBlocks.push({ type: "audio", data: data.toString("base64"), mimeType: att.mimeType });
20426
20464
  } else {
20427
20465
  if ((att.type === "image" || att.type === "audio") && !tooLarge) {
20428
- log32.debug(
20466
+ log33.debug(
20429
20467
  { type: att.type, capabilities: this.promptCapabilities ?? {} },
20430
20468
  "Agent does not support %s content, falling back to file path",
20431
20469
  att.type
@@ -21253,12 +21291,12 @@ var init_session_manager = __esm({
21253
21291
  });
21254
21292
 
21255
21293
  // src/core/sessions/session-bridge.ts
21256
- var log33, SessionBridge;
21294
+ var log34, SessionBridge;
21257
21295
  var init_session_bridge = __esm({
21258
21296
  "src/core/sessions/session-bridge.ts"() {
21259
21297
  "use strict";
21260
21298
  init_log();
21261
- log33 = createChildLogger({ module: "session-bridge" });
21299
+ log34 = createChildLogger({ module: "session-bridge" });
21262
21300
  SessionBridge = class {
21263
21301
  constructor(session, adapter, deps) {
21264
21302
  this.session = session;
@@ -21284,16 +21322,16 @@ var init_session_bridge = __esm({
21284
21322
  if (!result) return;
21285
21323
  this.tracer?.log("core", { step: "dispatch", sessionId, message: result.message });
21286
21324
  this.adapter.sendMessage(sessionId, result.message).catch((err) => {
21287
- log33.error({ err, sessionId }, "Failed to send message to adapter");
21325
+ log34.error({ err, sessionId }, "Failed to send message to adapter");
21288
21326
  });
21289
21327
  } else {
21290
21328
  this.tracer?.log("core", { step: "dispatch", sessionId, message });
21291
21329
  this.adapter.sendMessage(sessionId, message).catch((err) => {
21292
- log33.error({ err, sessionId }, "Failed to send message to adapter");
21330
+ log34.error({ err, sessionId }, "Failed to send message to adapter");
21293
21331
  });
21294
21332
  }
21295
21333
  } catch (err) {
21296
- log33.error({ err, sessionId }, "Error in sendMessage middleware");
21334
+ log34.error({ err, sessionId }, "Error in sendMessage middleware");
21297
21335
  }
21298
21336
  }
21299
21337
  connect() {
@@ -21348,20 +21386,20 @@ var init_session_bridge = __esm({
21348
21386
  }, async (e) => e).catch(() => {
21349
21387
  });
21350
21388
  } catch (err) {
21351
- log33.error({ err, sessionId: this.session.id }, "Error handling agent event after middleware");
21389
+ log34.error({ err, sessionId: this.session.id }, "Error handling agent event after middleware");
21352
21390
  }
21353
21391
  }).catch(() => {
21354
21392
  try {
21355
21393
  this.handleAgentEvent(event);
21356
21394
  } catch (err) {
21357
- log33.error({ err, sessionId: this.session.id }, "Error handling agent event (middleware fallback)");
21395
+ log34.error({ err, sessionId: this.session.id }, "Error handling agent event (middleware fallback)");
21358
21396
  }
21359
21397
  });
21360
21398
  } else {
21361
21399
  try {
21362
21400
  this.handleAgentEvent(event);
21363
21401
  } catch (err) {
21364
- log33.error({ err, sessionId: this.session.id }, "Error handling agent event");
21402
+ log34.error({ err, sessionId: this.session.id }, "Error handling agent event");
21365
21403
  }
21366
21404
  }
21367
21405
  };
@@ -21427,7 +21465,7 @@ var init_session_bridge = __esm({
21427
21465
  text: "",
21428
21466
  attachment: att
21429
21467
  });
21430
- }).catch((err) => log33.error({ err }, "Failed to save agent image"));
21468
+ }).catch((err) => log34.error({ err }, "Failed to save agent image"));
21431
21469
  }
21432
21470
  break;
21433
21471
  }
@@ -21444,12 +21482,12 @@ var init_session_bridge = __esm({
21444
21482
  text: "",
21445
21483
  attachment: att
21446
21484
  });
21447
- }).catch((err) => log33.error({ err }, "Failed to save agent audio"));
21485
+ }).catch((err) => log34.error({ err }, "Failed to save agent audio"));
21448
21486
  }
21449
21487
  break;
21450
21488
  }
21451
21489
  case "commands_update":
21452
- log33.debug({ commands: event.commands }, "Commands available");
21490
+ log34.debug({ commands: event.commands }, "Commands available");
21453
21491
  this.adapter.sendSkillCommands?.(this.session.id, event.commands);
21454
21492
  break;
21455
21493
  case "system_message":
@@ -21529,7 +21567,7 @@ var init_session_bridge = __esm({
21529
21567
  if (permReq.description.toLowerCase().includes("openacp")) {
21530
21568
  const allowOption = permReq.options.find((o) => o.isAllow);
21531
21569
  if (allowOption) {
21532
- log33.info(
21570
+ log34.info(
21533
21571
  { sessionId: this.session.id, requestId: permReq.id },
21534
21572
  "Auto-approving openacp command"
21535
21573
  );
@@ -21549,7 +21587,7 @@ var init_session_bridge = __esm({
21549
21587
  if (this.session.dangerousMode) {
21550
21588
  const allowOption = permReq.options.find((o) => o.isAllow);
21551
21589
  if (allowOption) {
21552
- log33.info(
21590
+ log34.info(
21553
21591
  { sessionId: this.session.id, requestId: permReq.id, optionId: allowOption.id },
21554
21592
  "Dangerous mode: auto-approving permission"
21555
21593
  );
@@ -21597,13 +21635,17 @@ var init_session_bridge = __esm({
21597
21635
  }
21598
21636
  };
21599
21637
  this.session.on("status_change", this.statusChangeHandler);
21600
- this.namedHandler = (name) => {
21601
- this.deps.sessionManager.patchRecord(this.session.id, { name });
21638
+ this.namedHandler = async (name) => {
21639
+ const record = this.deps.sessionManager.getSessionRecord(this.session.id);
21640
+ const alreadyNamed = !!record?.name;
21641
+ await this.deps.sessionManager.patchRecord(this.session.id, { name });
21602
21642
  this.deps.eventBus?.emit("session:updated", {
21603
21643
  sessionId: this.session.id,
21604
21644
  name
21605
21645
  });
21606
- this.adapter.renameSessionThread(this.session.id, name);
21646
+ if (!alreadyNamed) {
21647
+ await this.adapter.renameSessionThread(this.session.id, name);
21648
+ }
21607
21649
  };
21608
21650
  this.session.on("named", this.namedHandler);
21609
21651
  this.promptCountHandler = (count) => {
@@ -21761,13 +21803,13 @@ function computeLineDiff(oldStr, newStr) {
21761
21803
  removed: Math.max(0, oldLines.length - prefixLen - suffixLen)
21762
21804
  };
21763
21805
  }
21764
- var log34, MessageTransformer;
21806
+ var log35, MessageTransformer;
21765
21807
  var init_message_transformer = __esm({
21766
21808
  "src/core/message-transformer.ts"() {
21767
21809
  "use strict";
21768
21810
  init_extract_file_info();
21769
21811
  init_log();
21770
- log34 = createChildLogger({ module: "message-transformer" });
21812
+ log35 = createChildLogger({ module: "message-transformer" });
21771
21813
  MessageTransformer = class {
21772
21814
  tunnelService;
21773
21815
  /** Cache rawInput from tool_call so it's available in tool_update (which often lacks it) */
@@ -21933,14 +21975,14 @@ var init_message_transformer = __esm({
21933
21975
  }
21934
21976
  }
21935
21977
  if (!this.tunnelService || !sessionContext) {
21936
- log34.debug(
21978
+ log35.debug(
21937
21979
  { hasTunnel: !!this.tunnelService, hasCtx: !!sessionContext, kind },
21938
21980
  "enrichWithViewerLinks: skipping (no tunnel or session context)"
21939
21981
  );
21940
21982
  return;
21941
21983
  }
21942
21984
  const name = "name" in event ? event.name || "" : "";
21943
- log34.debug(
21985
+ log35.debug(
21944
21986
  { name, kind, status: event.status, hasContent: !!event.content, hasRawInput: !!event.rawInput },
21945
21987
  "enrichWithViewerLinks: inspecting event"
21946
21988
  );
@@ -21952,7 +21994,7 @@ var init_message_transformer = __esm({
21952
21994
  event.meta
21953
21995
  );
21954
21996
  if (!fileInfo) {
21955
- log34.debug(
21997
+ log35.debug(
21956
21998
  { name, kind, hasContent: !!event.content, hasRawInput: !!event.rawInput, hasMeta: !!event.meta },
21957
21999
  "enrichWithViewerLinks: extractFileInfo returned null"
21958
22000
  );
@@ -21960,10 +22002,10 @@ var init_message_transformer = __esm({
21960
22002
  }
21961
22003
  const publicUrl = this.tunnelService.getPublicUrl();
21962
22004
  if (publicUrl.startsWith("http://localhost") || publicUrl.startsWith("http://127.0.0.1")) {
21963
- log34.debug({ kind, filePath: fileInfo.filePath }, "enrichWithViewerLinks: skipping (no public tunnel URL)");
22005
+ log35.debug({ kind, filePath: fileInfo.filePath }, "enrichWithViewerLinks: skipping (no public tunnel URL)");
21964
22006
  return;
21965
22007
  }
21966
- log34.info(
22008
+ log35.info(
21967
22009
  {
21968
22010
  name,
21969
22011
  kind,
@@ -22009,12 +22051,12 @@ var init_message_transformer = __esm({
22009
22051
  // src/core/sessions/session-store.ts
22010
22052
  import fs45 from "fs";
22011
22053
  import path51 from "path";
22012
- var log35, DEBOUNCE_MS2, JsonFileSessionStore;
22054
+ var log36, DEBOUNCE_MS2, JsonFileSessionStore;
22013
22055
  var init_session_store = __esm({
22014
22056
  "src/core/sessions/session-store.ts"() {
22015
22057
  "use strict";
22016
22058
  init_log();
22017
- log35 = createChildLogger({ module: "session-store" });
22059
+ log36 = createChildLogger({ module: "session-store" });
22018
22060
  DEBOUNCE_MS2 = 2e3;
22019
22061
  JsonFileSessionStore = class {
22020
22062
  records = /* @__PURE__ */ new Map();
@@ -22102,7 +22144,7 @@ var init_session_store = __esm({
22102
22144
  fs45.readFileSync(this.filePath, "utf-8")
22103
22145
  );
22104
22146
  if (raw.version !== 1) {
22105
- log35.warn(
22147
+ log36.warn(
22106
22148
  { version: raw.version },
22107
22149
  "Unknown session store version, skipping load"
22108
22150
  );
@@ -22111,9 +22153,9 @@ var init_session_store = __esm({
22111
22153
  for (const [id, record] of Object.entries(raw.sessions)) {
22112
22154
  this.records.set(id, record);
22113
22155
  }
22114
- log35.debug({ count: this.records.size }, "Loaded session records");
22156
+ log36.debug({ count: this.records.size }, "Loaded session records");
22115
22157
  } catch (err) {
22116
- log35.error({ err }, "Failed to load session store, backing up corrupt file");
22158
+ log36.error({ err }, "Failed to load session store, backing up corrupt file");
22117
22159
  try {
22118
22160
  fs45.renameSync(this.filePath, `${this.filePath}.bak`);
22119
22161
  } catch {
@@ -22133,7 +22175,7 @@ var init_session_store = __esm({
22133
22175
  }
22134
22176
  }
22135
22177
  if (removed > 0) {
22136
- log35.info({ removed }, "Cleaned up expired session records");
22178
+ log36.info({ removed }, "Cleaned up expired session records");
22137
22179
  this.scheduleDiskWrite();
22138
22180
  }
22139
22181
  }
@@ -22148,19 +22190,20 @@ var init_session_store = __esm({
22148
22190
  });
22149
22191
 
22150
22192
  // src/core/sessions/session-factory.ts
22151
- var log36, SessionFactory;
22193
+ var log37, SessionFactory;
22152
22194
  var init_session_factory = __esm({
22153
22195
  "src/core/sessions/session-factory.ts"() {
22154
22196
  "use strict";
22155
22197
  init_session2();
22156
22198
  init_log();
22157
- log36 = createChildLogger({ module: "session-factory" });
22199
+ log37 = createChildLogger({ module: "session-factory" });
22158
22200
  SessionFactory = class {
22159
- constructor(agentManager, sessionManager, speechServiceAccessor, eventBus) {
22201
+ constructor(agentManager, sessionManager, speechServiceAccessor, eventBus, instanceRoot) {
22160
22202
  this.agentManager = agentManager;
22161
22203
  this.sessionManager = sessionManager;
22162
22204
  this.speechServiceAccessor = speechServiceAccessor;
22163
22205
  this.eventBus = eventBus;
22206
+ this.instanceRoot = instanceRoot;
22164
22207
  }
22165
22208
  middlewareChain;
22166
22209
  get speechService() {
@@ -22187,14 +22230,73 @@ var init_session_factory = __esm({
22187
22230
  channelId: result.channelId
22188
22231
  };
22189
22232
  }
22190
- const agentInstance = createParams.resumeAgentSessionId ? await this.agentManager.resume(
22191
- createParams.agentName,
22192
- createParams.workingDirectory,
22193
- createParams.resumeAgentSessionId
22194
- ) : await this.agentManager.spawn(
22195
- createParams.agentName,
22196
- createParams.workingDirectory
22197
- );
22233
+ let agentInstance;
22234
+ try {
22235
+ agentInstance = createParams.resumeAgentSessionId ? await this.agentManager.resume(
22236
+ createParams.agentName,
22237
+ createParams.workingDirectory,
22238
+ createParams.resumeAgentSessionId
22239
+ ) : await this.agentManager.spawn(
22240
+ createParams.agentName,
22241
+ createParams.workingDirectory
22242
+ );
22243
+ } catch (err) {
22244
+ const message = err instanceof Error ? err.message : String(err);
22245
+ const guidanceLines = [
22246
+ `\u274C Failed to start agent "${createParams.agentName}": ${message}`,
22247
+ "",
22248
+ "Run the agent CLI once in a terminal for this OpenACP instance to complete login or setup."
22249
+ ];
22250
+ if (this.instanceRoot) {
22251
+ guidanceLines.push(
22252
+ "",
22253
+ "Copy and run this command in your terminal:",
22254
+ ` cd "${this.instanceRoot}" && openacp agents run ${createParams.agentName}`
22255
+ );
22256
+ } else {
22257
+ guidanceLines.push(
22258
+ "",
22259
+ "Copy and run this command in your terminal (same project where you started OpenACP):",
22260
+ ` openacp agents run ${createParams.agentName}`
22261
+ );
22262
+ }
22263
+ guidanceLines.push(
22264
+ "",
22265
+ "After setup completes, retry creating the session here."
22266
+ );
22267
+ const guidance = {
22268
+ type: "system_message",
22269
+ message: guidanceLines.join("\n")
22270
+ };
22271
+ const failedSession = new Session({
22272
+ id: createParams.existingSessionId,
22273
+ channelId: createParams.channelId,
22274
+ agentName: createParams.agentName,
22275
+ workingDirectory: createParams.workingDirectory,
22276
+ // Dummy agent instance — will never be prompted
22277
+ agentInstance: {
22278
+ sessionId: "",
22279
+ prompt: async () => {
22280
+ },
22281
+ cancel: async () => {
22282
+ },
22283
+ destroy: async () => {
22284
+ },
22285
+ on: () => {
22286
+ },
22287
+ off: () => {
22288
+ }
22289
+ },
22290
+ speechService: this.speechService
22291
+ });
22292
+ this.sessionManager.registerSession(failedSession);
22293
+ failedSession.emit("agent_event", guidance);
22294
+ this.eventBus.emit("agent:event", {
22295
+ sessionId: failedSession.id,
22296
+ event: guidance
22297
+ });
22298
+ throw err;
22299
+ }
22198
22300
  agentInstance.middlewareChain = this.middlewareChain;
22199
22301
  const session = new Session({
22200
22302
  id: createParams.existingSessionId,
@@ -22627,7 +22729,7 @@ function createPluginContext(opts) {
22627
22729
  }
22628
22730
  };
22629
22731
  const baseLog = opts.log ?? noopLog;
22630
- const log39 = typeof baseLog.child === "function" ? baseLog.child({ plugin: pluginName }) : baseLog;
22732
+ const log40 = typeof baseLog.child === "function" ? baseLog.child({ plugin: pluginName }) : baseLog;
22631
22733
  const storageImpl = new PluginStorageImpl(storagePath);
22632
22734
  const storage = {
22633
22735
  async get(key) {
@@ -22654,7 +22756,7 @@ function createPluginContext(opts) {
22654
22756
  const ctx = {
22655
22757
  pluginName,
22656
22758
  pluginConfig,
22657
- log: log39,
22759
+ log: log40,
22658
22760
  storage,
22659
22761
  on(event, handler) {
22660
22762
  requirePermission(permissions, "events:read", "on()");
@@ -22689,7 +22791,7 @@ function createPluginContext(opts) {
22689
22791
  const registry = serviceRegistry.get("command-registry");
22690
22792
  if (registry && typeof registry.register === "function") {
22691
22793
  registry.register(def, pluginName);
22692
- log39.debug(`Command '/${def.name}' registered`);
22794
+ log40.debug(`Command '/${def.name}' registered`);
22693
22795
  }
22694
22796
  },
22695
22797
  async sendMessage(_sessionId, _content) {
@@ -23010,7 +23112,7 @@ var init_lifecycle_manager = __esm({
23010
23112
  // src/core/core.ts
23011
23113
  import path54 from "path";
23012
23114
  import os26 from "os";
23013
- var log37, OpenACPCore;
23115
+ var log38, OpenACPCore;
23014
23116
  var init_core = __esm({
23015
23117
  "src/core/core.ts"() {
23016
23118
  "use strict";
@@ -23028,7 +23130,7 @@ var init_core = __esm({
23028
23130
  init_middleware_chain();
23029
23131
  init_error_tracker();
23030
23132
  init_log();
23031
- log37 = createChildLogger({ module: "core" });
23133
+ log38 = createChildLogger({ module: "core" });
23032
23134
  OpenACPCore = class {
23033
23135
  configManager;
23034
23136
  agentCatalog;
@@ -23089,7 +23191,8 @@ var init_core = __esm({
23089
23191
  this.agentManager,
23090
23192
  this.sessionManager,
23091
23193
  () => this.speechService,
23092
- this.eventBus
23194
+ this.eventBus,
23195
+ ctx?.root
23093
23196
  );
23094
23197
  this.lifecycleManager = new LifecycleManager({
23095
23198
  serviceRegistry: new ServiceRegistry(),
@@ -23110,7 +23213,7 @@ var init_core = __esm({
23110
23213
  if (configPath === "logging.level" && typeof value === "string") {
23111
23214
  const { setLogLevel: setLogLevel2 } = await Promise.resolve().then(() => (init_log(), log_exports));
23112
23215
  setLogLevel2(value);
23113
- log37.info({ level: value }, "Log level changed at runtime");
23216
+ log38.info({ level: value }, "Log level changed at runtime");
23114
23217
  }
23115
23218
  if (configPath.startsWith("speech.")) {
23116
23219
  const speechSvc = this.speechService;
@@ -23121,7 +23224,7 @@ var init_core = __esm({
23121
23224
  tts: { provider: null, providers: {} }
23122
23225
  };
23123
23226
  speechSvc.refreshProviders(newSpeechConfig);
23124
- log37.info("Speech service config updated at runtime");
23227
+ log38.info("Speech service config updated at runtime");
23125
23228
  }
23126
23229
  }
23127
23230
  }
@@ -23139,14 +23242,14 @@ var init_core = __esm({
23139
23242
  }
23140
23243
  async start() {
23141
23244
  this.agentCatalog.refreshRegistryIfStale().catch((err) => {
23142
- log37.warn({ err }, "Background registry refresh failed");
23245
+ log38.warn({ err }, "Background registry refresh failed");
23143
23246
  });
23144
23247
  const failures = [];
23145
23248
  for (const [name, adapter] of this.adapters.entries()) {
23146
23249
  try {
23147
23250
  await adapter.start();
23148
23251
  } catch (err) {
23149
- log37.error({ err, adapter: name }, `Adapter "${name}" failed to start`);
23252
+ log38.error({ err, adapter: name }, `Adapter "${name}" failed to start`);
23150
23253
  failures.push({ name, error: err });
23151
23254
  }
23152
23255
  }
@@ -23184,20 +23287,9 @@ var init_core = __esm({
23184
23287
  if (!adapter) return { ok: false, error: "Adapter not found for session" };
23185
23288
  if (!adapter.archiveSessionTopic) return { ok: false, error: "Adapter does not support topic archiving" };
23186
23289
  try {
23187
- const newThreadId = await adapter.archiveSessionTopic(session.id);
23188
- session.threadId = newThreadId;
23189
- try {
23190
- const platform2 = {};
23191
- if (session.channelId === "telegram") {
23192
- platform2.topicId = Number(newThreadId);
23193
- } else {
23194
- platform2.threadId = newThreadId;
23195
- }
23196
- await this.sessionManager.patchRecord(sessionId, { platform: platform2 });
23197
- } catch (patchErr) {
23198
- log37.warn({ err: patchErr, sessionId }, "Failed to update session record after archive \u2014 session will work but may not survive restart");
23199
- }
23200
- return { ok: true, newThreadId };
23290
+ await adapter.archiveSessionTopic(session.id);
23291
+ await this.sessionManager.cancelSession(sessionId);
23292
+ return { ok: true };
23201
23293
  } catch (err) {
23202
23294
  session.archiving = false;
23203
23295
  return { ok: false, error: err.message };
@@ -23205,7 +23297,7 @@ var init_core = __esm({
23205
23297
  }
23206
23298
  // --- Message Routing ---
23207
23299
  async handleMessage(message) {
23208
- log37.debug(
23300
+ log38.debug(
23209
23301
  {
23210
23302
  channelId: message.channelId,
23211
23303
  threadId: message.threadId,
@@ -23224,7 +23316,7 @@ var init_core = __esm({
23224
23316
  }
23225
23317
  const access2 = this.securityGuard.checkAccess(message);
23226
23318
  if (!access2.allowed) {
23227
- log37.warn({ userId: message.userId, reason: access2.reason }, "Access denied");
23319
+ log38.warn({ userId: message.userId, reason: access2.reason }, "Access denied");
23228
23320
  if (access2.reason.includes("Session limit")) {
23229
23321
  const adapter = this.adapters.get(message.channelId);
23230
23322
  if (adapter) {
@@ -23244,7 +23336,7 @@ var init_core = __esm({
23244
23336
  session = await this.lazyResume(message) ?? void 0;
23245
23337
  }
23246
23338
  if (!session) {
23247
- log37.warn(
23339
+ log38.warn(
23248
23340
  { channelId: message.channelId, threadId: message.threadId },
23249
23341
  "No session found for thread (in-memory miss + lazy resume returned null)"
23250
23342
  );
@@ -23304,7 +23396,7 @@ var init_core = __esm({
23304
23396
  currentPromptCount: session.promptCount,
23305
23397
  agentSwitchHistory: session.agentSwitchHistory
23306
23398
  });
23307
- log37.info(
23399
+ log38.info(
23308
23400
  { sessionId: session.id, agentName: params.agentName },
23309
23401
  "Session created via pipeline"
23310
23402
  );
@@ -23313,7 +23405,7 @@ var init_core = __esm({
23313
23405
  async handleNewSession(channelId, agentName, workspacePath, options) {
23314
23406
  const config = this.configManager.get();
23315
23407
  const resolvedAgent = agentName || config.defaultAgent;
23316
- log37.info({ channelId, agentName: resolvedAgent }, "New session request");
23408
+ log38.info({ channelId, agentName: resolvedAgent }, "New session request");
23317
23409
  const agentDef = this.agentCatalog.resolve(resolvedAgent);
23318
23410
  const resolvedWorkspace = this.configManager.resolveWorkspace(
23319
23411
  workspacePath || agentDef?.workingDirectory
@@ -23462,7 +23554,7 @@ var init_core = __esm({
23462
23554
  params.contextOptions
23463
23555
  );
23464
23556
  } catch (err) {
23465
- log37.warn({ err }, "Context building failed, proceeding without context");
23557
+ log38.warn({ err }, "Context building failed, proceeding without context");
23466
23558
  }
23467
23559
  const session = await this.createSession({
23468
23560
  channelId: params.channelId,
@@ -23504,6 +23596,21 @@ var init_core = __esm({
23504
23596
  const caps = getAgentCapabilities(toAgent);
23505
23597
  const canResume = !!(lastEntry && caps.supportsResume && lastEntry.promptCount === 0);
23506
23598
  const resumed = canResume;
23599
+ const startEvent = {
23600
+ type: "system_message",
23601
+ message: `Switching from ${fromAgent} to ${toAgent}...`
23602
+ };
23603
+ session.emit("agent_event", startEvent);
23604
+ this.eventBus.emit("agent:event", {
23605
+ sessionId,
23606
+ event: startEvent
23607
+ });
23608
+ this.eventBus.emit("session:agentSwitch", {
23609
+ sessionId,
23610
+ fromAgent,
23611
+ toAgent,
23612
+ status: "starting"
23613
+ });
23507
23614
  const bridge = this.bridges.get(sessionId);
23508
23615
  if (bridge) bridge.disconnect();
23509
23616
  const switchAdapter = this.adapters.get(session.channelId);
@@ -23538,7 +23645,40 @@ var init_core = __esm({
23538
23645
  return instance;
23539
23646
  }
23540
23647
  });
23648
+ const successEvent = {
23649
+ type: "system_message",
23650
+ message: resumed ? `Switched to ${toAgent} (resumed previous session).` : `Switched to ${toAgent} (new session).`
23651
+ };
23652
+ session.emit("agent_event", successEvent);
23653
+ this.eventBus.emit("agent:event", {
23654
+ sessionId,
23655
+ event: successEvent
23656
+ });
23657
+ this.eventBus.emit("session:agentSwitch", {
23658
+ sessionId,
23659
+ fromAgent,
23660
+ toAgent,
23661
+ status: "succeeded",
23662
+ resumed
23663
+ });
23541
23664
  } catch (err) {
23665
+ const errorMessage = err instanceof Error ? err.message : String(err);
23666
+ const failedEvent = {
23667
+ type: "system_message",
23668
+ message: `Failed to switch to ${toAgent}: ${errorMessage}`
23669
+ };
23670
+ session.emit("agent_event", failedEvent);
23671
+ this.eventBus.emit("agent:event", {
23672
+ sessionId,
23673
+ event: failedEvent
23674
+ });
23675
+ this.eventBus.emit("session:agentSwitch", {
23676
+ sessionId,
23677
+ fromAgent,
23678
+ toAgent,
23679
+ status: "failed",
23680
+ error: errorMessage
23681
+ });
23542
23682
  try {
23543
23683
  let rollbackInstance;
23544
23684
  try {
@@ -23556,10 +23696,10 @@ var init_core = __esm({
23556
23696
  const rollbackBridge = this.createBridge(session, adapter);
23557
23697
  rollbackBridge.connect();
23558
23698
  }
23559
- log37.warn({ sessionId, fromAgent, toAgent, err }, "Agent switch failed, rolled back to previous agent");
23699
+ log38.warn({ sessionId, fromAgent, toAgent, err }, "Agent switch failed, rolled back to previous agent");
23560
23700
  } catch (rollbackErr) {
23561
23701
  session.fail(`Switch failed and rollback failed: ${rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr)}`);
23562
- log37.error({ sessionId, fromAgent, toAgent, err, rollbackErr }, "Agent switch failed and rollback also failed");
23702
+ log38.error({ sessionId, fromAgent, toAgent, err, rollbackErr }, "Agent switch failed and rollback also failed");
23563
23703
  }
23564
23704
  throw err;
23565
23705
  }
@@ -23607,14 +23747,14 @@ var init_core = __esm({
23607
23747
  (p2) => String(p2.topicId) === message.threadId
23608
23748
  );
23609
23749
  if (!record) {
23610
- log37.debug(
23750
+ log38.debug(
23611
23751
  { threadId: message.threadId, channelId: message.channelId },
23612
23752
  "No session record found for thread"
23613
23753
  );
23614
23754
  return null;
23615
23755
  }
23616
23756
  if (record.status === "error" || record.status === "cancelled") {
23617
- log37.debug(
23757
+ log38.debug(
23618
23758
  {
23619
23759
  threadId: message.threadId,
23620
23760
  sessionId: record.sessionId,
@@ -23624,7 +23764,7 @@ var init_core = __esm({
23624
23764
  );
23625
23765
  return null;
23626
23766
  }
23627
- log37.info(
23767
+ log38.info(
23628
23768
  {
23629
23769
  threadId: message.threadId,
23630
23770
  sessionId: record.sessionId,
@@ -23648,13 +23788,13 @@ var init_core = __esm({
23648
23788
  if (record.firstAgent) session.firstAgent = record.firstAgent;
23649
23789
  if (record.agentSwitchHistory) session.agentSwitchHistory = record.agentSwitchHistory;
23650
23790
  if (record.currentPromptCount != null) session.promptCount = record.currentPromptCount;
23651
- log37.info(
23791
+ log38.info(
23652
23792
  { sessionId: session.id, threadId: message.threadId },
23653
23793
  "Lazy resume successful"
23654
23794
  );
23655
23795
  return session;
23656
23796
  } catch (err) {
23657
- log37.error({ err, record }, "Lazy resume failed");
23797
+ log38.error({ err, record }, "Lazy resume failed");
23658
23798
  const adapter = this.adapters.get(message.channelId);
23659
23799
  if (adapter) {
23660
23800
  try {
@@ -24149,14 +24289,14 @@ async function runPostUpgradeChecks(config) {
24149
24289
  const { ensureCloudflared: ensureCloudflared2 } = await Promise.resolve().then(() => (init_install_cloudflared(), install_cloudflared_exports));
24150
24290
  await ensureCloudflared2();
24151
24291
  } catch (err) {
24152
- log38.warn(
24292
+ log39.warn(
24153
24293
  { err: err.message },
24154
24294
  "Could not install cloudflared. Tunnel may not work."
24155
24295
  );
24156
24296
  }
24157
24297
  } else {
24158
24298
  if (!commandExists(config.tunnel.provider)) {
24159
- log38.warn(
24299
+ log39.warn(
24160
24300
  `Tunnel provider "${config.tunnel.provider}" is not installed. Install it or switch to cloudflare (free, auto-installed).`
24161
24301
  );
24162
24302
  }
@@ -24168,7 +24308,7 @@ async function runPostUpgradeChecks(config) {
24168
24308
  if (integration) {
24169
24309
  const allInstalled = integration.items.every((item) => item.isInstalled());
24170
24310
  if (!allInstalled) {
24171
- log38.info(
24311
+ log39.info(
24172
24312
  'Claude CLI integration not installed. Run "openacp integrate claude" for session transfer + tunnel skill.'
24173
24313
  );
24174
24314
  }
@@ -24178,7 +24318,7 @@ async function runPostUpgradeChecks(config) {
24178
24318
  const { ensureJq: ensureJq2 } = await Promise.resolve().then(() => (init_install_jq(), install_jq_exports));
24179
24319
  await ensureJq2();
24180
24320
  } catch (err) {
24181
- log38.warn(
24321
+ log39.warn(
24182
24322
  { err: err.message },
24183
24323
  "Could not install jq. Handoff hooks may not work."
24184
24324
  );
@@ -24188,7 +24328,7 @@ async function runPostUpgradeChecks(config) {
24188
24328
  } catch {
24189
24329
  }
24190
24330
  if (!commandExists("unzip")) {
24191
- log38.warn(
24331
+ log39.warn(
24192
24332
  "unzip is not installed. Some agent installations (binary distribution) may fail. Install: brew install unzip (macOS) or apt install unzip (Linux)"
24193
24333
  );
24194
24334
  }
@@ -24201,20 +24341,20 @@ async function runPostUpgradeChecks(config) {
24201
24341
  (a) => a.distribution === "uvx"
24202
24342
  );
24203
24343
  if (hasUvxAgent && !commandExists("uvx")) {
24204
- log38.warn(
24344
+ log39.warn(
24205
24345
  "uvx is not installed but you have Python-based agents. Install: pip install uv"
24206
24346
  );
24207
24347
  }
24208
24348
  } catch {
24209
24349
  }
24210
24350
  }
24211
- var log38;
24351
+ var log39;
24212
24352
  var init_post_upgrade = __esm({
24213
24353
  "src/cli/post-upgrade.ts"() {
24214
24354
  "use strict";
24215
24355
  init_log();
24216
24356
  init_agent_dependencies();
24217
- log38 = createChildLogger({ module: "post-upgrade" });
24357
+ log39 = createChildLogger({ module: "post-upgrade" });
24218
24358
  }
24219
24359
  });
24220
24360
 
@@ -26378,7 +26518,7 @@ a "Handoff" slash command to Claude Code.
26378
26518
  if (uninstall) {
26379
26519
  console.log(`Removing ${agent}/${item.id}...`);
26380
26520
  const result = await item.uninstall();
26381
- for (const log39 of result.logs) console.log(` ${log39}`);
26521
+ for (const log40 of result.logs) console.log(` ${log40}`);
26382
26522
  if (result.success) {
26383
26523
  console.log(` ${item.name} removed.`);
26384
26524
  } else {
@@ -26388,7 +26528,7 @@ a "Handoff" slash command to Claude Code.
26388
26528
  } else {
26389
26529
  console.log(`Installing ${agent}/${item.id}...`);
26390
26530
  const result = await item.install();
26391
- for (const log39 of result.logs) console.log(` ${log39}`);
26531
+ for (const log40 of result.logs) console.log(` ${log40}`);
26392
26532
  if (result.success) {
26393
26533
  console.log(` ${item.name} installed.`);
26394
26534
  } else {