@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/.turbo/turbo-build.log +10 -10
- package/AGENTS.md +62 -0
- package/CHANGELOG.md +10 -0
- package/dist/index.d.mts +62 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.js +105 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +105 -16
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/router/MessageRouter.ts +169 -17
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @axiom-lattice/gateway@2.1.
|
|
2
|
+
> @axiom-lattice/gateway@2.1.89 build /home/runner/work/agentic/agentic/packages/gateway
|
|
3
3
|
> tsup src/index.ts --format cjs,esm --dts --clean --sourcemap
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -18,15 +18,15 @@
|
|
|
18
18
|
You need to set the output format to "esm" for "import.meta" to work correctly.
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
[
|
|
21
|
+
[32mCJS[39m [1mdist/index.js [22m[32m246.20 KB[39m
|
|
22
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m515.19 KB[39m
|
|
23
|
+
[32mCJS[39m ⚡️ Build success in 415ms
|
|
24
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m241.30 KB[39m
|
|
22
25
|
[32mESM[39m [1mdist/sender-PX32VSHB.mjs [22m[32m873.00 B[39m
|
|
23
|
-
[32mESM[39m [1mdist/index.mjs.map [22m[32m504.85 KB[39m
|
|
24
26
|
[32mESM[39m [1mdist/sender-PX32VSHB.mjs.map [22m[32m2.07 KB[39m
|
|
25
|
-
[32mESM[39m
|
|
26
|
-
[
|
|
27
|
-
[32mCJS[39m [1mdist/index.js.map [22m[32m506.42 KB[39m
|
|
28
|
-
[32mCJS[39m ⚡️ Build success in 418ms
|
|
27
|
+
[32mESM[39m [1mdist/index.mjs.map [22m[32m513.63 KB[39m
|
|
28
|
+
[32mESM[39m ⚡️ Build success in 415ms
|
|
29
29
|
[34mDTS[39m Build start
|
|
30
|
-
[32mDTS[39m ⚡️ Build success in
|
|
31
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
32
|
-
[32mDTS[39m [1mdist/index.d.mts [22m[
|
|
30
|
+
[32mDTS[39m ⚡️ Build success in 14563ms
|
|
31
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m7.57 KB[39m
|
|
32
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m7.57 KB[39m
|
package/AGENTS.md
CHANGED
|
@@ -24,6 +24,12 @@ src/
|
|
|
24
24
|
queue_service.ts # Task queue (memory/redis)
|
|
25
25
|
agent_task_consumer.ts # Queue consumer
|
|
26
26
|
sandbox_service.ts # Sandbox proxy
|
|
27
|
+
channels/ # External channel adapters (Lark, Slack, etc.)
|
|
28
|
+
lark/ # Lark (Feishu) adapter
|
|
29
|
+
registry.ts # ChannelAdapterRegistry
|
|
30
|
+
router/ # Message routing
|
|
31
|
+
MessageRouter.ts # Inbound message dispatch + channel reply
|
|
32
|
+
MessageContext.ts # Request-scoped context
|
|
27
33
|
schemas/index.ts # Zod/Fastify schemas
|
|
28
34
|
logger/Logger.ts # Logger lattice integration
|
|
29
35
|
```
|
|
@@ -40,6 +46,62 @@ src/
|
|
|
40
46
|
- **DB configs**: `routes/index.ts`, `controllers/database-configs.ts`
|
|
41
47
|
- **Queue setup**: `services/queue_service.ts`, `services/agent_task_consumer.ts`
|
|
42
48
|
- **Logger lattice**: `logger/Logger.ts`, integration in `index.ts`
|
|
49
|
+
- **Channel message routing**: `router/MessageRouter.ts`, `channels/lark/`
|
|
50
|
+
- **Channel reply mechanism**: `router/MessageRouter.ts#dispatch` (see below)
|
|
51
|
+
|
|
52
|
+
## CHANNEL MESSAGE REPLY FLOW
|
|
53
|
+
|
|
54
|
+
When an external channel (e.g. Lark) sends a message to a bound agent, the gateway
|
|
55
|
+
routes the message and sends the AI response back to the channel. The flow:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
1. Channel webhook → ChannelAdapter.receive(rawPayload)
|
|
59
|
+
→ InboundMessage with replyTarget set
|
|
60
|
+
|
|
61
|
+
2. MessageRouter.dispatch(inboundMessage)
|
|
62
|
+
→ resolve binding → create/reuse thread
|
|
63
|
+
→ subscribe to Agent 'reply:ready' event (before addMessage)
|
|
64
|
+
→ agent.addMessage({ custom_run_config: { _replyTarget: replyTarget } })
|
|
65
|
+
|
|
66
|
+
3. Agent queue processes the message
|
|
67
|
+
→ agentStreamExecutor() runs the agent
|
|
68
|
+
→ emits 'reply:ready' { state, customRunConfig }
|
|
69
|
+
|
|
70
|
+
4. reply:ready callback fires
|
|
71
|
+
→ extracts last AI message from state.values.messages
|
|
72
|
+
→ calls ChannelAdapter.sendReply(replyTarget, { text }, installation)
|
|
73
|
+
→ reply appears in the external channel
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Reply deduplication per thread
|
|
77
|
+
|
|
78
|
+
`MessageRouter` uses a counter-based subscription (`_replySubs: Map<string, { count, timer }>`) to handle
|
|
79
|
+
multiple concurrent dispatches on the same thread:
|
|
80
|
+
|
|
81
|
+
- Each dispatch increments the counter, only the first registers the `subscribeOnce` listener.
|
|
82
|
+
- When `reply:ready` fires, the callback decrements the counter. The subscription is removed
|
|
83
|
+
only when the counter reaches 0.
|
|
84
|
+
- A 1-hour timeout cleans up stale subscriptions (e.g. agent aborted, sequence error).
|
|
85
|
+
|
|
86
|
+
### Reply timing across QueueModes
|
|
87
|
+
|
|
88
|
+
| Mode | `reply:ready` emitted | Reply behavior |
|
|
89
|
+
|------|----------------------|----------------|
|
|
90
|
+
| STEER | After each execution | 1 reply per message |
|
|
91
|
+
| FOLLOWUP | After each execution | 1 reply per message |
|
|
92
|
+
| COLLECT | After the batch (all per-message events first) | 1 reply for the entire batch |
|
|
93
|
+
|
|
94
|
+
### Key logs for debugging
|
|
95
|
+
|
|
96
|
+
| Event | Meaning |
|
|
97
|
+
|-------|---------|
|
|
98
|
+
| `dispatch:reply:subscribed` | First reply subscription for this thread |
|
|
99
|
+
| `dispatch:reply:incremented` | Additional dispatch joined existing subscription |
|
|
100
|
+
| `dispatch:reply:sending` | AI text extracted, about to call sendReply |
|
|
101
|
+
| `dispatch:reply:sent` | Channel API responded OK |
|
|
102
|
+
| `dispatch:reply:failed` | Channel API returned an error |
|
|
103
|
+
| `dispatch:reply:empty` | Agent produced no output, skipping reply |
|
|
104
|
+
| `dispatch:reply:timeout` | No reply:ready fired within 1h, subscription cleaned up |
|
|
43
105
|
|
|
44
106
|
## CONVENTIONS
|
|
45
107
|
- Controllers export named handlers, consume from `services/`
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @axiom-lattice/gateway
|
|
2
2
|
|
|
3
|
+
## 2.1.89
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 176bfe8: custom middleware, enhance messagerouter
|
|
8
|
+
- Updated dependencies [176bfe8]
|
|
9
|
+
- @axiom-lattice/core@2.1.77
|
|
10
|
+
- @axiom-lattice/agent-eval@2.1.71
|
|
11
|
+
- @axiom-lattice/pg-stores@1.0.68
|
|
12
|
+
|
|
3
13
|
## 2.1.88
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
package/dist/index.d.mts
CHANGED
|
@@ -62,19 +62,81 @@ interface MessageContext {
|
|
|
62
62
|
}
|
|
63
63
|
type MessageMiddleware = (ctx: MessageContext, next: () => Promise<void>) => Promise<void>;
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Configuration for {@link MessageRouter}.
|
|
67
|
+
*
|
|
68
|
+
* @param middlewares - Ordered middleware chain executed before dispatch
|
|
69
|
+
* @param bindingRegistry - Resolves sender-to-agent bindings
|
|
70
|
+
* @param adapterRegistry - Registry of {@link ChannelAdapter} implementations
|
|
71
|
+
* @param installationStore - Persisted channel installation configs
|
|
72
|
+
*/
|
|
65
73
|
interface MessageRouterConfig {
|
|
66
74
|
middlewares: MessageMiddleware[];
|
|
67
75
|
bindingRegistry: BindingRegistry;
|
|
68
76
|
adapterRegistry: ChannelAdapterRegistry;
|
|
69
77
|
installationStore: ChannelInstallationStore;
|
|
70
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Core message router for external channel integration.
|
|
81
|
+
*
|
|
82
|
+
* Receives normalized {@link InboundMessage} objects from channel adapters,
|
|
83
|
+
* resolves the target agent via {@link BindingRegistry}, manages thread
|
|
84
|
+
* lifecycle, and sends AI responses back to the channel via
|
|
85
|
+
* `ChannelAdapter.sendReply`.
|
|
86
|
+
*
|
|
87
|
+
* ## Reply flow
|
|
88
|
+
*
|
|
89
|
+
* The router uses a counter-based deduplication scheme on the internal
|
|
90
|
+
* {@link Agent.reply:ready} event:
|
|
91
|
+
*
|
|
92
|
+
* 1. Before `agent.addMessage()`, it subscribes to `reply:ready` on the agent.
|
|
93
|
+
* 2. Multiple concurrent dispatches on the same thread increment a counter;
|
|
94
|
+
* only the first registers the `EventEmitter.once` listener.
|
|
95
|
+
* 3. When `reply:ready` fires, the callback extracts the last AI message from
|
|
96
|
+
* the LangGraph state and sends it via `ChannelAdapter.sendReply`.
|
|
97
|
+
* 4. The counter is decremented; the subscription is only removed when the
|
|
98
|
+
* counter reaches 0.
|
|
99
|
+
* 5. Stale subscriptions are cleaned up after a 1-hour timeout.
|
|
100
|
+
*
|
|
101
|
+
* The `replyTarget` is carried through by injecting `_replyTarget` into
|
|
102
|
+
* `custom_run_config` on {@link Agent.addMessage}, and retrieved from the
|
|
103
|
+
* `reply:ready` event payload.
|
|
104
|
+
*
|
|
105
|
+
* @see {@link ChannelAdapter.sendReply}
|
|
106
|
+
* @see {@link InboundMessage.replyTarget}
|
|
107
|
+
*/
|
|
71
108
|
declare class MessageRouter {
|
|
72
109
|
private middlewares;
|
|
73
110
|
private bindingRegistry;
|
|
74
111
|
private adapterRegistry;
|
|
75
112
|
private installationStore;
|
|
113
|
+
/**
|
|
114
|
+
* Tracks reply subscriptions per thread+channel to avoid duplicate
|
|
115
|
+
* `subscribeOnce` registrations and ensure proper cleanup.
|
|
116
|
+
*
|
|
117
|
+
* Key format: `{threadId}:{adapterChannel}:reply`
|
|
118
|
+
*/
|
|
119
|
+
private _replySubs;
|
|
76
120
|
constructor(config: MessageRouterConfig);
|
|
121
|
+
/**
|
|
122
|
+
* Register an additional middleware at the end of the chain.
|
|
123
|
+
*
|
|
124
|
+
* @param middleware - A {@link MessageMiddleware} function
|
|
125
|
+
*/
|
|
77
126
|
use(middleware: MessageMiddleware): void;
|
|
127
|
+
/**
|
|
128
|
+
* Dispatch an inbound channel message to the bound agent.
|
|
129
|
+
*
|
|
130
|
+
* Full pipeline: middleware chain → binding resolution → thread lifecycle
|
|
131
|
+
* → agent.addMessage() → (async) reply via {@link ChannelAdapter.sendReply}.
|
|
132
|
+
*
|
|
133
|
+
* If the message has a {@link InboundMessage.replyTarget}, the router subscribes
|
|
134
|
+
* to the agent's `reply:ready` event before enqueuing the message, and sends
|
|
135
|
+
* the AI response back to the channel when it arrives.
|
|
136
|
+
*
|
|
137
|
+
* @param message - Normalized inbound message from a channel adapter
|
|
138
|
+
* @returns {@link DispatchResult} with success status, bindingId, and threadId
|
|
139
|
+
*/
|
|
78
140
|
dispatch(message: InboundMessage): Promise<DispatchResult>;
|
|
79
141
|
private runMiddlewares;
|
|
80
142
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -62,19 +62,81 @@ interface MessageContext {
|
|
|
62
62
|
}
|
|
63
63
|
type MessageMiddleware = (ctx: MessageContext, next: () => Promise<void>) => Promise<void>;
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Configuration for {@link MessageRouter}.
|
|
67
|
+
*
|
|
68
|
+
* @param middlewares - Ordered middleware chain executed before dispatch
|
|
69
|
+
* @param bindingRegistry - Resolves sender-to-agent bindings
|
|
70
|
+
* @param adapterRegistry - Registry of {@link ChannelAdapter} implementations
|
|
71
|
+
* @param installationStore - Persisted channel installation configs
|
|
72
|
+
*/
|
|
65
73
|
interface MessageRouterConfig {
|
|
66
74
|
middlewares: MessageMiddleware[];
|
|
67
75
|
bindingRegistry: BindingRegistry;
|
|
68
76
|
adapterRegistry: ChannelAdapterRegistry;
|
|
69
77
|
installationStore: ChannelInstallationStore;
|
|
70
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Core message router for external channel integration.
|
|
81
|
+
*
|
|
82
|
+
* Receives normalized {@link InboundMessage} objects from channel adapters,
|
|
83
|
+
* resolves the target agent via {@link BindingRegistry}, manages thread
|
|
84
|
+
* lifecycle, and sends AI responses back to the channel via
|
|
85
|
+
* `ChannelAdapter.sendReply`.
|
|
86
|
+
*
|
|
87
|
+
* ## Reply flow
|
|
88
|
+
*
|
|
89
|
+
* The router uses a counter-based deduplication scheme on the internal
|
|
90
|
+
* {@link Agent.reply:ready} event:
|
|
91
|
+
*
|
|
92
|
+
* 1. Before `agent.addMessage()`, it subscribes to `reply:ready` on the agent.
|
|
93
|
+
* 2. Multiple concurrent dispatches on the same thread increment a counter;
|
|
94
|
+
* only the first registers the `EventEmitter.once` listener.
|
|
95
|
+
* 3. When `reply:ready` fires, the callback extracts the last AI message from
|
|
96
|
+
* the LangGraph state and sends it via `ChannelAdapter.sendReply`.
|
|
97
|
+
* 4. The counter is decremented; the subscription is only removed when the
|
|
98
|
+
* counter reaches 0.
|
|
99
|
+
* 5. Stale subscriptions are cleaned up after a 1-hour timeout.
|
|
100
|
+
*
|
|
101
|
+
* The `replyTarget` is carried through by injecting `_replyTarget` into
|
|
102
|
+
* `custom_run_config` on {@link Agent.addMessage}, and retrieved from the
|
|
103
|
+
* `reply:ready` event payload.
|
|
104
|
+
*
|
|
105
|
+
* @see {@link ChannelAdapter.sendReply}
|
|
106
|
+
* @see {@link InboundMessage.replyTarget}
|
|
107
|
+
*/
|
|
71
108
|
declare class MessageRouter {
|
|
72
109
|
private middlewares;
|
|
73
110
|
private bindingRegistry;
|
|
74
111
|
private adapterRegistry;
|
|
75
112
|
private installationStore;
|
|
113
|
+
/**
|
|
114
|
+
* Tracks reply subscriptions per thread+channel to avoid duplicate
|
|
115
|
+
* `subscribeOnce` registrations and ensure proper cleanup.
|
|
116
|
+
*
|
|
117
|
+
* Key format: `{threadId}:{adapterChannel}:reply`
|
|
118
|
+
*/
|
|
119
|
+
private _replySubs;
|
|
76
120
|
constructor(config: MessageRouterConfig);
|
|
121
|
+
/**
|
|
122
|
+
* Register an additional middleware at the end of the chain.
|
|
123
|
+
*
|
|
124
|
+
* @param middleware - A {@link MessageMiddleware} function
|
|
125
|
+
*/
|
|
77
126
|
use(middleware: MessageMiddleware): void;
|
|
127
|
+
/**
|
|
128
|
+
* Dispatch an inbound channel message to the bound agent.
|
|
129
|
+
*
|
|
130
|
+
* Full pipeline: middleware chain → binding resolution → thread lifecycle
|
|
131
|
+
* → agent.addMessage() → (async) reply via {@link ChannelAdapter.sendReply}.
|
|
132
|
+
*
|
|
133
|
+
* If the message has a {@link InboundMessage.replyTarget}, the router subscribes
|
|
134
|
+
* to the agent's `reply:ready` event before enqueuing the message, and sends
|
|
135
|
+
* the AI response back to the channel when it arrives.
|
|
136
|
+
*
|
|
137
|
+
* @param message - Normalized inbound message from a channel adapter
|
|
138
|
+
* @returns {@link DispatchResult} with success status, bindingId, and threadId
|
|
139
|
+
*/
|
|
78
140
|
dispatch(message: InboundMessage): Promise<DispatchResult>;
|
|
79
141
|
private runMiddlewares;
|
|
80
142
|
}
|
package/dist/index.js
CHANGED
|
@@ -6689,14 +6689,39 @@ var BindingNotFoundError = class extends Error {
|
|
|
6689
6689
|
};
|
|
6690
6690
|
var MessageRouter = class {
|
|
6691
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();
|
|
6692
6699
|
this.middlewares = [...config.middlewares];
|
|
6693
6700
|
this.bindingRegistry = config.bindingRegistry;
|
|
6694
6701
|
this.adapterRegistry = config.adapterRegistry;
|
|
6695
6702
|
this.installationStore = config.installationStore;
|
|
6696
6703
|
}
|
|
6704
|
+
/**
|
|
6705
|
+
* Register an additional middleware at the end of the chain.
|
|
6706
|
+
*
|
|
6707
|
+
* @param middleware - A {@link MessageMiddleware} function
|
|
6708
|
+
*/
|
|
6697
6709
|
use(middleware) {
|
|
6698
6710
|
this.middlewares.push(middleware);
|
|
6699
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
|
+
*/
|
|
6700
6725
|
async dispatch(message) {
|
|
6701
6726
|
const ctx = {
|
|
6702
6727
|
inboundMessage: message,
|
|
@@ -6843,32 +6868,96 @@ var MessageRouter = class {
|
|
|
6843
6868
|
workspace_id: ctx.binding.workspaceId || "",
|
|
6844
6869
|
project_id: ctx.binding.projectId || ""
|
|
6845
6870
|
});
|
|
6846
|
-
const addResult = await agent.addMessage({
|
|
6847
|
-
input: { message: message.content.text },
|
|
6848
|
-
custom_run_config: message.content.metadata || {}
|
|
6849
|
-
});
|
|
6850
|
-
console.log({
|
|
6851
|
-
event: "dispatch:complete",
|
|
6852
|
-
agentId: ctx.binding.agentId,
|
|
6853
|
-
threadId,
|
|
6854
|
-
messageId: addResult?.messageId,
|
|
6855
|
-
result: JSON.stringify(addResult)
|
|
6856
|
-
}, "Agent dispatch complete \u2014 messageId = " + (addResult?.messageId || "N/A"));
|
|
6857
6871
|
if (message.replyTarget) {
|
|
6872
|
+
const replySubKey = `${threadId}:${message.replyTarget.adapterChannel}:reply`;
|
|
6858
6873
|
const adapter = this.adapterRegistry.get(message.replyTarget.adapterChannel);
|
|
6859
6874
|
if (adapter) {
|
|
6860
6875
|
const installation = await this.installationStore.getInstallationById(
|
|
6861
6876
|
message.channelInstallationId
|
|
6862
6877
|
);
|
|
6863
6878
|
if (installation) {
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
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
|
+
}
|
|
6869
6947
|
}
|
|
6870
6948
|
}
|
|
6871
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"));
|
|
6872
6961
|
});
|
|
6873
6962
|
return {
|
|
6874
6963
|
success: true,
|