@axiom-lattice/gateway 2.1.88 → 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.mjs CHANGED
@@ -6637,14 +6637,39 @@ var BindingNotFoundError = class extends Error {
6637
6637
  };
6638
6638
  var MessageRouter = class {
6639
6639
  constructor(config) {
6640
+ /**
6641
+ * Tracks reply subscriptions per thread+channel to avoid duplicate
6642
+ * `subscribeOnce` registrations and ensure proper cleanup.
6643
+ *
6644
+ * Key format: `{threadId}:{adapterChannel}:reply`
6645
+ */
6646
+ this._replySubs = /* @__PURE__ */ new Map();
6640
6647
  this.middlewares = [...config.middlewares];
6641
6648
  this.bindingRegistry = config.bindingRegistry;
6642
6649
  this.adapterRegistry = config.adapterRegistry;
6643
6650
  this.installationStore = config.installationStore;
6644
6651
  }
6652
+ /**
6653
+ * Register an additional middleware at the end of the chain.
6654
+ *
6655
+ * @param middleware - A {@link MessageMiddleware} function
6656
+ */
6645
6657
  use(middleware) {
6646
6658
  this.middlewares.push(middleware);
6647
6659
  }
6660
+ /**
6661
+ * Dispatch an inbound channel message to the bound agent.
6662
+ *
6663
+ * Full pipeline: middleware chain → binding resolution → thread lifecycle
6664
+ * → agent.addMessage() → (async) reply via {@link ChannelAdapter.sendReply}.
6665
+ *
6666
+ * If the message has a {@link InboundMessage.replyTarget}, the router subscribes
6667
+ * to the agent's `reply:ready` event before enqueuing the message, and sends
6668
+ * the AI response back to the channel when it arrives.
6669
+ *
6670
+ * @param message - Normalized inbound message from a channel adapter
6671
+ * @returns {@link DispatchResult} with success status, bindingId, and threadId
6672
+ */
6648
6673
  async dispatch(message) {
6649
6674
  const ctx = {
6650
6675
  inboundMessage: message,
@@ -6791,32 +6816,96 @@ var MessageRouter = class {
6791
6816
  workspace_id: ctx.binding.workspaceId || "",
6792
6817
  project_id: ctx.binding.projectId || ""
6793
6818
  });
6794
- const addResult = await agent.addMessage({
6795
- input: { message: message.content.text },
6796
- custom_run_config: message.content.metadata || {}
6797
- });
6798
- console.log({
6799
- event: "dispatch:complete",
6800
- agentId: ctx.binding.agentId,
6801
- threadId,
6802
- messageId: addResult?.messageId,
6803
- result: JSON.stringify(addResult)
6804
- }, "Agent dispatch complete \u2014 messageId = " + (addResult?.messageId || "N/A"));
6805
6819
  if (message.replyTarget) {
6820
+ const replySubKey = `${threadId}:${message.replyTarget.adapterChannel}:reply`;
6806
6821
  const adapter = this.adapterRegistry.get(message.replyTarget.adapterChannel);
6807
6822
  if (adapter) {
6808
6823
  const installation = await this.installationStore.getInstallationById(
6809
6824
  message.channelInstallationId
6810
6825
  );
6811
6826
  if (installation) {
6812
- await adapter.sendReply(
6813
- message.replyTarget,
6814
- { text: ctx.result || "" },
6815
- installation
6816
- );
6827
+ const existing = this._replySubs.get(replySubKey);
6828
+ if (!existing || existing.count === 0) {
6829
+ const timer = setTimeout(() => {
6830
+ const entry = this._replySubs.get(replySubKey);
6831
+ if (entry) {
6832
+ this._replySubs.delete(replySubKey);
6833
+ console.warn({
6834
+ event: "dispatch:reply:timeout",
6835
+ threadId,
6836
+ channel: message.replyTarget.adapterChannel
6837
+ }, "Reply subscription timed out \u2014 no reply:ready fired within 1h");
6838
+ }
6839
+ }, 36e5);
6840
+ this._replySubs.set(replySubKey, { count: 1, timer });
6841
+ console.log({
6842
+ event: "dispatch:reply:subscribed",
6843
+ threadId,
6844
+ channel: message.replyTarget.adapterChannel,
6845
+ senderId: message.sender.id
6846
+ }, "Subscribed to reply:ready for thread");
6847
+ agent.subscribeOnce("reply:ready", (data) => {
6848
+ const messages = data.state?.values?.messages;
6849
+ const lastAI = messages?.filter((m) => m.type === "ai" || m.getType?.() === "ai").pop();
6850
+ const replyText = lastAI?.content ?? "";
6851
+ const entry = this._replySubs.get(replySubKey);
6852
+ if (entry) {
6853
+ entry.count--;
6854
+ if (entry.count <= 0) {
6855
+ clearTimeout(entry.timer);
6856
+ this._replySubs.delete(replySubKey);
6857
+ }
6858
+ }
6859
+ if (replyText) {
6860
+ console.log({
6861
+ event: "dispatch:reply:sending",
6862
+ threadId,
6863
+ channel: message.replyTarget.adapterChannel,
6864
+ replyLength: replyText.length
6865
+ }, "Sending channel reply");
6866
+ adapter.sendReply(message.replyTarget, { text: replyText }, installation).then(() => {
6867
+ console.log({
6868
+ event: "dispatch:reply:sent",
6869
+ threadId,
6870
+ channel: message.replyTarget.adapterChannel
6871
+ }, "Channel reply sent successfully");
6872
+ }).catch((err) => console.error({
6873
+ event: "dispatch:reply:failed",
6874
+ threadId,
6875
+ channel: message.replyTarget.adapterChannel,
6876
+ error: err instanceof Error ? err.message : String(err)
6877
+ }, "Failed to send channel reply"));
6878
+ } else {
6879
+ console.warn({
6880
+ event: "dispatch:reply:empty",
6881
+ threadId,
6882
+ channel: message.replyTarget.adapterChannel
6883
+ }, "Agent produced no text output \u2014 skipping reply");
6884
+ }
6885
+ });
6886
+ } else {
6887
+ existing.count++;
6888
+ console.log({
6889
+ event: "dispatch:reply:incremented",
6890
+ threadId,
6891
+ channel: message.replyTarget.adapterChannel,
6892
+ count: existing.count
6893
+ }, "Incremented reply counter for thread (already subscribed)");
6894
+ }
6817
6895
  }
6818
6896
  }
6819
6897
  }
6898
+ const addResult = await agent.addMessage({
6899
+ input: { message: message.content.text },
6900
+ custom_run_config: message.replyTarget ? { ...message.content.metadata || {}, _replyTarget: message.replyTarget } : message.content.metadata || {}
6901
+ });
6902
+ console.log({
6903
+ event: "dispatch:complete",
6904
+ agentId: ctx.binding.agentId,
6905
+ threadId,
6906
+ messageId: addResult?.messageId,
6907
+ result: JSON.stringify(addResult)
6908
+ }, "Agent dispatch complete \u2014 messageId = " + (addResult?.messageId || "N/A"));
6820
6909
  });
6821
6910
  return {
6822
6911
  success: true,