@openacp/cli 2026.331.2 → 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
@@ -3929,7 +3929,7 @@ function initLogger(config) {
3929
3929
  target: "pino-pretty",
3930
3930
  options: {
3931
3931
  colorize: true,
3932
- translateTime: "HH:mm:ss",
3932
+ translateTime: "SYS:HH:MM:ss",
3933
3933
  ignore: "pid,hostname",
3934
3934
  singleLine: true
3935
3935
  },
@@ -4964,7 +4964,7 @@ var init_tunnel_registry = __esm({
4964
4964
  this.providerOptions = opts.providerOptions ?? {};
4965
4965
  this.registryPath = opts.registryPath ?? path16.join(os9.homedir(), ".openacp", "tunnels.json");
4966
4966
  }
4967
- async add(port, opts) {
4967
+ async add(port, opts, _autoRetry = true) {
4968
4968
  if (this.entries.has(port)) {
4969
4969
  const existing = this.entries.get(port);
4970
4970
  if (existing.entry.status === "active" || existing.entry.status === "starting") {
@@ -5018,6 +5018,15 @@ var init_tunnel_registry = __esm({
5018
5018
  entry.status = "failed";
5019
5019
  log7.error({ port, err: err.message }, "Tunnel failed to start");
5020
5020
  this.scheduleSave();
5021
+ const live = this.entries.get(port);
5022
+ if (_autoRetry && live && !this.shuttingDown && live.entry.retryCount < MAX_RETRIES) {
5023
+ const delay = BASE_RETRY_DELAY_MS * Math.pow(2, live.entry.retryCount);
5024
+ log7.warn(
5025
+ { port, retry: live.entry.retryCount + 1, maxRetries: MAX_RETRIES, delayMs: delay },
5026
+ "Scheduling retry after initial start failure"
5027
+ );
5028
+ live.retryTimer = setTimeout(() => this.retry(port, opts), delay);
5029
+ }
5021
5030
  throw err;
5022
5031
  });
5023
5032
  this.entries.set(port, { entry, process: provider, spawnPromise, retryTimer: null });
@@ -5034,7 +5043,7 @@ var init_tunnel_registry = __esm({
5034
5043
  if (live.retryTimer) clearTimeout(live.retryTimer);
5035
5044
  this.entries.delete(port);
5036
5045
  try {
5037
- const entry = await this.add(port, opts);
5046
+ const entry = await this.add(port, opts, false);
5038
5047
  entry.retryCount = retryCount;
5039
5048
  } catch (err) {
5040
5049
  log7.error({ port, err: err.message, retry: retryCount }, "Tunnel retry failed");
@@ -9943,7 +9952,7 @@ async function handleArchive(ctx, core) {
9943
9952
  return;
9944
9953
  }
9945
9954
  await ctx.reply(
9946
- "\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>",
9947
9956
  {
9948
9957
  parse_mode: "HTML",
9949
9958
  reply_markup: new InlineKeyboard3().text("\u{1F5D1} Yes, archive", `ar:yes:${identifier}`).text("\u274C Cancel", `ar:no:${identifier}`)
@@ -9964,18 +9973,7 @@ async function handleArchiveConfirm(ctx, core, chatId) {
9964
9973
  return;
9965
9974
  }
9966
9975
  const result = await core.archiveSession(identifier);
9967
- if (result.ok) {
9968
- const adapter = core.adapters.get("telegram");
9969
- if (adapter) {
9970
- try {
9971
- await adapter.sendMessage(identifier, {
9972
- type: "text",
9973
- text: "Chat history cleared. Session is still active \u2014 send a message to continue."
9974
- });
9975
- } catch {
9976
- }
9977
- }
9978
- } else {
9976
+ if (!result.ok) {
9979
9977
  try {
9980
9978
  await ctx.editMessageText(`Failed to archive: <code>${escapeHtml4(result.error)}</code>`, { parse_mode: "HTML" });
9981
9979
  } catch {
@@ -15237,27 +15235,28 @@ var init_adapter = __esm({
15237
15235
  }
15238
15236
  return prev(method, payload, signal);
15239
15237
  });
15240
- await this.bot.api.setMyCommands(STATIC_COMMANDS, {
15241
- scope: { type: "chat", chat_id: this.telegramConfig.chatId }
15242
- });
15238
+ this.registerCommandsWithRetry();
15243
15239
  this.bot.use((ctx, next) => {
15244
15240
  const chatId = ctx.chat?.id ?? ctx.callbackQuery?.message?.chat?.id;
15245
15241
  if (chatId !== this.telegramConfig.chatId) return;
15246
15242
  return next();
15247
15243
  });
15248
- const topics = await ensureTopics(
15249
- this.bot,
15250
- this.telegramConfig.chatId,
15251
- this.telegramConfig,
15252
- async (updates) => {
15253
- if (this.saveTopicIds) {
15254
- await this.saveTopicIds(updates);
15255
- } else {
15256
- await this.core.configManager.save({
15257
- channels: { telegram: updates }
15258
- });
15244
+ const topics = await this.retryWithBackoff(
15245
+ () => ensureTopics(
15246
+ this.bot,
15247
+ this.telegramConfig.chatId,
15248
+ this.telegramConfig,
15249
+ async (updates) => {
15250
+ if (this.saveTopicIds) {
15251
+ await this.saveTopicIds(updates);
15252
+ } else {
15253
+ await this.core.configManager.save({
15254
+ channels: { telegram: updates }
15255
+ });
15256
+ }
15259
15257
  }
15260
- }
15258
+ ),
15259
+ "ensureTopics"
15261
15260
  );
15262
15261
  this.notificationTopicId = topics.notificationTopicId;
15263
15262
  this.assistantTopicId = topics.assistantTopicId;
@@ -15505,6 +15504,40 @@ var init_adapter = __esm({
15505
15504
  });
15506
15505
  }
15507
15506
  }
15507
+ /**
15508
+ * Retry an async operation with exponential backoff.
15509
+ * Used for Telegram API calls that may fail due to transient network issues.
15510
+ */
15511
+ async retryWithBackoff(fn, label, maxRetries = 5, baseDelayMs = 2e3) {
15512
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
15513
+ try {
15514
+ return await fn();
15515
+ } catch (err) {
15516
+ if (attempt === maxRetries) throw err;
15517
+ const delay = baseDelayMs * Math.pow(2, attempt - 1);
15518
+ log26.warn(
15519
+ { err, attempt, maxRetries, delayMs: delay, operation: label },
15520
+ `${label} failed, retrying in ${delay}ms`
15521
+ );
15522
+ await new Promise((r) => setTimeout(r, delay));
15523
+ }
15524
+ }
15525
+ throw new Error("unreachable");
15526
+ }
15527
+ /**
15528
+ * Register Telegram commands in the background with retries.
15529
+ * Non-critical — bot works fine without autocomplete commands.
15530
+ */
15531
+ registerCommandsWithRetry() {
15532
+ this.retryWithBackoff(
15533
+ () => this.bot.api.setMyCommands(STATIC_COMMANDS, {
15534
+ scope: { type: "chat", chat_id: this.telegramConfig.chatId }
15535
+ }),
15536
+ "setMyCommands"
15537
+ ).catch((err) => {
15538
+ log26.warn({ err }, "Failed to register Telegram commands after retries (non-critical)");
15539
+ });
15540
+ }
15508
15541
  async stop() {
15509
15542
  for (const tracker of this.sessionTrackers.values()) {
15510
15543
  tracker.destroy();
@@ -16221,10 +16254,6 @@ Task completed.
16221
16254
  this.sessionTrackers.delete(session.id);
16222
16255
  }
16223
16256
  await deleteSessionTopic(this.bot, chatId, oldTopicId);
16224
- const topicName = session.name ?? `Session ${session.id.slice(0, 6)}`;
16225
- const newTopicId = await createSessionTopic(this.bot, chatId, topicName);
16226
- session.archiving = false;
16227
- return String(newTopicId);
16228
16257
  }
16229
16258
  };
16230
16259
  }
@@ -18813,7 +18842,7 @@ async function setupIntegrations(config) {
18813
18842
  if (integration) {
18814
18843
  for (const item of integration.items) {
18815
18844
  const result = await item.install();
18816
- for (const log39 of result.logs) console.log(` ${log39}`);
18845
+ for (const log40 of result.logs) console.log(` ${log40}`);
18817
18846
  }
18818
18847
  }
18819
18848
  console.log("Claude CLI integration installed.\n");
@@ -19176,6 +19205,10 @@ async function runSetup(configManager, opts) {
19176
19205
  const builtInOptions = [
19177
19206
  { label: "Telegram", value: "telegram" }
19178
19207
  ];
19208
+ const officialAdapters = [
19209
+ { label: "Discord", value: "official:@openacp/discord-adapter" },
19210
+ { label: "Slack", value: "official:@openacp/slack-adapter" }
19211
+ ];
19179
19212
  const communityOptions = communityAdapters.map((a) => ({
19180
19213
  label: `${a.icon} ${a.displayName}${a.verified ? " (verified)" : ""}`,
19181
19214
  value: `community:${a.name}`
@@ -19185,6 +19218,7 @@ async function runSetup(configManager, opts) {
19185
19218
  message: "Which channels do you want to set up?",
19186
19219
  options: [
19187
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" })),
19188
19222
  ...communityOptions.length > 0 ? communityOptions.map((o) => ({ value: o.value, label: o.label, hint: "from plugin registry" })) : []
19189
19223
  ],
19190
19224
  required: true,
@@ -19214,6 +19248,54 @@ async function runSetup(configManager, opts) {
19214
19248
  description: telegramPlugin.description
19215
19249
  });
19216
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
+ }
19217
19299
  if (channelId.startsWith("community:")) {
19218
19300
  const npmPackage = channelId.slice("community:".length);
19219
19301
  const { execFileSync: execFileSync8 } = await import("child_process");
@@ -19906,7 +19988,7 @@ function resolveAgentCommand(cmd) {
19906
19988
  }
19907
19989
  return { command: cmd, args: [] };
19908
19990
  }
19909
- var log32, AgentInstance;
19991
+ var log33, AgentInstance;
19910
19992
  var init_agent_instance = __esm({
19911
19993
  "src/core/agents/agent-instance.ts"() {
19912
19994
  "use strict";
@@ -19918,7 +20000,7 @@ var init_agent_instance = __esm({
19918
20000
  init_mcp_manager();
19919
20001
  init_debug_tracer();
19920
20002
  init_log();
19921
- log32 = createChildLogger({ module: "agent-instance" });
20003
+ log33 = createChildLogger({ module: "agent-instance" });
19922
20004
  AgentInstance = class _AgentInstance extends TypedEmitter {
19923
20005
  connection;
19924
20006
  child;
@@ -19940,7 +20022,7 @@ var init_agent_instance = __esm({
19940
20022
  static async spawnSubprocess(agentDef, workingDirectory) {
19941
20023
  const instance = new _AgentInstance(agentDef.name);
19942
20024
  const resolved = resolveAgentCommand(agentDef.command);
19943
- log32.debug(
20025
+ log33.debug(
19944
20026
  {
19945
20027
  agentName: agentDef.name,
19946
20028
  command: resolved.command,
@@ -20014,13 +20096,13 @@ var init_agent_instance = __esm({
20014
20096
  }
20015
20097
  });
20016
20098
  if (initResponse.protocolVersion !== PROTOCOL_VERSION) {
20017
- log32.warn(
20099
+ log33.warn(
20018
20100
  { expected: PROTOCOL_VERSION, got: initResponse.protocolVersion },
20019
20101
  "ACP protocol version mismatch \u2014 some features may not work correctly"
20020
20102
  );
20021
20103
  }
20022
20104
  instance.promptCapabilities = initResponse.agentCapabilities?.promptCapabilities;
20023
- log32.info(
20105
+ log33.info(
20024
20106
  { promptCapabilities: instance.promptCapabilities ?? {} },
20025
20107
  "Agent prompt capabilities"
20026
20108
  );
@@ -20029,7 +20111,7 @@ var init_agent_instance = __esm({
20029
20111
  setupCrashDetection() {
20030
20112
  this.child.on("exit", (code, signal) => {
20031
20113
  if (this._destroying) return;
20032
- log32.info(
20114
+ log33.info(
20033
20115
  { sessionId: this.sessionId, exitCode: code, signal },
20034
20116
  "Agent process exited"
20035
20117
  );
@@ -20044,11 +20126,11 @@ ${stderr}`
20044
20126
  }
20045
20127
  });
20046
20128
  this.connection.closed.then(() => {
20047
- log32.debug({ sessionId: this.sessionId }, "ACP connection closed");
20129
+ log33.debug({ sessionId: this.sessionId }, "ACP connection closed");
20048
20130
  });
20049
20131
  }
20050
20132
  static async spawn(agentDef, workingDirectory, mcpServers) {
20051
- log32.debug(
20133
+ log33.debug(
20052
20134
  { agentName: agentDef.name, command: agentDef.command },
20053
20135
  "Spawning agent"
20054
20136
  );
@@ -20065,14 +20147,14 @@ ${stderr}`
20065
20147
  instance.sessionId = response.sessionId;
20066
20148
  instance.debugTracer = createDebugTracer(response.sessionId, workingDirectory);
20067
20149
  instance.setupCrashDetection();
20068
- log32.info(
20150
+ log33.info(
20069
20151
  { sessionId: response.sessionId, durationMs: Date.now() - spawnStart },
20070
20152
  "Agent spawn complete"
20071
20153
  );
20072
20154
  return instance;
20073
20155
  }
20074
20156
  static async resume(agentDef, workingDirectory, agentSessionId, mcpServers) {
20075
- log32.debug({ agentName: agentDef.name, agentSessionId }, "Resuming agent");
20157
+ log33.debug({ agentName: agentDef.name, agentSessionId }, "Resuming agent");
20076
20158
  const spawnStart = Date.now();
20077
20159
  const instance = await _AgentInstance.spawnSubprocess(
20078
20160
  agentDef,
@@ -20085,12 +20167,12 @@ ${stderr}`
20085
20167
  });
20086
20168
  instance.sessionId = response.sessionId;
20087
20169
  instance.debugTracer = createDebugTracer(response.sessionId, workingDirectory);
20088
- log32.info(
20170
+ log33.info(
20089
20171
  { sessionId: response.sessionId, durationMs: Date.now() - spawnStart },
20090
20172
  "Agent resume complete"
20091
20173
  );
20092
20174
  } catch (err) {
20093
- log32.warn(
20175
+ log33.warn(
20094
20176
  { err, agentSessionId },
20095
20177
  "Resume failed, falling back to new session"
20096
20178
  );
@@ -20101,7 +20183,7 @@ ${stderr}`
20101
20183
  });
20102
20184
  instance.sessionId = response.sessionId;
20103
20185
  instance.debugTracer = createDebugTracer(response.sessionId, workingDirectory);
20104
- log32.info(
20186
+ log33.info(
20105
20187
  { sessionId: response.sessionId, durationMs: Date.now() - spawnStart },
20106
20188
  "Agent fallback spawn complete"
20107
20189
  );
@@ -20381,7 +20463,7 @@ ${stderr}`
20381
20463
  contentBlocks.push({ type: "audio", data: data.toString("base64"), mimeType: att.mimeType });
20382
20464
  } else {
20383
20465
  if ((att.type === "image" || att.type === "audio") && !tooLarge) {
20384
- log32.debug(
20466
+ log33.debug(
20385
20467
  { type: att.type, capabilities: this.promptCapabilities ?? {} },
20386
20468
  "Agent does not support %s content, falling back to file path",
20387
20469
  att.type
@@ -21209,12 +21291,12 @@ var init_session_manager = __esm({
21209
21291
  });
21210
21292
 
21211
21293
  // src/core/sessions/session-bridge.ts
21212
- var log33, SessionBridge;
21294
+ var log34, SessionBridge;
21213
21295
  var init_session_bridge = __esm({
21214
21296
  "src/core/sessions/session-bridge.ts"() {
21215
21297
  "use strict";
21216
21298
  init_log();
21217
- log33 = createChildLogger({ module: "session-bridge" });
21299
+ log34 = createChildLogger({ module: "session-bridge" });
21218
21300
  SessionBridge = class {
21219
21301
  constructor(session, adapter, deps) {
21220
21302
  this.session = session;
@@ -21240,16 +21322,16 @@ var init_session_bridge = __esm({
21240
21322
  if (!result) return;
21241
21323
  this.tracer?.log("core", { step: "dispatch", sessionId, message: result.message });
21242
21324
  this.adapter.sendMessage(sessionId, result.message).catch((err) => {
21243
- log33.error({ err, sessionId }, "Failed to send message to adapter");
21325
+ log34.error({ err, sessionId }, "Failed to send message to adapter");
21244
21326
  });
21245
21327
  } else {
21246
21328
  this.tracer?.log("core", { step: "dispatch", sessionId, message });
21247
21329
  this.adapter.sendMessage(sessionId, message).catch((err) => {
21248
- log33.error({ err, sessionId }, "Failed to send message to adapter");
21330
+ log34.error({ err, sessionId }, "Failed to send message to adapter");
21249
21331
  });
21250
21332
  }
21251
21333
  } catch (err) {
21252
- log33.error({ err, sessionId }, "Error in sendMessage middleware");
21334
+ log34.error({ err, sessionId }, "Error in sendMessage middleware");
21253
21335
  }
21254
21336
  }
21255
21337
  connect() {
@@ -21304,20 +21386,20 @@ var init_session_bridge = __esm({
21304
21386
  }, async (e) => e).catch(() => {
21305
21387
  });
21306
21388
  } catch (err) {
21307
- 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");
21308
21390
  }
21309
21391
  }).catch(() => {
21310
21392
  try {
21311
21393
  this.handleAgentEvent(event);
21312
21394
  } catch (err) {
21313
- 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)");
21314
21396
  }
21315
21397
  });
21316
21398
  } else {
21317
21399
  try {
21318
21400
  this.handleAgentEvent(event);
21319
21401
  } catch (err) {
21320
- log33.error({ err, sessionId: this.session.id }, "Error handling agent event");
21402
+ log34.error({ err, sessionId: this.session.id }, "Error handling agent event");
21321
21403
  }
21322
21404
  }
21323
21405
  };
@@ -21383,7 +21465,7 @@ var init_session_bridge = __esm({
21383
21465
  text: "",
21384
21466
  attachment: att
21385
21467
  });
21386
- }).catch((err) => log33.error({ err }, "Failed to save agent image"));
21468
+ }).catch((err) => log34.error({ err }, "Failed to save agent image"));
21387
21469
  }
21388
21470
  break;
21389
21471
  }
@@ -21400,12 +21482,12 @@ var init_session_bridge = __esm({
21400
21482
  text: "",
21401
21483
  attachment: att
21402
21484
  });
21403
- }).catch((err) => log33.error({ err }, "Failed to save agent audio"));
21485
+ }).catch((err) => log34.error({ err }, "Failed to save agent audio"));
21404
21486
  }
21405
21487
  break;
21406
21488
  }
21407
21489
  case "commands_update":
21408
- log33.debug({ commands: event.commands }, "Commands available");
21490
+ log34.debug({ commands: event.commands }, "Commands available");
21409
21491
  this.adapter.sendSkillCommands?.(this.session.id, event.commands);
21410
21492
  break;
21411
21493
  case "system_message":
@@ -21485,7 +21567,7 @@ var init_session_bridge = __esm({
21485
21567
  if (permReq.description.toLowerCase().includes("openacp")) {
21486
21568
  const allowOption = permReq.options.find((o) => o.isAllow);
21487
21569
  if (allowOption) {
21488
- log33.info(
21570
+ log34.info(
21489
21571
  { sessionId: this.session.id, requestId: permReq.id },
21490
21572
  "Auto-approving openacp command"
21491
21573
  );
@@ -21505,7 +21587,7 @@ var init_session_bridge = __esm({
21505
21587
  if (this.session.dangerousMode) {
21506
21588
  const allowOption = permReq.options.find((o) => o.isAllow);
21507
21589
  if (allowOption) {
21508
- log33.info(
21590
+ log34.info(
21509
21591
  { sessionId: this.session.id, requestId: permReq.id, optionId: allowOption.id },
21510
21592
  "Dangerous mode: auto-approving permission"
21511
21593
  );
@@ -21553,13 +21635,17 @@ var init_session_bridge = __esm({
21553
21635
  }
21554
21636
  };
21555
21637
  this.session.on("status_change", this.statusChangeHandler);
21556
- this.namedHandler = (name) => {
21557
- 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 });
21558
21642
  this.deps.eventBus?.emit("session:updated", {
21559
21643
  sessionId: this.session.id,
21560
21644
  name
21561
21645
  });
21562
- this.adapter.renameSessionThread(this.session.id, name);
21646
+ if (!alreadyNamed) {
21647
+ await this.adapter.renameSessionThread(this.session.id, name);
21648
+ }
21563
21649
  };
21564
21650
  this.session.on("named", this.namedHandler);
21565
21651
  this.promptCountHandler = (count) => {
@@ -21717,13 +21803,13 @@ function computeLineDiff(oldStr, newStr) {
21717
21803
  removed: Math.max(0, oldLines.length - prefixLen - suffixLen)
21718
21804
  };
21719
21805
  }
21720
- var log34, MessageTransformer;
21806
+ var log35, MessageTransformer;
21721
21807
  var init_message_transformer = __esm({
21722
21808
  "src/core/message-transformer.ts"() {
21723
21809
  "use strict";
21724
21810
  init_extract_file_info();
21725
21811
  init_log();
21726
- log34 = createChildLogger({ module: "message-transformer" });
21812
+ log35 = createChildLogger({ module: "message-transformer" });
21727
21813
  MessageTransformer = class {
21728
21814
  tunnelService;
21729
21815
  /** Cache rawInput from tool_call so it's available in tool_update (which often lacks it) */
@@ -21889,14 +21975,14 @@ var init_message_transformer = __esm({
21889
21975
  }
21890
21976
  }
21891
21977
  if (!this.tunnelService || !sessionContext) {
21892
- log34.debug(
21978
+ log35.debug(
21893
21979
  { hasTunnel: !!this.tunnelService, hasCtx: !!sessionContext, kind },
21894
21980
  "enrichWithViewerLinks: skipping (no tunnel or session context)"
21895
21981
  );
21896
21982
  return;
21897
21983
  }
21898
21984
  const name = "name" in event ? event.name || "" : "";
21899
- log34.debug(
21985
+ log35.debug(
21900
21986
  { name, kind, status: event.status, hasContent: !!event.content, hasRawInput: !!event.rawInput },
21901
21987
  "enrichWithViewerLinks: inspecting event"
21902
21988
  );
@@ -21908,7 +21994,7 @@ var init_message_transformer = __esm({
21908
21994
  event.meta
21909
21995
  );
21910
21996
  if (!fileInfo) {
21911
- log34.debug(
21997
+ log35.debug(
21912
21998
  { name, kind, hasContent: !!event.content, hasRawInput: !!event.rawInput, hasMeta: !!event.meta },
21913
21999
  "enrichWithViewerLinks: extractFileInfo returned null"
21914
22000
  );
@@ -21916,10 +22002,10 @@ var init_message_transformer = __esm({
21916
22002
  }
21917
22003
  const publicUrl = this.tunnelService.getPublicUrl();
21918
22004
  if (publicUrl.startsWith("http://localhost") || publicUrl.startsWith("http://127.0.0.1")) {
21919
- 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)");
21920
22006
  return;
21921
22007
  }
21922
- log34.info(
22008
+ log35.info(
21923
22009
  {
21924
22010
  name,
21925
22011
  kind,
@@ -21965,12 +22051,12 @@ var init_message_transformer = __esm({
21965
22051
  // src/core/sessions/session-store.ts
21966
22052
  import fs45 from "fs";
21967
22053
  import path51 from "path";
21968
- var log35, DEBOUNCE_MS2, JsonFileSessionStore;
22054
+ var log36, DEBOUNCE_MS2, JsonFileSessionStore;
21969
22055
  var init_session_store = __esm({
21970
22056
  "src/core/sessions/session-store.ts"() {
21971
22057
  "use strict";
21972
22058
  init_log();
21973
- log35 = createChildLogger({ module: "session-store" });
22059
+ log36 = createChildLogger({ module: "session-store" });
21974
22060
  DEBOUNCE_MS2 = 2e3;
21975
22061
  JsonFileSessionStore = class {
21976
22062
  records = /* @__PURE__ */ new Map();
@@ -22058,7 +22144,7 @@ var init_session_store = __esm({
22058
22144
  fs45.readFileSync(this.filePath, "utf-8")
22059
22145
  );
22060
22146
  if (raw.version !== 1) {
22061
- log35.warn(
22147
+ log36.warn(
22062
22148
  { version: raw.version },
22063
22149
  "Unknown session store version, skipping load"
22064
22150
  );
@@ -22067,9 +22153,9 @@ var init_session_store = __esm({
22067
22153
  for (const [id, record] of Object.entries(raw.sessions)) {
22068
22154
  this.records.set(id, record);
22069
22155
  }
22070
- log35.debug({ count: this.records.size }, "Loaded session records");
22156
+ log36.debug({ count: this.records.size }, "Loaded session records");
22071
22157
  } catch (err) {
22072
- 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");
22073
22159
  try {
22074
22160
  fs45.renameSync(this.filePath, `${this.filePath}.bak`);
22075
22161
  } catch {
@@ -22089,7 +22175,7 @@ var init_session_store = __esm({
22089
22175
  }
22090
22176
  }
22091
22177
  if (removed > 0) {
22092
- log35.info({ removed }, "Cleaned up expired session records");
22178
+ log36.info({ removed }, "Cleaned up expired session records");
22093
22179
  this.scheduleDiskWrite();
22094
22180
  }
22095
22181
  }
@@ -22104,19 +22190,20 @@ var init_session_store = __esm({
22104
22190
  });
22105
22191
 
22106
22192
  // src/core/sessions/session-factory.ts
22107
- var log36, SessionFactory;
22193
+ var log37, SessionFactory;
22108
22194
  var init_session_factory = __esm({
22109
22195
  "src/core/sessions/session-factory.ts"() {
22110
22196
  "use strict";
22111
22197
  init_session2();
22112
22198
  init_log();
22113
- log36 = createChildLogger({ module: "session-factory" });
22199
+ log37 = createChildLogger({ module: "session-factory" });
22114
22200
  SessionFactory = class {
22115
- constructor(agentManager, sessionManager, speechServiceAccessor, eventBus) {
22201
+ constructor(agentManager, sessionManager, speechServiceAccessor, eventBus, instanceRoot) {
22116
22202
  this.agentManager = agentManager;
22117
22203
  this.sessionManager = sessionManager;
22118
22204
  this.speechServiceAccessor = speechServiceAccessor;
22119
22205
  this.eventBus = eventBus;
22206
+ this.instanceRoot = instanceRoot;
22120
22207
  }
22121
22208
  middlewareChain;
22122
22209
  get speechService() {
@@ -22143,14 +22230,73 @@ var init_session_factory = __esm({
22143
22230
  channelId: result.channelId
22144
22231
  };
22145
22232
  }
22146
- const agentInstance = createParams.resumeAgentSessionId ? await this.agentManager.resume(
22147
- createParams.agentName,
22148
- createParams.workingDirectory,
22149
- createParams.resumeAgentSessionId
22150
- ) : await this.agentManager.spawn(
22151
- createParams.agentName,
22152
- createParams.workingDirectory
22153
- );
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
+ }
22154
22300
  agentInstance.middlewareChain = this.middlewareChain;
22155
22301
  const session = new Session({
22156
22302
  id: createParams.existingSessionId,
@@ -22583,7 +22729,7 @@ function createPluginContext(opts) {
22583
22729
  }
22584
22730
  };
22585
22731
  const baseLog = opts.log ?? noopLog;
22586
- const log39 = typeof baseLog.child === "function" ? baseLog.child({ plugin: pluginName }) : baseLog;
22732
+ const log40 = typeof baseLog.child === "function" ? baseLog.child({ plugin: pluginName }) : baseLog;
22587
22733
  const storageImpl = new PluginStorageImpl(storagePath);
22588
22734
  const storage = {
22589
22735
  async get(key) {
@@ -22610,7 +22756,7 @@ function createPluginContext(opts) {
22610
22756
  const ctx = {
22611
22757
  pluginName,
22612
22758
  pluginConfig,
22613
- log: log39,
22759
+ log: log40,
22614
22760
  storage,
22615
22761
  on(event, handler) {
22616
22762
  requirePermission(permissions, "events:read", "on()");
@@ -22645,7 +22791,7 @@ function createPluginContext(opts) {
22645
22791
  const registry = serviceRegistry.get("command-registry");
22646
22792
  if (registry && typeof registry.register === "function") {
22647
22793
  registry.register(def, pluginName);
22648
- log39.debug(`Command '/${def.name}' registered`);
22794
+ log40.debug(`Command '/${def.name}' registered`);
22649
22795
  }
22650
22796
  },
22651
22797
  async sendMessage(_sessionId, _content) {
@@ -22966,7 +23112,7 @@ var init_lifecycle_manager = __esm({
22966
23112
  // src/core/core.ts
22967
23113
  import path54 from "path";
22968
23114
  import os26 from "os";
22969
- var log37, OpenACPCore;
23115
+ var log38, OpenACPCore;
22970
23116
  var init_core = __esm({
22971
23117
  "src/core/core.ts"() {
22972
23118
  "use strict";
@@ -22984,7 +23130,7 @@ var init_core = __esm({
22984
23130
  init_middleware_chain();
22985
23131
  init_error_tracker();
22986
23132
  init_log();
22987
- log37 = createChildLogger({ module: "core" });
23133
+ log38 = createChildLogger({ module: "core" });
22988
23134
  OpenACPCore = class {
22989
23135
  configManager;
22990
23136
  agentCatalog;
@@ -23045,7 +23191,8 @@ var init_core = __esm({
23045
23191
  this.agentManager,
23046
23192
  this.sessionManager,
23047
23193
  () => this.speechService,
23048
- this.eventBus
23194
+ this.eventBus,
23195
+ ctx?.root
23049
23196
  );
23050
23197
  this.lifecycleManager = new LifecycleManager({
23051
23198
  serviceRegistry: new ServiceRegistry(),
@@ -23066,7 +23213,7 @@ var init_core = __esm({
23066
23213
  if (configPath === "logging.level" && typeof value === "string") {
23067
23214
  const { setLogLevel: setLogLevel2 } = await Promise.resolve().then(() => (init_log(), log_exports));
23068
23215
  setLogLevel2(value);
23069
- log37.info({ level: value }, "Log level changed at runtime");
23216
+ log38.info({ level: value }, "Log level changed at runtime");
23070
23217
  }
23071
23218
  if (configPath.startsWith("speech.")) {
23072
23219
  const speechSvc = this.speechService;
@@ -23077,7 +23224,7 @@ var init_core = __esm({
23077
23224
  tts: { provider: null, providers: {} }
23078
23225
  };
23079
23226
  speechSvc.refreshProviders(newSpeechConfig);
23080
- log37.info("Speech service config updated at runtime");
23227
+ log38.info("Speech service config updated at runtime");
23081
23228
  }
23082
23229
  }
23083
23230
  }
@@ -23095,10 +23242,21 @@ var init_core = __esm({
23095
23242
  }
23096
23243
  async start() {
23097
23244
  this.agentCatalog.refreshRegistryIfStale().catch((err) => {
23098
- log37.warn({ err }, "Background registry refresh failed");
23245
+ log38.warn({ err }, "Background registry refresh failed");
23099
23246
  });
23100
- for (const adapter of this.adapters.values()) {
23101
- await adapter.start();
23247
+ const failures = [];
23248
+ for (const [name, adapter] of this.adapters.entries()) {
23249
+ try {
23250
+ await adapter.start();
23251
+ } catch (err) {
23252
+ log38.error({ err, adapter: name }, `Adapter "${name}" failed to start`);
23253
+ failures.push({ name, error: err });
23254
+ }
23255
+ }
23256
+ if (failures.length > 0 && failures.length === this.adapters.size) {
23257
+ throw new Error(
23258
+ `All adapters failed to start: ${failures.map((f) => f.name).join(", ")}`
23259
+ );
23102
23260
  }
23103
23261
  }
23104
23262
  async stop() {
@@ -23129,20 +23287,9 @@ var init_core = __esm({
23129
23287
  if (!adapter) return { ok: false, error: "Adapter not found for session" };
23130
23288
  if (!adapter.archiveSessionTopic) return { ok: false, error: "Adapter does not support topic archiving" };
23131
23289
  try {
23132
- const newThreadId = await adapter.archiveSessionTopic(session.id);
23133
- session.threadId = newThreadId;
23134
- try {
23135
- const platform2 = {};
23136
- if (session.channelId === "telegram") {
23137
- platform2.topicId = Number(newThreadId);
23138
- } else {
23139
- platform2.threadId = newThreadId;
23140
- }
23141
- await this.sessionManager.patchRecord(sessionId, { platform: platform2 });
23142
- } catch (patchErr) {
23143
- log37.warn({ err: patchErr, sessionId }, "Failed to update session record after archive \u2014 session will work but may not survive restart");
23144
- }
23145
- return { ok: true, newThreadId };
23290
+ await adapter.archiveSessionTopic(session.id);
23291
+ await this.sessionManager.cancelSession(sessionId);
23292
+ return { ok: true };
23146
23293
  } catch (err) {
23147
23294
  session.archiving = false;
23148
23295
  return { ok: false, error: err.message };
@@ -23150,7 +23297,7 @@ var init_core = __esm({
23150
23297
  }
23151
23298
  // --- Message Routing ---
23152
23299
  async handleMessage(message) {
23153
- log37.debug(
23300
+ log38.debug(
23154
23301
  {
23155
23302
  channelId: message.channelId,
23156
23303
  threadId: message.threadId,
@@ -23169,7 +23316,7 @@ var init_core = __esm({
23169
23316
  }
23170
23317
  const access2 = this.securityGuard.checkAccess(message);
23171
23318
  if (!access2.allowed) {
23172
- log37.warn({ userId: message.userId, reason: access2.reason }, "Access denied");
23319
+ log38.warn({ userId: message.userId, reason: access2.reason }, "Access denied");
23173
23320
  if (access2.reason.includes("Session limit")) {
23174
23321
  const adapter = this.adapters.get(message.channelId);
23175
23322
  if (adapter) {
@@ -23189,7 +23336,7 @@ var init_core = __esm({
23189
23336
  session = await this.lazyResume(message) ?? void 0;
23190
23337
  }
23191
23338
  if (!session) {
23192
- log37.warn(
23339
+ log38.warn(
23193
23340
  { channelId: message.channelId, threadId: message.threadId },
23194
23341
  "No session found for thread (in-memory miss + lazy resume returned null)"
23195
23342
  );
@@ -23249,7 +23396,7 @@ var init_core = __esm({
23249
23396
  currentPromptCount: session.promptCount,
23250
23397
  agentSwitchHistory: session.agentSwitchHistory
23251
23398
  });
23252
- log37.info(
23399
+ log38.info(
23253
23400
  { sessionId: session.id, agentName: params.agentName },
23254
23401
  "Session created via pipeline"
23255
23402
  );
@@ -23258,7 +23405,7 @@ var init_core = __esm({
23258
23405
  async handleNewSession(channelId, agentName, workspacePath, options) {
23259
23406
  const config = this.configManager.get();
23260
23407
  const resolvedAgent = agentName || config.defaultAgent;
23261
- log37.info({ channelId, agentName: resolvedAgent }, "New session request");
23408
+ log38.info({ channelId, agentName: resolvedAgent }, "New session request");
23262
23409
  const agentDef = this.agentCatalog.resolve(resolvedAgent);
23263
23410
  const resolvedWorkspace = this.configManager.resolveWorkspace(
23264
23411
  workspacePath || agentDef?.workingDirectory
@@ -23407,7 +23554,7 @@ var init_core = __esm({
23407
23554
  params.contextOptions
23408
23555
  );
23409
23556
  } catch (err) {
23410
- log37.warn({ err }, "Context building failed, proceeding without context");
23557
+ log38.warn({ err }, "Context building failed, proceeding without context");
23411
23558
  }
23412
23559
  const session = await this.createSession({
23413
23560
  channelId: params.channelId,
@@ -23449,6 +23596,21 @@ var init_core = __esm({
23449
23596
  const caps = getAgentCapabilities(toAgent);
23450
23597
  const canResume = !!(lastEntry && caps.supportsResume && lastEntry.promptCount === 0);
23451
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
+ });
23452
23614
  const bridge = this.bridges.get(sessionId);
23453
23615
  if (bridge) bridge.disconnect();
23454
23616
  const switchAdapter = this.adapters.get(session.channelId);
@@ -23483,7 +23645,40 @@ var init_core = __esm({
23483
23645
  return instance;
23484
23646
  }
23485
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
+ });
23486
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
+ });
23487
23682
  try {
23488
23683
  let rollbackInstance;
23489
23684
  try {
@@ -23501,10 +23696,10 @@ var init_core = __esm({
23501
23696
  const rollbackBridge = this.createBridge(session, adapter);
23502
23697
  rollbackBridge.connect();
23503
23698
  }
23504
- 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");
23505
23700
  } catch (rollbackErr) {
23506
23701
  session.fail(`Switch failed and rollback failed: ${rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr)}`);
23507
- 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");
23508
23703
  }
23509
23704
  throw err;
23510
23705
  }
@@ -23552,14 +23747,14 @@ var init_core = __esm({
23552
23747
  (p2) => String(p2.topicId) === message.threadId
23553
23748
  );
23554
23749
  if (!record) {
23555
- log37.debug(
23750
+ log38.debug(
23556
23751
  { threadId: message.threadId, channelId: message.channelId },
23557
23752
  "No session record found for thread"
23558
23753
  );
23559
23754
  return null;
23560
23755
  }
23561
23756
  if (record.status === "error" || record.status === "cancelled") {
23562
- log37.debug(
23757
+ log38.debug(
23563
23758
  {
23564
23759
  threadId: message.threadId,
23565
23760
  sessionId: record.sessionId,
@@ -23569,7 +23764,7 @@ var init_core = __esm({
23569
23764
  );
23570
23765
  return null;
23571
23766
  }
23572
- log37.info(
23767
+ log38.info(
23573
23768
  {
23574
23769
  threadId: message.threadId,
23575
23770
  sessionId: record.sessionId,
@@ -23593,13 +23788,13 @@ var init_core = __esm({
23593
23788
  if (record.firstAgent) session.firstAgent = record.firstAgent;
23594
23789
  if (record.agentSwitchHistory) session.agentSwitchHistory = record.agentSwitchHistory;
23595
23790
  if (record.currentPromptCount != null) session.promptCount = record.currentPromptCount;
23596
- log37.info(
23791
+ log38.info(
23597
23792
  { sessionId: session.id, threadId: message.threadId },
23598
23793
  "Lazy resume successful"
23599
23794
  );
23600
23795
  return session;
23601
23796
  } catch (err) {
23602
- log37.error({ err, record }, "Lazy resume failed");
23797
+ log38.error({ err, record }, "Lazy resume failed");
23603
23798
  const adapter = this.adapters.get(message.channelId);
23604
23799
  if (adapter) {
23605
23800
  try {
@@ -24094,14 +24289,14 @@ async function runPostUpgradeChecks(config) {
24094
24289
  const { ensureCloudflared: ensureCloudflared2 } = await Promise.resolve().then(() => (init_install_cloudflared(), install_cloudflared_exports));
24095
24290
  await ensureCloudflared2();
24096
24291
  } catch (err) {
24097
- log38.warn(
24292
+ log39.warn(
24098
24293
  { err: err.message },
24099
24294
  "Could not install cloudflared. Tunnel may not work."
24100
24295
  );
24101
24296
  }
24102
24297
  } else {
24103
24298
  if (!commandExists(config.tunnel.provider)) {
24104
- log38.warn(
24299
+ log39.warn(
24105
24300
  `Tunnel provider "${config.tunnel.provider}" is not installed. Install it or switch to cloudflare (free, auto-installed).`
24106
24301
  );
24107
24302
  }
@@ -24113,7 +24308,7 @@ async function runPostUpgradeChecks(config) {
24113
24308
  if (integration) {
24114
24309
  const allInstalled = integration.items.every((item) => item.isInstalled());
24115
24310
  if (!allInstalled) {
24116
- log38.info(
24311
+ log39.info(
24117
24312
  'Claude CLI integration not installed. Run "openacp integrate claude" for session transfer + tunnel skill.'
24118
24313
  );
24119
24314
  }
@@ -24123,7 +24318,7 @@ async function runPostUpgradeChecks(config) {
24123
24318
  const { ensureJq: ensureJq2 } = await Promise.resolve().then(() => (init_install_jq(), install_jq_exports));
24124
24319
  await ensureJq2();
24125
24320
  } catch (err) {
24126
- log38.warn(
24321
+ log39.warn(
24127
24322
  { err: err.message },
24128
24323
  "Could not install jq. Handoff hooks may not work."
24129
24324
  );
@@ -24133,7 +24328,7 @@ async function runPostUpgradeChecks(config) {
24133
24328
  } catch {
24134
24329
  }
24135
24330
  if (!commandExists("unzip")) {
24136
- log38.warn(
24331
+ log39.warn(
24137
24332
  "unzip is not installed. Some agent installations (binary distribution) may fail. Install: brew install unzip (macOS) or apt install unzip (Linux)"
24138
24333
  );
24139
24334
  }
@@ -24146,20 +24341,20 @@ async function runPostUpgradeChecks(config) {
24146
24341
  (a) => a.distribution === "uvx"
24147
24342
  );
24148
24343
  if (hasUvxAgent && !commandExists("uvx")) {
24149
- log38.warn(
24344
+ log39.warn(
24150
24345
  "uvx is not installed but you have Python-based agents. Install: pip install uv"
24151
24346
  );
24152
24347
  }
24153
24348
  } catch {
24154
24349
  }
24155
24350
  }
24156
- var log38;
24351
+ var log39;
24157
24352
  var init_post_upgrade = __esm({
24158
24353
  "src/cli/post-upgrade.ts"() {
24159
24354
  "use strict";
24160
24355
  init_log();
24161
24356
  init_agent_dependencies();
24162
- log38 = createChildLogger({ module: "post-upgrade" });
24357
+ log39 = createChildLogger({ module: "post-upgrade" });
24163
24358
  }
24164
24359
  });
24165
24360
 
@@ -26323,7 +26518,7 @@ a "Handoff" slash command to Claude Code.
26323
26518
  if (uninstall) {
26324
26519
  console.log(`Removing ${agent}/${item.id}...`);
26325
26520
  const result = await item.uninstall();
26326
- for (const log39 of result.logs) console.log(` ${log39}`);
26521
+ for (const log40 of result.logs) console.log(` ${log40}`);
26327
26522
  if (result.success) {
26328
26523
  console.log(` ${item.name} removed.`);
26329
26524
  } else {
@@ -26333,7 +26528,7 @@ a "Handoff" slash command to Claude Code.
26333
26528
  } else {
26334
26529
  console.log(`Installing ${agent}/${item.id}...`);
26335
26530
  const result = await item.install();
26336
- for (const log39 of result.logs) console.log(` ${log39}`);
26531
+ for (const log40 of result.logs) console.log(` ${log40}`);
26337
26532
  if (result.success) {
26338
26533
  console.log(` ${item.name} installed.`);
26339
26534
  } else {