@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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @axiom-lattice/gateway@2.1.87 build /home/runner/work/agentic/agentic/packages/gateway
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
  CLI Building entry: src/index.ts
@@ -11,22 +11,22 @@
11
11
  ESM Build start
12
12
  [warn] ▲ [WARNING] "import.meta" is not available with the "cjs" output format and will be empty [empty-import-meta]
13
13
 
14
- src/index.ts:178:33:
15
-  178 │ const __filename = fileURLToPath(import.meta.url);
14
+ src/index.ts:175:33:
15
+  175 │ const __filename = fileURLToPath(import.meta.url);
16
16
  ╵ ~~~~~~~~~~~
17
17
 
18
18
  You need to set the output format to "esm" for "import.meta" to work correctly.
19
19
 
20
20
 
21
- CJS dist/index.js 242.46 KB
22
- CJS dist/index.js.map 507.89 KB
23
- CJS ⚡️ Build success in 387ms
24
- ESM dist/index.mjs 237.76 KB
21
+ CJS dist/index.js 246.20 KB
22
+ CJS dist/index.js.map 515.19 KB
23
+ CJS ⚡️ Build success in 415ms
24
+ ESM dist/index.mjs 241.30 KB
25
25
  ESM dist/sender-PX32VSHB.mjs 873.00 B
26
26
  ESM dist/sender-PX32VSHB.mjs.map 2.07 KB
27
- ESM dist/index.mjs.map 506.38 KB
28
- ESM ⚡️ Build success in 391ms
27
+ ESM dist/index.mjs.map 513.63 KB
28
+ ESM ⚡️ Build success in 415ms
29
29
  DTS Build start
30
- DTS ⚡️ Build success in 13983ms
31
- DTS dist/index.d.ts 5.01 KB
32
- DTS dist/index.d.mts 5.01 KB
30
+ DTS ⚡️ Build success in 14563ms
31
+ DTS dist/index.d.ts 7.57 KB
32
+ DTS dist/index.d.mts 7.57 KB
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,27 @@
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
+
13
+ ## 2.1.88
14
+
15
+ ### Patch Changes
16
+
17
+ - 6ff5bd5: fix issue
18
+ - Updated dependencies [6ff5bd5]
19
+ - @axiom-lattice/agent-eval@2.1.70
20
+ - @axiom-lattice/core@2.1.76
21
+ - @axiom-lattice/pg-stores@1.0.67
22
+ - @axiom-lattice/protocols@2.1.39
23
+ - @axiom-lattice/queue-redis@1.0.38
24
+
3
25
  ## 2.1.87
4
26
 
5
27
  ### Patch Changes
package/README.md CHANGED
@@ -1,125 +1,68 @@
1
- # Lattice Gateway
1
+ # @axiom-lattice/gateway
2
2
 
3
- 这是 Lattice 项目的 API 网关服务,提供了基于 Fastify HTTP API 接口,用于与 Lattice Core 交互。
3
+ Fastify-based HTTP API gateway exposing REST endpoints for agent execution, thread management, and configuration.
4
4
 
5
- ## 功能特点
5
+ ## Store Consumption
6
6
 
7
- - 基于 Fastify HTTP API
8
- - 代理调用服务
9
- - 流式响应支持
10
- - 事件总线
11
- - 队列服务(基于 QueueLattice)
12
- - 日志服务(基于 LoggerLattice,支持自定义配置)
7
+ The gateway reads all stores from `StoreLatticeManager` at startup — no manual wiring needed.
8
+ Register stores via `configureStores` **before** calling `startAsHttpEndpoint()`:
13
9
 
14
- ## 安装
15
-
16
- ```bash
17
- pnpm install
18
- ```
19
-
20
- ## 启动服务
21
-
22
- ```bash
23
- # 开发环境
24
- pnpm dev
25
-
26
- # 生产环境
27
- pnpm build
28
- pnpm start
29
- ```
30
-
31
- ## API 接口
32
-
33
- ### Configuration API
34
-
35
- The gateway supports dynamic configuration updates via JSON. You can update environment variables at runtime without restarting the server.
10
+ ```typescript
11
+ import { configureStores } from "@axiom-lattice/core";
12
+ import { createPgStoreConfig } from "@axiom-lattice/pg-stores";
13
+ import { LatticeGateway } from "@axiom-lattice/gateway";
36
14
 
37
- #### Get Configuration
15
+ const stores = createPgStoreConfig(process.env.DATABASE_URL);
16
+ await configureStores({ ...stores });
38
17
 
39
- ```bash
40
- GET /api/config
18
+ // Gateway picks up stores from StoreLatticeManager automatically
19
+ LatticeGateway.startAsHttpEndpoint({ port: 4001 });
41
20
  ```
42
21
 
43
- Returns the current configuration (sensitive values are masked).
22
+ ### How it works
44
23
 
45
- #### Update Configuration
24
+ The gateway internally calls `getStoreLattice("default", type)` for each store it needs:
46
25
 
47
- ```bash
48
- PUT /api/config
49
- Content-Type: application/json
26
+ - **`channelBinding`** — used by `MessageRouter` for sender-to-agent binding resolution
27
+ - **`channelInstallation`** — used for channel instance lookup
28
+ - **`database`** — `SqlDatabaseManager` reads configs lazily on first query
29
+ - **`metrics`** — `MetricsServerManager` loads configs on first access
30
+ - **All other stores** — consumed by controllers via `getStoreLattice("default", type)`
50
31
 
