@axiom-lattice/gateway 2.1.87 → 2.1.89

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1715,7 +1715,7 @@ async function executeDataQuery(request, reply) {
1715
1715
  message: `Metrics server not registered: ${body.serverKey}. Please register the server first.`
1716
1716
  };
1717
1717
  }
1718
- const client = import_core12.metricsServerManager.getClient(tenantId, body.serverKey);
1718
+ const client = await import_core12.metricsServerManager.getClient(tenantId, body.serverKey);
1719
1719
  if (isSemanticQuery) {
1720
1720
  return await executeSemanticQuery(client, body, reply);
1721
1721
  } else {
@@ -3915,7 +3915,7 @@ async function testMetricsServerConnection(request, reply) {
3915
3915
  const testKey = `__test_${key}_${Date.now()}`;
3916
3916
  import_core21.metricsServerManager.registerServer(tenantId, testKey, config.config);
3917
3917
  try {
3918
- const client = import_core21.metricsServerManager.getClient(tenantId, testKey);
3918
+ const client = await import_core21.metricsServerManager.getClient(tenantId, testKey);
3919
3919
  const result = await client.testConnection();
3920
3920
  import_core21.metricsServerManager.removeServer(tenantId, testKey);
3921
3921
  return {
@@ -3966,7 +3966,7 @@ async function listAvailableMetrics(request, reply) {
3966
3966
  if (!import_core21.metricsServerManager.hasServer(tenantId, key)) {
3967
3967
  import_core21.metricsServerManager.registerServer(tenantId, key, config.config);
3968
3968
  }
3969
- const client = import_core21.metricsServerManager.getClient(tenantId, key);
3969
+ const client = await import_core21.metricsServerManager.getClient(tenantId, key);
3970
3970
  const metrics = await client.listMetrics();
3971
3971
  return {
3972
3972
  success: true,
@@ -4012,7 +4012,7 @@ async function queryMetricsData(request, reply) {
4012
4012
  if (!import_core21.metricsServerManager.hasServer(tenantId, key)) {
4013
4013
  import_core21.metricsServerManager.registerServer(tenantId, key, config.config);
4014
4014
  }
4015
- const client = import_core21.metricsServerManager.getClient(tenantId, key);
4015
+ const client = await import_core21.metricsServerManager.getClient(tenantId, key);
4016
4016
  const result = await client.queryMetricData(metricName, {
4017
4017
  startTime,
4018
4018
  endTime,
@@ -6354,19 +6354,8 @@ function registerChannelInstallationRoutes(app2) {
6354
6354
  app2.delete("/api/channel-installations/:installationId", deleteChannelInstallation);
6355
6355
  }
6356
6356
 
6357
- // src/bindings/index.ts
6358
- var registryInstance = null;
6359
- function setBindingRegistry(registry) {
6360
- registryInstance = registry;
6361
- }
6362
- function getBindingRegistry() {
6363
- if (!registryInstance) {
6364
- throw new Error("BindingRegistry not initialized. Call setBindingRegistry() first.");
6365
- }
6366
- return registryInstance;
6367
- }
6368
-
6369
6357
  // src/controllers/channel-bindings.ts
6358
+ var import_core28 = require("@axiom-lattice/core");
6370
6359
  function getTenantId12(request) {
6371
6360
  const userTenantId = request.user?.tenantId;
6372
6361
  if (userTenantId) return userTenantId;
@@ -6376,7 +6365,7 @@ async function getBindingList(request, _reply) {
6376
6365
  const tenantId = getTenantId12(request);
6377
6366
  const { channel, agentId, channelInstallationId, limit, offset } = request.query;
6378
6367
  try {
6379
- const registry = getBindingRegistry();
6368
+ const registry = (0, import_core28.getBindingRegistry)();
6380
6369
  const bindings = await registry.list({ channel, agentId, tenantId, channelInstallationId, limit, offset });
6381
6370
  return { success: true, message: "Bindings retrieved", data: { records: bindings, total: bindings.length } };
6382
6371
  } catch (error) {
@@ -6387,7 +6376,7 @@ async function getBindingList(request, _reply) {
6387
6376
  async function getBinding(request, reply) {
6388
6377
  const tenantId = getTenantId12(request);
6389
6378
  try {
6390
- const registry = getBindingRegistry();
6379
+ const registry = (0, import_core28.getBindingRegistry)();
6391
6380
  const bindings = await registry.list({ tenantId });
6392
6381
  const binding = bindings.find((b) => b.id === request.params.id);
6393
6382
  if (!binding || binding.tenantId !== tenantId) {
@@ -6403,7 +6392,7 @@ async function getBinding(request, reply) {
6403
6392
  async function createBinding(request, reply) {
6404
6393
  const tenantId = getTenantId12(request);
6405
6394
  try {
6406
- const registry = getBindingRegistry();
6395
+ const registry = (0, import_core28.getBindingRegistry)();
6407
6396
  const binding = await registry.create({ ...request.body, tenantId });
6408
6397
  reply.status(201);
6409
6398
  return { success: true, message: "Binding created", data: binding };
@@ -6416,7 +6405,7 @@ async function createBinding(request, reply) {
6416
6405
  async function updateBinding(request, reply) {
6417
6406
  try {
6418
6407
  const tenantId = getTenantId12(request);
6419
- const registry = getBindingRegistry();
6408
+ const registry = (0, import_core28.getBindingRegistry)();
6420
6409
  const bindings = await registry.list({ tenantId });
6421
6410
  const existing = bindings.find((b) => b.id === request.params.id);
6422
6411
  if (!existing || existing.tenantId !== tenantId) {
@@ -6434,7 +6423,7 @@ async function updateBinding(request, reply) {
6434
6423
  async function deleteBinding(request, reply) {
6435
6424
  try {
6436
6425
  const tenantId = getTenantId12(request);
6437
- const registry = getBindingRegistry();
6426
+ const registry = (0, import_core28.getBindingRegistry)();
6438
6427
  const bindings = await registry.list({ tenantId });
6439
6428
  const existing = bindings.find((b) => b.id === request.params.id);
6440
6429
  if (!existing || existing.tenantId !== tenantId) {
@@ -6453,7 +6442,7 @@ async function resolveBinding(request, _reply) {
6453
6442
  const tenantId = getTenantId12(request);
6454
6443
  const { channel, senderId, channelInstallationId } = request.query;
6455
6444
  try {
6456
- const registry = getBindingRegistry();
6445
+ const registry = (0, import_core28.getBindingRegistry)();
6457
6446
  const binding = await registry.resolve({ channel, senderId, channelInstallationId, tenantId });
6458
6447
  if (!binding) {
6459
6448
  return { success: false, message: "No binding found", data: null };
@@ -6690,7 +6679,7 @@ var registerLatticeRoutes = (app2, channelDeps) => {
6690
6679
  };
6691
6680
 
6692
6681
  // src/router/MessageRouter.ts
6693
- var import_core28 = require("@axiom-lattice/core");
6682
+ var import_core29 = require("@axiom-lattice/core");
6694
6683
  var import_crypto8 = require("crypto");
6695
6684
  var BindingNotFoundError = class extends Error {
6696
6685
  constructor(message) {
@@ -6700,14 +6689,39 @@ var BindingNotFoundError = class extends Error {
6700
6689
  };
6701
6690
  var MessageRouter = class {
6702
6691
  constructor(config) {
6692
+ /**
6693
+ * Tracks reply subscriptions per thread+channel to avoid duplicate
6694
+ * `subscribeOnce` registrations and ensure proper cleanup.
6695
+ *
6696
+ * Key format: `{threadId}:{adapterChannel}:reply`
6697
+ */
6698
+ this._replySubs = /* @__PURE__ */ new Map();
6703
6699
  this.middlewares = [...config.middlewares];
6704
6700
  this.bindingRegistry = config.bindingRegistry;
6705
6701
  this.adapterRegistry = config.adapterRegistry;
6706
6702
  this.installationStore = config.installationStore;
6707
6703
  }
6704
+ /**
6705
+ * Register an additional middleware at the end of the chain.
6706
+ *
6707
+ * @param middleware - A {@link MessageMiddleware} function
6708
+ */
6708
6709
  use(middleware) {
6709
6710
  this.middlewares.push(middleware);
6710
6711
  }
6712
+ /**
6713
+ * Dispatch an inbound channel message to the bound agent.
6714
+ *
6715
+ * Full pipeline: middleware chain → binding resolution → thread lifecycle
6716
+ * → agent.addMessage() → (async) reply via {@link ChannelAdapter.sendReply}.
6717
+ *
6718
+ * If the message has a {@link InboundMessage.replyTarget}, the router subscribes
6719
+ * to the agent's `reply:ready` event before enqueuing the message, and sends
6720
+ * the AI response back to the channel when it arrives.
6721
+ *
6722
+ * @param message - Normalized inbound message from a channel adapter
6723
+ * @returns {@link DispatchResult} with success status, bindingId, and threadId
6724
+ */
6711
6725
  async dispatch(message) {
6712
6726
  const ctx = {
6713
6727
  inboundMessage: message,
@@ -6806,7 +6820,7 @@ var MessageRouter = class {
6806
6820
  }
6807
6821
  let threadId = ctx.binding.threadMode === "per_conversation" ? void 0 : ctx.binding.threadId;
6808
6822
  if (!threadId) {
6809
- const threadStore = (0, import_core28.getStoreLattice)("default", "thread").store;
6823
+ const threadStore = (0, import_core29.getStoreLattice)("default", "thread").store;
6810
6824
  const newThreadId = (0, import_crypto8.randomUUID)();
6811
6825
  console.log({
6812
6826
  event: "dispatch:thread:create",
@@ -6847,39 +6861,103 @@ var MessageRouter = class {
6847
6861
  senderId: message.sender.id,
6848
6862
  contentLength: message.content.text.length
6849
6863
  }, "Dispatching to agent");
6850
- const agent = import_core28.agentInstanceManager.getAgent({
6864
+ const agent = import_core29.agentInstanceManager.getAgent({
6851
6865
  tenant_id: tenantId,
6852
6866
  assistant_id: ctx.binding.agentId,
6853
6867
  thread_id: threadId,
6854
6868
  workspace_id: ctx.binding.workspaceId || "",
6855
6869
  project_id: ctx.binding.projectId || ""
6856
6870
  });
6857
- const addResult = await agent.addMessage({
6858
- input: { message: message.content.text },
6859
- custom_run_config: message.content.metadata || {}
6860
- });
6861
- console.log({
6862
- event: "dispatch:complete",
6863
- agentId: ctx.binding.agentId,
6864
- threadId,
6865
- messageId: addResult?.messageId,
6866
- result: JSON.stringify(addResult)
6867
- }, "Agent dispatch complete \u2014 messageId = " + (addResult?.messageId || "N/A"));
6868
6871
  if (message.replyTarget) {
6872
+ const replySubKey = `${threadId}:${message.replyTarget.adapterChannel}:reply`;
6869
6873
  const adapter = this.adapterRegistry.get(message.replyTarget.adapterChannel);
6870
6874
  if (adapter) {
6871
6875
  const installation = await this.installationStore.getInstallationById(
6872
6876
  message.channelInstallationId
6873
6877
  );
6874
6878
  if (installation) {
6875
- await adapter.sendReply(
6876
- message.replyTarget,
6877
- { text: ctx.result || "" },
6878
- installation
6879
- );
6879
+ const existing = this._replySubs.get(replySubKey);
6880
+ if (!existing || existing.count === 0) {
6881
+ const timer = setTimeout(() => {
6882
+ const entry = this._replySubs.get(replySubKey);
6883
+ if (entry) {
6884
+ this._replySubs.delete(replySubKey);
6885
+ console.warn({
6886
+ event: "dispatch:reply:timeout",
6887
+ threadId,
6888
+ channel: message.replyTarget.adapterChannel
6889
+ }, "Reply subscription timed out \u2014 no reply:ready fired within 1h");
6890
+ }
6891
+ }, 36e5);
6892
+ this._replySubs.set(replySubKey, { count: 1, timer });
6893
+ console.log({
6894
+ event: "dispatch:reply:subscribed",
6895
+ threadId,
6896
+ channel: message.replyTarget.adapterChannel,
6897
+ senderId: message.sender.id
6898
+ }, "Subscribed to reply:ready for thread");
6899
+ agent.subscribeOnce("reply:ready", (data) => {
6900
+ const messages = data.state?.values?.messages;
6901
+ const lastAI = messages?.filter((m) => m.type === "ai" || m.getType?.() === "ai").pop();
6902
+ const replyText = lastAI?.content ?? "";
6903
+ const entry = this._replySubs.get(replySubKey);
6904
+ if (entry) {
6905
+ entry.count--;
6906
+ if (entry.count <= 0) {
6907
+ clearTimeout(entry.timer);
6908
+ this._replySubs.delete(replySubKey);
6909
+ }
6910
+ }
6911
+ if (replyText) {
6912
+ console.log({
6913
+ event: "dispatch:reply:sending",
6914
+ threadId,
6915
+ channel: message.replyTarget.adapterChannel,
6916
+ replyLength: replyText.length
6917
+ }, "Sending channel reply");
6918
+ adapter.sendReply(message.replyTarget, { text: replyText }, installation).then(() => {
6919
+ console.log({
6920
+ event: "dispatch:reply:sent",
6921
+ threadId,
6922
+ channel: message.replyTarget.adapterChannel
6923
+ }, "Channel reply sent successfully");
6924
+ }).catch((err) => console.error({
6925
+ event: "dispatch:reply:failed",
6926
+ threadId,
6927
+ channel: message.replyTarget.adapterChannel,
6928
+ error: err instanceof Error ? err.message : String(err)
6929
+ }, "Failed to send channel reply"));
6930
+ } else {
6931
+ console.warn({
6932
+ event: "dispatch:reply:empty",
6933
+ threadId,
6934
+ channel: message.replyTarget.adapterChannel
6935
+ }, "Agent produced no text output \u2014 skipping reply");
6936
+ }
6937
+ });
6938
+ } else {
6939
+ existing.count++;
6940
+ console.log({
6941
+ event: "dispatch:reply:incremented",
6942
+ threadId,
6943
+ channel: message.replyTarget.adapterChannel,
6944
+ count: existing.count
6945
+ }, "Incremented reply counter for thread (already subscribed)");
6946
+ }
6880
6947
  }
6881
6948
  }
6882
6949
  }
6950
+ const addResult = await agent.addMessage({
6951
+ input: { message: message.content.text },
6952
+ custom_run_config: message.replyTarget ? { ...message.content.metadata || {}, _replyTarget: message.replyTarget } : message.content.metadata || {}
6953
+ });
6954
+ console.log({
6955
+ event: "dispatch:complete",
6956
+ agentId: ctx.binding.agentId,
6957
+ threadId,
6958
+ messageId: addResult?.messageId,
6959
+ result: JSON.stringify(addResult)
6960
+ }, "Agent dispatch complete \u2014 messageId = " + (addResult?.messageId || "N/A"));
6883
6961
  });
6884
6962
  return {
6885
6963
  success: true,
@@ -7027,7 +7105,7 @@ function createAuditLoggerMiddleware() {
7027
7105
  }
7028
7106
 
7029
7107
  // src/index.ts
7030
- var import_core30 = require("@axiom-lattice/core");
7108
+ var import_core31 = require("@axiom-lattice/core");
7031
7109
 
7032
7110
  // src/swagger.ts
7033
7111
  var import_swagger = __toESM(require("@fastify/swagger"));
@@ -7092,7 +7170,7 @@ var configureSwagger = async (app2, customSwaggerConfig, customSwaggerUiConfig)
7092
7170
  };
7093
7171
 
7094
7172
  // src/services/agent_task_consumer.ts
7095
- var import_core29 = require("@axiom-lattice/core");
7173
+ var import_core30 = require("@axiom-lattice/core");
7096
7174
  var handleAgentTask = async (taskRequest, retryCount = 0) => {
7097
7175
  const {
7098
7176
  assistant_id,
@@ -7110,18 +7188,18 @@ var handleAgentTask = async (taskRequest, retryCount = 0) => {
7110
7188
  console.log(
7111
7189
  `\u5F00\u59CB\u5904\u7406\u4EFB\u52A1 [assistant_id: ${assistant_id}, thread_id: ${thread_id}]`
7112
7190
  );
7113
- const agent = import_core29.agentInstanceManager.getAgent({ assistant_id, thread_id, tenant_id, workspace_id: runConfig?.workspaceId, project_id: runConfig?.projectId, custom_run_config: runConfig });
7114
- await agent.addMessage({ input, command, custom_run_config: runConfig }, import_core29.QueueMode.STEER);
7191
+ const agent = import_core30.agentInstanceManager.getAgent({ assistant_id, thread_id, tenant_id, workspace_id: runConfig?.workspaceId, project_id: runConfig?.projectId, custom_run_config: runConfig });
7192
+ await agent.addMessage({ input, command, custom_run_config: runConfig }, import_core30.QueueMode.STEER);
7115
7193
  if (callback_event) {
7116
7194
  agent.subscribeOnce("message:completed", (evt) => {
7117
- import_core29.eventBus.publish(callback_event, {
7195
+ import_core30.eventBus.publish(callback_event, {
7118
7196
  success: true,
7119
7197
  state: evt.state,
7120
7198
  config: { assistant_id, thread_id, tenant_id }
7121
7199
  });
7122
7200
  if (main_thread_id && main_tenant_id) {
7123
7201
  try {
7124
- const mainAgent = import_core29.agentInstanceManager.getAgent({
7202
+ const mainAgent = import_core30.agentInstanceManager.getAgent({
7125
7203
  assistant_id: main_assistant_id ?? assistant_id,
7126
7204
  thread_id: main_thread_id,
7127
7205
  tenant_id: main_tenant_id
@@ -7149,7 +7227,7 @@ ${summary}`
7149
7227
  }
7150
7228
  });
7151
7229
  agent.subscribeOnce("message:interrupted", (evt) => {
7152
- import_core29.eventBus.publish(callback_event, {
7230
+ import_core30.eventBus.publish(callback_event, {
7153
7231
  success: true,
7154
7232
  state: evt.state,
7155
7233
  config: { assistant_id, thread_id, tenant_id }
@@ -7173,7 +7251,7 @@ ${summary}`
7173
7251
  return handleAgentTask(taskRequest, nextRetryCount);
7174
7252
  }
7175
7253
  if (callback_event) {
7176
- import_core29.eventBus.publish(callback_event, {
7254
+ import_core30.eventBus.publish(callback_event, {
7177
7255
  success: false,
7178
7256
  error: error instanceof Error ? error.message : String(error),
7179
7257
  config: { assistant_id, thread_id, tenant_id }
@@ -7211,7 +7289,7 @@ var _AgentTaskConsumer = class _AgentTaskConsumer {
7211
7289
  * 初始化事件监听和队列轮询
7212
7290
  */
7213
7291
  initialize() {
7214
- import_core29.eventBus.subscribe(import_core29.AGENT_TASK_EVENT, this.trigger_agent_task.bind(this));
7292
+ import_core30.eventBus.subscribe(import_core30.AGENT_TASK_EVENT, this.trigger_agent_task.bind(this));
7215
7293
  this.startPollingQueue();
7216
7294
  console.log("Agent\u4EFB\u52A1\u6D88\u8D39\u8005\u5DF2\u542F\u52A8\u5E76\u76D1\u542C\u4EFB\u52A1\u4E8B\u4EF6\u548C\u961F\u5217");
7217
7295
  }
@@ -7330,7 +7408,7 @@ var _AgentTaskConsumer = class _AgentTaskConsumer {
7330
7408
  handleAgentTask(taskRequest).catch((error) => {
7331
7409
  console.error("\u5904\u7406Agent\u4EFB\u52A1\u65F6\u53D1\u751F\u672A\u6355\u83B7\u7684\u9519\u8BEF:", error);
7332
7410
  if (taskRequest.callback_event) {
7333
- import_core29.eventBus.publish(taskRequest.callback_event, {
7411
+ import_core30.eventBus.publish(taskRequest.callback_event, {
7334
7412
  success: false,
7335
7413
  error: error instanceof Error ? error.message : String(error),
7336
7414
  config: {
@@ -7350,7 +7428,7 @@ _AgentTaskConsumer.agent_run_endpoint = "http://localhost:4001/api/runs";
7350
7428
  var AgentTaskConsumer = _AgentTaskConsumer;
7351
7429
 
7352
7430
  // src/index.ts
7353
- var import_core31 = require("@axiom-lattice/core");
7431
+ var import_core32 = require("@axiom-lattice/core");
7354
7432
  var import_protocols4 = require("@axiom-lattice/protocols");
7355
7433
  var import_meta = {};
7356
7434
  process.on("unhandledRejection", (reason, promise) => {
@@ -7366,11 +7444,11 @@ var DEFAULT_LOGGER_CONFIG = {
7366
7444
  var loggerLattice = initializeLogger(DEFAULT_LOGGER_CONFIG);
7367
7445
  var logger3 = loggerLattice.client;
7368
7446
  function initializeLogger(config) {
7369
- if (import_core31.loggerLatticeManager.hasLattice("default")) {
7370
- import_core31.loggerLatticeManager.removeLattice("default");
7447
+ if (import_core32.loggerLatticeManager.hasLattice("default")) {
7448
+ import_core32.loggerLatticeManager.removeLattice("default");
7371
7449
  }
7372
- (0, import_core31.registerLoggerLattice)("default", config);
7373
- return (0, import_core31.getLoggerLattice)("default");
7450
+ (0, import_core32.registerLoggerLattice)("default", config);
7451
+ return (0, import_core32.getLoggerLattice)("default");
7374
7452
  }
7375
7453
  var app = (0, import_fastify.default)({
7376
7454
  logger: false,
@@ -7473,7 +7551,7 @@ app.setErrorHandler((error, request, reply) => {
7473
7551
  });
7474
7552
  function getConfiguredSandboxProvider() {
7475
7553
  const sandboxProviderType = process.env.SANDBOX_PROVIDER_TYPE || "microsandbox-remote";
7476
- return (0, import_core31.createSandboxProvider)({
7554
+ return (0, import_core32.createSandboxProvider)({
7477
7555
  type: sandboxProviderType,
7478
7556
  remoteBaseURL: process.env.SANDBOX_BASE_URL,
7479
7557
  microsandboxServiceBaseURL: process.env.MICROSANDBOX_SERVICE_BASE_URL,
@@ -7505,8 +7583,7 @@ var start = async (config) => {
7505
7583
  const { getStoreLattice: getStoreLattice16 } = await import("@axiom-lattice/core");
7506
7584
  const bindingStore = getStoreLattice16("default", "channelBinding").store;
7507
7585
  const installationStore = getStoreLattice16("default", "channelInstallation").store;
7508
- setBindingRegistry(bindingStore);
7509
- (0, import_core30.setBindingRegistry)(bindingStore);
7586
+ (0, import_core31.setBindingRegistry)(bindingStore);
7510
7587
  const adapterRegistry = new ChannelAdapterRegistry();
7511
7588
  adapterRegistry.register(larkChannelAdapter);
7512
7589
  const router = new MessageRouter({
@@ -7523,16 +7600,8 @@ var start = async (config) => {
7523
7600
  } catch {
7524
7601
  }
7525
7602
  registerLatticeRoutes(app, channelDeps);
7526
- try {
7527
- const storeLattice = (0, import_core31.getStoreLattice)("default", "database");
7528
- const store = storeLattice.store;
7529
- import_core31.sqlDatabaseManager.setConfigStore(store);
7530
- logger3.info("Database config store set for SqlDatabaseManager");
7531
- } catch (error) {
7532
- logger3.warn("Failed to set database config store: " + (error instanceof Error ? error.message : String(error)));
7533
- }
7534
- if (!import_core31.sandboxLatticeManager.hasLattice("default")) {
7535
- import_core31.sandboxLatticeManager.registerLattice("default", getConfiguredSandboxProvider());
7603
+ if (!import_core32.sandboxLatticeManager.hasLattice("default")) {
7604
+ import_core32.sandboxLatticeManager.registerLattice("default", getConfiguredSandboxProvider());
7536
7605
  logger3.info("Registered sandbox manager from env configuration");
7537
7606
  }
7538
7607
  const target_port = config?.port || Number(process.env.PORT) || 4001;
@@ -7553,7 +7622,7 @@ var start = async (config) => {
7553
7622
  }
7554
7623
  try {
7555
7624
  logger3.info("Starting agent instance recovery...");
7556
- const restoreStats = await import_core31.agentInstanceManager.restore();
7625
+ const restoreStats = await import_core32.agentInstanceManager.restore();
7557
7626
  logger3.info(`Agent recovery complete: ${restoreStats.restored} threads restored, ${restoreStats.errors} errors`);
7558
7627
  } catch (error) {
7559
7628
  logger3.error("Agent recovery failed", { error });