51
- {
52
- "config": {
53
- "port": 4001,
54
- "queueServiceType": "redis",
55
- "redisUrl": "redis://localhost:6379",
56
- "redisPassword": "your-password",
57
- "queueName": "tasks"
58
- }
59
- }
60
- ```
61
-
62
- **Note**: When updating `queueServiceType`, the queue service will be automatically reconfigured.
32
+ No `setConfigStore()` or `loadConfigsFromStore()` calls needed — each manager reads directly from `StoreLatticeManager`.
63
33
 
64
- **Example**:
34
+ ## Quick Start
65
35
 
66
36
  ```typescript
67
- // Update configuration from frontend
68
- const response = await fetch("http://localhost:4001/api/config", {
69
- method: "PUT",
70
- headers: {
71
- "Content-Type": "application/json",
72
- },
73
- body: JSON.stringify({
74
- config: {
75
- queueServiceType: "redis",
76
- redisUrl: "redis://localhost:6379",
77
- redisPassword: "your-password",
78
- },
79
- }),
80
- });
37
+ import { LatticeGateway } from "@axiom-lattice/gateway";
81
38
 
82
- const result = await response.json();
83
- console.log(result); // { success: true, message: "Configuration updated successfully", data: {...} }
39
+ LatticeGateway.startAsHttpEndpoint({
40
+ port: 4001,
41
+ queueServiceConfig: { type: "memory", defaultStartPollingQueue: true },
42
+ });
84
43
  ```
85
44
 
86
- ### 代理调用
87
-
88
- - `POST /api/v1/run`: 运行代理
89
- - `POST /api/v1/run/stream`: 流式运行代理
90
- - `GET /api/v1/run/:thread_id`: 获取运行状态
91
- - `GET /api/v1/run/:thread_id/messages`: 获取消息历史
92
-
93
- ## Logger 配置
94
-
95
- Gateway 使用 Logger Lattice 进行日志管理,支持自定义日志配置。详细说明请参考 [LOGGER_CONFIG.md](./LOGGER_CONFIG.md)。
96
-
97
- ### 快速开始
45
+ ## Logger Configuration
98
46
 
99
47
  ```typescript
100
48
  import { LatticeGateway } from "@axiom-lattice/gateway";
101
49
  import { LoggerType } from "@axiom-lattice/protocols";
102
50
 
103
- // 使用默认配置
104
- LatticeGateway.startAsHttpEndpoint({
105
- port: 4001,
106
- });
107
-
108
- // 自定义日志配置
109
51
  LatticeGateway.startAsHttpEndpoint({
110
52
  port: 4001,
111
53
  loggerConfig: {
112
- file: "./logs/my-app/gateway",
54
+ name: "default",
55
+ type: LoggerType.PINO,
113
56
  serviceName: "my-service",
114
57
  },
115
58
  });
116
59
  ```
117
60
 
118
- ## 目录结构
61
+ ## API Endpoints
119
62
 
120
- - `src/config/`: 配置文件
121
- - `src/controllers/`: 控制器
122
- - `src/routes/`: 路由定义
123
- - `src/services/`: 服务实现
124
- - `src/types/`: 类型定义
125
- - `LOGGER_CONFIG.md`: Logger 配置详细文档
63
+ - `POST /api/v1/run` — Execute agent
64
+ - `POST /api/v1/run/stream` — Stream agent execution
65
+ - `GET /api/v1/run/:thread_id` — Get run status
66
+ - `GET /api/v1/run/:thread_id/messages` — Get message history
67
+ - `GET /api/config` — Get gateway configuration
68
+ - `PUT /api/config` — Update gateway configuration
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
  }