@lmcl/ailo-mcp-sdk 0.0.4 → 0.2.0
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/README.md +45 -94
- package/dist/ailo-client.d.ts +35 -21
- package/dist/ailo-client.d.ts.map +1 -1
- package/dist/ailo-client.js +185 -99
- package/dist/bootstrap.d.ts +5 -8
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +39 -47
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/types.d.ts +34 -12
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,65 +40,36 @@ server.registerTool(
|
|
|
40
40
|
runMcp(server);
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
###
|
|
43
|
+
### 场景二:双向通道(接收平台消息 + 工具发消息)
|
|
44
44
|
|
|
45
45
|
```typescript
|
|
46
|
-
import type { BridgeHandler,
|
|
46
|
+
import type { BridgeHandler, ChannelContext } from "@lmcl/ailo-mcp-sdk";
|
|
47
47
|
import { runMcpChannel } from "@lmcl/ailo-mcp-sdk";
|
|
48
48
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
49
49
|
|
|
50
50
|
class MyHandler implements BridgeHandler {
|
|
51
|
-
private
|
|
51
|
+
private ctx!: ChannelContext;
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
this.
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async start() {
|
|
53
|
+
async start(ctx: ChannelContext) {
|
|
54
|
+
this.ctx = ctx;
|
|
58
55
|
// 启动平台连接(WebSocket / long polling 等)
|
|
59
|
-
// 收到消息时:
|
|
56
|
+
// 收到消息时:await ctx.accept({ text, contextTags, attachments? })
|
|
57
|
+
// 读写持久化数据:await ctx.storage.getData("key") / setData / deleteData
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
stop
|
|
60
|
+
async stop() {
|
|
63
61
|
// 断开平台连接
|
|
64
62
|
}
|
|
65
63
|
}
|
|
66
64
|
|
|
67
65
|
const handler = new MyHandler();
|
|
68
66
|
const server = new McpServer({ name: "channel:my-platform", version: "1.0.0" });
|
|
67
|
+
// 注册出站工具...
|
|
69
68
|
|
|
70
69
|
runMcpChannel({
|
|
71
70
|
handler,
|
|
72
71
|
mcpServer: server,
|
|
73
|
-
|
|
74
|
-
});
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
### 场景三:双向收发(既有推送,也有工具)
|
|
78
|
-
|
|
79
|
-
在场景二基础上,给 MCP Server 注册发消息工具即可:
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
83
|
-
import { z } from "zod";
|
|
84
|
-
|
|
85
|
-
const server = new McpServer({ name: "channel:my-platform", version: "1.0.0" });
|
|
86
|
-
|
|
87
|
-
server.registerTool(
|
|
88
|
-
"send_message",
|
|
89
|
-
{
|
|
90
|
-
description: "发送消息到平台",
|
|
91
|
-
inputSchema: { chat_id: z.string(), text: z.string() },
|
|
92
|
-
},
|
|
93
|
-
async ({ chat_id, text }) => {
|
|
94
|
-
// 调用平台 API 或 handler 发消息
|
|
95
|
-
return { content: [{ type: "text", text: "已发送" }] };
|
|
96
|
-
}
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
runMcpChannel({
|
|
100
|
-
handler: new MyHandler(),
|
|
101
|
-
mcpServer: server,
|
|
72
|
+
displayName: "我的平台",
|
|
102
73
|
buildChannelPrompt: () => "本通道规则说明",
|
|
103
74
|
});
|
|
104
75
|
```
|
|
@@ -109,64 +80,36 @@ runMcpChannel({
|
|
|
109
80
|
|
|
110
81
|
MCP 使用 **stdio** 传输,**stdout 只能输出 JSON-RPC 消息**。任何 `console.log`、第三方库日志写入 stdout 都会破坏协议,导致解析错误。
|
|
111
82
|
|
|
112
|
-
**本 SDK 已内置保护**:自动拦截 stdout,仅 JSON-RPC 转发到 stdout,其余重定向到 stderr。你可以自由使用 `console.log`、`console.info`、`console.debug
|
|
113
|
-
|
|
114
|
-
**入口顺序**:请将 `import { runMcpChannel } from "@lmcl/ailo-mcp-sdk"` 作为入口文件的**首个 import**,确保保护在 dotenv、平台 SDK 等之前生效。若入口结构复杂,可显式第一行写:
|
|
83
|
+
**本 SDK 已内置保护**:自动拦截 stdout,仅 JSON-RPC 转发到 stdout,其余重定向到 stderr。你可以自由使用 `console.log`、`console.info`、`console.debug`,以及会打日志的第三方库,无需额外处理。
|
|
115
84
|
|
|
116
|
-
|
|
117
|
-
import "@lmcl/ailo-mcp-sdk/stdio-guard";
|
|
118
|
-
import "dotenv/config";
|
|
119
|
-
import { runMcpChannel } from "@lmcl/ailo-mcp-sdk";
|
|
120
|
-
```
|
|
85
|
+
**入口顺序**:请将 `import { runMcpChannel } from "@lmcl/ailo-mcp-sdk"` 作为入口文件的**首个 import**,确保保护在 dotenv、平台 SDK 等之前生效。
|
|
121
86
|
|
|
122
87
|
---
|
|
123
88
|
|
|
124
89
|
## BridgeHandler 接口
|
|
125
90
|
|
|
126
|
-
通道需实现 `BridgeHandler
|
|
91
|
+
通道需实现 `BridgeHandler`,SDK 保证 `start(ctx)` 调用时 `ctx` 已就绪:
|
|
92
|
+
|
|
93
|
+
| 方法 | 说明 |
|
|
94
|
+
|------|------|
|
|
95
|
+
| `start(ctx)` | 启动平台连接。`ctx.accept(msg)` 投递消息到 AILO,`ctx.storage` 读写持久化数据 |
|
|
96
|
+
| `stop()` | 优雅停止。SDK 在 SIGINT/SIGTERM 时调用 |
|
|
97
|
+
|
|
98
|
+
### ChannelContext
|
|
127
99
|
|
|
128
|
-
|
|
|
100
|
+
| 字段 | 类型 | 说明 |
|
|
129
101
|
|------|------|------|
|
|
130
|
-
| `
|
|
131
|
-
| `
|
|
132
|
-
| `stop?()` | 否 | 断开平台连接,优雅退出时调用 |
|
|
133
|
-
| `setDataProvider?(storage)` | 否 | SDK 连接后注入持久化存储,用于通道数据(如外部用户映射) |
|
|
102
|
+
| `accept(msg)` | `(msg: BridgeMessage) => Promise<void>` | 投递入站消息到 AILO。SDK 内部做空消息过滤 |
|
|
103
|
+
| `storage` | `ChannelStorage` | 通道级 KV 存储(`getData`/`setData`/`deleteData`),数据持久化在 AILO 侧 |
|
|
134
104
|
|
|
135
105
|
### BridgeMessage(时空场模型)
|
|
136
106
|
|
|
137
|
-
通道自己定义,全在 contextTags 里:
|
|
138
|
-
|
|
139
107
|
| 字段 | 类型 | 说明 |
|
|
140
108
|
|------|------|------|
|
|
141
|
-
| `text` | string | 消息正文 |
|
|
109
|
+
| `text` | string? | 消息正文 |
|
|
142
110
|
| `contextTags` | ContextTag[] | 时空场标签,必须 |
|
|
143
111
|
| `attachments` | Attachment[]? | 附件 |
|
|
144
|
-
|
|
145
|
-
---
|
|
146
|
-
|
|
147
|
-
## 通道持久化数据(可选)
|
|
148
|
-
|
|
149
|
-
通道运行时产生的持久化数据(如外部用户映射)可存于 AILO 本体。实现 `setDataProvider` 后,SDK 会注入带 `getData` / `setData` / `deleteData` 的对象:
|
|
150
|
-
|
|
151
|
-
```typescript
|
|
152
|
-
class MyHandler implements BridgeHandler {
|
|
153
|
-
private storage: {
|
|
154
|
-
getData(key: string): Promise<string | null>;
|
|
155
|
-
setData(key: string, value: string): Promise<void>;
|
|
156
|
-
deleteData(key: string): Promise<void>;
|
|
157
|
-
} | null = null;
|
|
158
|
-
|
|
159
|
-
setDataProvider(storage: typeof this.storage) {
|
|
160
|
-
this.storage = storage;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
async someMethod() {
|
|
164
|
-
const v = await this.storage?.getData("key");
|
|
165
|
-
await this.storage?.setData("key", "value");
|
|
166
|
-
await this.storage?.deleteData("key");
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
```
|
|
112
|
+
| `requiresResponse` | boolean? | 覆盖通道级 defaultRequiresResponse |
|
|
170
113
|
|
|
171
114
|
---
|
|
172
115
|
|
|
@@ -176,10 +119,10 @@ class MyHandler implements BridgeHandler {
|
|
|
176
119
|
|------|------|------|
|
|
177
120
|
| `handler` | 是 | BridgeHandler 实例 |
|
|
178
121
|
| `mcpServer` | 是 | MCP Server 实例(需注册工具,AILO 才能调用) |
|
|
122
|
+
| `displayName` | 是 | 中文通道显示名(如 "飞书") |
|
|
179
123
|
| `buildChannelPrompt` | 否 | 通道规则提示词,connect 时注册 |
|
|
180
|
-
| `
|
|
181
|
-
| `
|
|
182
|
-
| `ailoToken` | 否 | 认证 Token,不传则从 `AILO_TOKEN` 读取 |
|
|
124
|
+
| `defaultRequiresResponse` | 否 | 默认 true(主动信号) |
|
|
125
|
+
| `channelName` | 否 | 不传则从 `AILO_MCP_NAME` 读取 |
|
|
183
126
|
|
|
184
127
|
---
|
|
185
128
|
|
|
@@ -189,21 +132,15 @@ class MyHandler implements BridgeHandler {
|
|
|
189
132
|
|
|
190
133
|
| 变量 | 说明 |
|
|
191
134
|
|------|------|
|
|
192
|
-
| `AILO_WS_URL` | AILO WebSocket
|
|
135
|
+
| `AILO_WS_URL` | AILO WebSocket 网关地址 |
|
|
193
136
|
| `AILO_TOKEN` | 认证 Token |
|
|
194
|
-
| `AILO_MCP_NAME` | MCP
|
|
195
|
-
| `AILO_MCP_WORKDIR` | MCP
|
|
137
|
+
| `AILO_MCP_NAME` | MCP 名(即通道名) |
|
|
138
|
+
| `AILO_MCP_WORKDIR` | MCP 专属工作目录(绝对路径) |
|
|
196
139
|
|
|
197
140
|
**获取工作目录**:`import { getWorkDir } from "@lmcl/ailo-mcp-sdk"`,`getWorkDir()` 返回 `AILO_MCP_WORKDIR` 或 `null`。
|
|
198
141
|
|
|
199
142
|
---
|
|
200
143
|
|
|
201
|
-
## 时空场(ContextTags)
|
|
202
|
-
|
|
203
|
-
每条消息必须带 contextTags,通道自己定义。格式:`{ desc, value, core }[]`。`desc` 给 LLM 看,`value` 为标签值,`core` 表示是否参与时空场键。
|
|
204
|
-
|
|
205
|
-
---
|
|
206
|
-
|
|
207
144
|
## 类型与导出
|
|
208
145
|
|
|
209
146
|
```typescript
|
|
@@ -211,19 +148,33 @@ import {
|
|
|
211
148
|
runMcp,
|
|
212
149
|
runMcpChannel,
|
|
213
150
|
getWorkDir,
|
|
151
|
+
AiloClient,
|
|
152
|
+
tagValue,
|
|
214
153
|
defaultBuildChannelPrompt,
|
|
215
154
|
} from "@lmcl/ailo-mcp-sdk";
|
|
216
155
|
import type {
|
|
217
156
|
BridgeHandler,
|
|
218
157
|
BridgeMessage,
|
|
158
|
+
ChannelContext,
|
|
159
|
+
ChannelStorage,
|
|
219
160
|
Attachment,
|
|
220
161
|
ContextTag,
|
|
221
162
|
McpChannelConfig,
|
|
163
|
+
AiloClientConfig,
|
|
222
164
|
} from "@lmcl/ailo-mcp-sdk";
|
|
223
165
|
```
|
|
224
166
|
|
|
225
167
|
---
|
|
226
168
|
|
|
169
|
+
## AiloClient 特性
|
|
170
|
+
|
|
171
|
+
- 指数退避自动重连(1s → 2s → 4s → ... → 60s max)
|
|
172
|
+
- 请求超时(30s,防止 Promise 泄漏)
|
|
173
|
+
- WebSocket 心跳(ping/pong,30s 间隔,10s 超时检测半死连接)
|
|
174
|
+
- 断线时自动清理所有 pending 请求
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
227
178
|
## 依赖
|
|
228
179
|
|
|
229
180
|
- Node.js >= 18
|
package/dist/ailo-client.d.ts
CHANGED
|
@@ -1,35 +1,49 @@
|
|
|
1
|
-
import type { BridgeMessage, ContextTag } from "./types.js";
|
|
1
|
+
import type { BridgeMessage, ChannelStorage, ContextTag } from "./types.js";
|
|
2
2
|
/** 按 kind 查找第一个匹配标签的 value */
|
|
3
3
|
export declare function tagValue(tags: ContextTag[], kind: string): string;
|
|
4
|
+
export interface AiloClientConfig {
|
|
5
|
+
url: string;
|
|
6
|
+
token: string;
|
|
7
|
+
channel: string;
|
|
8
|
+
displayName: string;
|
|
9
|
+
defaultRequiresResponse?: boolean;
|
|
10
|
+
channelPrompt?: string;
|
|
11
|
+
}
|
|
4
12
|
/**
|
|
5
|
-
* 反向 WebSocket
|
|
6
|
-
*
|
|
7
|
-
* 连接 Ailo 网关,connect 时传入 channel、displayName、defaultRequiresResponse,一步完成注册。
|
|
8
|
-
* 负责 channel.accept(入站信号投递)。
|
|
13
|
+
* 反向 WebSocket 客户端。
|
|
9
14
|
*
|
|
10
|
-
*
|
|
15
|
+
* 连接 Ailo 网关,自动重连(指数退避)、心跳(ping/pong)、请求超时。
|
|
16
|
+
* 实现 ChannelStorage 接口,可直接作为 ctx.storage 使用。
|
|
11
17
|
*/
|
|
12
|
-
export declare class AiloClient {
|
|
18
|
+
export declare class AiloClient implements ChannelStorage {
|
|
13
19
|
private ws;
|
|
14
|
-
private
|
|
15
|
-
private token;
|
|
16
|
-
private channel;
|
|
17
|
-
private displayName;
|
|
18
|
-
private defaultRequiresResponse;
|
|
19
|
-
private channelPrompt;
|
|
20
|
-
private reconnectTimer;
|
|
20
|
+
private cfg;
|
|
21
21
|
private reqId;
|
|
22
|
-
|
|
23
|
-
private
|
|
22
|
+
private pending;
|
|
23
|
+
private heartbeatTimer;
|
|
24
|
+
private pongTimer;
|
|
25
|
+
private reconnectTimer;
|
|
26
|
+
private reconnectAttempt;
|
|
27
|
+
private intentionalClose;
|
|
28
|
+
constructor(config: AiloClientConfig);
|
|
29
|
+
/** 建立连接并完成握手。首次调用;后续断线由内部自动重连。 */
|
|
24
30
|
connect(): Promise<void>;
|
|
25
|
-
private
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
/** 简单 KV,数据存 AILO 本体,自动持久化 */
|
|
31
|
+
private dial;
|
|
32
|
+
private handshake;
|
|
33
|
+
private attachHandlers;
|
|
34
|
+
private request;
|
|
30
35
|
getData(key: string): Promise<string | null>;
|
|
31
36
|
setData(key: string, value: string): Promise<void>;
|
|
32
37
|
deleteData(key: string): Promise<void>;
|
|
38
|
+
sendMessage(msg: BridgeMessage): Promise<void>;
|
|
39
|
+
/** 通过 WS 将日志发给 Ailo 代打,不阻塞,失败静默忽略 */
|
|
40
|
+
sendLog(level: "debug" | "info" | "warn" | "error", message: string, data?: Record<string, unknown>): void;
|
|
41
|
+
private startHeartbeat;
|
|
42
|
+
private stopHeartbeat;
|
|
43
|
+
private onDisconnect;
|
|
44
|
+
private rejectAllPending;
|
|
45
|
+
private scheduleReconnect;
|
|
46
|
+
/** 主动关闭连接(不触发自动重连) */
|
|
33
47
|
close(): void;
|
|
34
48
|
}
|
|
35
49
|
//# sourceMappingURL=ailo-client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ailo-client.d.ts","sourceRoot":"","sources":["../src/ailo-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"ailo-client.d.ts","sourceRoot":"","sources":["../src/ailo-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAsB5E,8BAA8B;AAC9B,wBAAgB,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAKjE;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;GAKG;AACH,qBAAa,UAAW,YAAW,cAAc;IAC/C,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,GAAG,CAAmB;IAC9B,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,cAAc,CAA+C;IACrE,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,gBAAgB,CAAS;gBAErB,MAAM,EAAE,gBAAgB;IAIpC,kCAAkC;IAC5B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAO9B,OAAO,CAAC,IAAI;IA6BZ,OAAO,CAAC,SAAS;IA+BjB,OAAO,CAAC,cAAc;IAuBtB,OAAO,CAAC,OAAO;IAmBT,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAK5C,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO5C,WAAW,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB9C,qCAAqC;IACrC,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAQ1G,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,iBAAiB;IAczB,sBAAsB;IACtB,KAAK,IAAI,IAAI;CAOd"}
|
package/dist/ailo-client.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import WebSocket from "ws";
|
|
2
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
3
|
+
const HEARTBEAT_INTERVAL_MS = 30_000;
|
|
4
|
+
const HEARTBEAT_TIMEOUT_MS = 10_000;
|
|
5
|
+
const RECONNECT_BASE_MS = 1_000;
|
|
6
|
+
const RECONNECT_MAX_MS = 60_000;
|
|
2
7
|
/** 按 kind 查找第一个匹配标签的 value */
|
|
3
8
|
export function tagValue(tags, kind) {
|
|
4
9
|
for (const t of tags) {
|
|
@@ -8,115 +13,142 @@ export function tagValue(tags, kind) {
|
|
|
8
13
|
return "";
|
|
9
14
|
}
|
|
10
15
|
/**
|
|
11
|
-
* 反向 WebSocket
|
|
16
|
+
* 反向 WebSocket 客户端。
|
|
12
17
|
*
|
|
13
|
-
* 连接 Ailo
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* 出站(AI → 平台)由 MCP stdio 工具处理,不经过此客户端。
|
|
18
|
+
* 连接 Ailo 网关,自动重连(指数退避)、心跳(ping/pong)、请求超时。
|
|
19
|
+
* 实现 ChannelStorage 接口,可直接作为 ctx.storage 使用。
|
|
17
20
|
*/
|
|
18
21
|
export class AiloClient {
|
|
19
22
|
ws = null;
|
|
20
|
-
|
|
21
|
-
token;
|
|
22
|
-
channel;
|
|
23
|
-
displayName;
|
|
24
|
-
defaultRequiresResponse;
|
|
25
|
-
channelPrompt;
|
|
26
|
-
reconnectTimer = null;
|
|
23
|
+
cfg;
|
|
27
24
|
reqId = 0;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
pending = new Map();
|
|
26
|
+
heartbeatTimer = null;
|
|
27
|
+
pongTimer = null;
|
|
28
|
+
reconnectTimer = null;
|
|
29
|
+
reconnectAttempt = 0;
|
|
30
|
+
intentionalClose = false;
|
|
31
|
+
constructor(config) {
|
|
32
|
+
this.cfg = config;
|
|
35
33
|
}
|
|
36
|
-
|
|
34
|
+
/** 建立连接并完成握手。首次调用;后续断线由内部自动重连。 */
|
|
35
|
+
async connect() {
|
|
36
|
+
this.intentionalClose = false;
|
|
37
|
+
await this.dial();
|
|
38
|
+
}
|
|
39
|
+
// ── 连接/握手 ──
|
|
40
|
+
dial() {
|
|
37
41
|
return new Promise((resolve, reject) => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
const ws = new WebSocket(this.cfg.url);
|
|
43
|
+
let settled = false;
|
|
44
|
+
const settle = (ok, err) => {
|
|
45
|
+
if (settled)
|
|
46
|
+
return;
|
|
47
|
+
settled = true;
|
|
48
|
+
ok ? resolve() : reject(err);
|
|
49
|
+
};
|
|
50
|
+
ws.on("open", async () => {
|
|
51
|
+
try {
|
|
52
|
+
await this.handshake(ws);
|
|
53
|
+
this.ws = ws;
|
|
54
|
+
this.reconnectAttempt = 0;
|
|
55
|
+
this.attachHandlers(ws);
|
|
56
|
+
this.startHeartbeat();
|
|
57
|
+
settle(true);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
ws.close();
|
|
61
|
+
settle(false, err);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
ws.on("error", (err) => settle(false, err));
|
|
65
|
+
ws.on("close", () => settle(false, new Error("closed before handshake")));
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
handshake(ws) {
|
|
69
|
+
const id = `connect-${++this.reqId}`;
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
const timer = setTimeout(() => {
|
|
72
|
+
ws.off("message", handler);
|
|
73
|
+
reject(new Error("handshake timeout"));
|
|
74
|
+
}, REQUEST_TIMEOUT_MS);
|
|
43
75
|
const handler = (raw) => {
|
|
44
76
|
const frame = JSON.parse(raw.toString());
|
|
45
77
|
if (frame.type === "res" && frame.id === id) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
reject(new Error(frame.error?.message ?? `${method} failed`));
|
|
52
|
-
}
|
|
78
|
+
clearTimeout(timer);
|
|
79
|
+
ws.off("message", handler);
|
|
80
|
+
frame.ok ? resolve() : reject(new Error(frame.error?.message ?? "connect rejected"));
|
|
53
81
|
}
|
|
54
82
|
};
|
|
55
|
-
|
|
56
|
-
|
|
83
|
+
ws.on("message", handler);
|
|
84
|
+
ws.send(JSON.stringify({
|
|
85
|
+
type: "req", id, method: "connect",
|
|
86
|
+
params: {
|
|
87
|
+
role: "channel",
|
|
88
|
+
token: this.cfg.token,
|
|
89
|
+
channel: this.cfg.channel,
|
|
90
|
+
displayName: this.cfg.displayName,
|
|
91
|
+
defaultRequiresResponse: this.cfg.defaultRequiresResponse ?? true,
|
|
92
|
+
prompt: this.cfg.channelPrompt ?? "",
|
|
93
|
+
},
|
|
94
|
+
}));
|
|
57
95
|
});
|
|
58
96
|
}
|
|
59
|
-
|
|
97
|
+
attachHandlers(ws) {
|
|
98
|
+
ws.on("message", (raw) => {
|
|
99
|
+
const frame = JSON.parse(raw.toString());
|
|
100
|
+
if (frame.type === "res" && frame.id) {
|
|
101
|
+
const req = this.pending.get(frame.id);
|
|
102
|
+
if (!req)
|
|
103
|
+
return;
|
|
104
|
+
this.pending.delete(frame.id);
|
|
105
|
+
clearTimeout(req.timer);
|
|
106
|
+
frame.ok
|
|
107
|
+
? req.resolve(frame.payload ?? {})
|
|
108
|
+
: req.reject(new Error(frame.error?.message ?? "request failed"));
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
ws.on("close", () => this.onDisconnect());
|
|
112
|
+
ws.on("pong", () => {
|
|
113
|
+
if (this.pongTimer) {
|
|
114
|
+
clearTimeout(this.pongTimer);
|
|
115
|
+
this.pongTimer = null;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
// ── 请求/响应 ──
|
|
120
|
+
request(method, params) {
|
|
60
121
|
return new Promise((resolve, reject) => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
token: this.token,
|
|
73
|
-
channel: this.channel,
|
|
74
|
-
displayName: this.displayName,
|
|
75
|
-
defaultRequiresResponse: this.defaultRequiresResponse,
|
|
76
|
-
prompt: this.channelPrompt,
|
|
77
|
-
},
|
|
78
|
-
}));
|
|
79
|
-
await new Promise((res, rej) => {
|
|
80
|
-
const onMsg = (raw) => {
|
|
81
|
-
const frame = JSON.parse(raw.toString());
|
|
82
|
-
if (frame.type === "res" && frame.id === id) {
|
|
83
|
-
ws.off("message", onMsg);
|
|
84
|
-
if (frame.ok) {
|
|
85
|
-
res();
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
rej(new Error(frame.error?.message ?? "connect failed"));
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
ws.on("message", onMsg);
|
|
93
|
-
});
|
|
94
|
-
resolve();
|
|
95
|
-
}
|
|
96
|
-
catch (err) {
|
|
97
|
-
reject(err);
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
ws.on("close", () => {
|
|
101
|
-
this.ws = null;
|
|
102
|
-
this.scheduleReconnect(resolve);
|
|
103
|
-
});
|
|
104
|
-
ws.on("error", (err) => {
|
|
105
|
-
reject(err);
|
|
106
|
-
});
|
|
122
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
123
|
+
reject(new Error("not connected"));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const id = `${method}-${++this.reqId}`;
|
|
127
|
+
const timer = setTimeout(() => {
|
|
128
|
+
this.pending.delete(id);
|
|
129
|
+
reject(new Error(`${method} timeout (${REQUEST_TIMEOUT_MS}ms)`));
|
|
130
|
+
}, REQUEST_TIMEOUT_MS);
|
|
131
|
+
this.pending.set(id, { resolve: resolve, reject, timer });
|
|
132
|
+
this.ws.send(JSON.stringify({ type: "req", id, method, params }));
|
|
107
133
|
});
|
|
108
134
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
this.reconnectTimer = null;
|
|
114
|
-
this.connect()
|
|
115
|
-
.then(() => onReconnect?.())
|
|
116
|
-
.catch(() => this.scheduleReconnect(onReconnect));
|
|
117
|
-
}, 3000);
|
|
135
|
+
// ── ChannelStorage ──
|
|
136
|
+
async getData(key) {
|
|
137
|
+
const res = await this.request("channel.data.get", { key });
|
|
138
|
+
return res.found ? (res.value ?? null) : null;
|
|
118
139
|
}
|
|
140
|
+
async setData(key, value) {
|
|
141
|
+
await this.request("channel.data.set", { key, value });
|
|
142
|
+
}
|
|
143
|
+
async deleteData(key) {
|
|
144
|
+
await this.request("channel.data.delete", { key });
|
|
145
|
+
}
|
|
146
|
+
// ── 消息投递 ──
|
|
147
|
+
// channel.accept 是单向推送,不需要等待响应。机制上不进入 request/pending,发完即返回。
|
|
119
148
|
sendMessage(msg) {
|
|
149
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
150
|
+
return Promise.reject(new Error("not connected"));
|
|
151
|
+
}
|
|
120
152
|
const chatId = tagValue(msg.contextTags, "chat_id") || "main";
|
|
121
153
|
const params = {
|
|
122
154
|
chatId,
|
|
@@ -127,24 +159,78 @@ export class AiloClient {
|
|
|
127
159
|
if (msg.requiresResponse !== undefined) {
|
|
128
160
|
params.requiresResponse = msg.requiresResponse;
|
|
129
161
|
}
|
|
130
|
-
|
|
162
|
+
const id = `channel.accept-${++this.reqId}`;
|
|
163
|
+
this.ws.send(JSON.stringify({ type: "req", id, method: "channel.accept", params }));
|
|
164
|
+
return Promise.resolve();
|
|
131
165
|
}
|
|
132
|
-
/**
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
166
|
+
/** 通过 WS 将日志发给 Ailo 代打,不阻塞,失败静默忽略 */
|
|
167
|
+
sendLog(level, message, data) {
|
|
168
|
+
const params = { level, message };
|
|
169
|
+
if (data && Object.keys(data).length > 0)
|
|
170
|
+
params.data = data;
|
|
171
|
+
this.request("channel.log", params).catch(() => { });
|
|
136
172
|
}
|
|
137
|
-
|
|
138
|
-
|
|
173
|
+
// ── 心跳 ──
|
|
174
|
+
startHeartbeat() {
|
|
175
|
+
this.stopHeartbeat();
|
|
176
|
+
this.heartbeatTimer = setInterval(() => {
|
|
177
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
|
|
178
|
+
return;
|
|
179
|
+
this.ws.ping();
|
|
180
|
+
this.pongTimer = setTimeout(() => {
|
|
181
|
+
console.error("[ailo-client] pong timeout, closing");
|
|
182
|
+
this.ws?.terminate();
|
|
183
|
+
}, HEARTBEAT_TIMEOUT_MS);
|
|
184
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
139
185
|
}
|
|
140
|
-
|
|
141
|
-
|
|
186
|
+
stopHeartbeat() {
|
|
187
|
+
if (this.heartbeatTimer) {
|
|
188
|
+
clearInterval(this.heartbeatTimer);
|
|
189
|
+
this.heartbeatTimer = null;
|
|
190
|
+
}
|
|
191
|
+
if (this.pongTimer) {
|
|
192
|
+
clearTimeout(this.pongTimer);
|
|
193
|
+
this.pongTimer = null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// ── 断线/重连 ──
|
|
197
|
+
onDisconnect() {
|
|
198
|
+
this.ws = null;
|
|
199
|
+
this.stopHeartbeat();
|
|
200
|
+
this.rejectAllPending(new Error("disconnected"));
|
|
201
|
+
if (!this.intentionalClose)
|
|
202
|
+
this.scheduleReconnect();
|
|
203
|
+
}
|
|
204
|
+
rejectAllPending(err) {
|
|
205
|
+
for (const [, req] of this.pending) {
|
|
206
|
+
clearTimeout(req.timer);
|
|
207
|
+
req.reject(err);
|
|
208
|
+
}
|
|
209
|
+
this.pending.clear();
|
|
210
|
+
}
|
|
211
|
+
scheduleReconnect() {
|
|
212
|
+
if (this.reconnectTimer)
|
|
213
|
+
return;
|
|
214
|
+
const delay = Math.min(RECONNECT_BASE_MS * 2 ** this.reconnectAttempt, RECONNECT_MAX_MS);
|
|
215
|
+
this.reconnectAttempt++;
|
|
216
|
+
console.error(`[ailo-client] reconnecting in ${delay}ms (attempt ${this.reconnectAttempt})`);
|
|
217
|
+
this.reconnectTimer = setTimeout(() => {
|
|
218
|
+
this.reconnectTimer = null;
|
|
219
|
+
this.dial().catch((err) => {
|
|
220
|
+
console.error("[ailo-client] reconnect failed:", err.message);
|
|
221
|
+
this.scheduleReconnect();
|
|
222
|
+
});
|
|
223
|
+
}, delay);
|
|
142
224
|
}
|
|
225
|
+
/** 主动关闭连接(不触发自动重连) */
|
|
143
226
|
close() {
|
|
227
|
+
this.intentionalClose = true;
|
|
144
228
|
if (this.reconnectTimer) {
|
|
145
229
|
clearTimeout(this.reconnectTimer);
|
|
146
230
|
this.reconnectTimer = null;
|
|
147
231
|
}
|
|
232
|
+
this.stopHeartbeat();
|
|
233
|
+
this.rejectAllPending(new Error("client closed"));
|
|
148
234
|
if (this.ws) {
|
|
149
235
|
this.ws.close();
|
|
150
236
|
this.ws = null;
|
package/dist/bootstrap.d.ts
CHANGED
|
@@ -19,10 +19,7 @@ export interface McpChannelConfig {
|
|
|
19
19
|
ailoWsUrl?: string;
|
|
20
20
|
/** Ailo 网关认证 Token。不传则从 AILO_TOKEN 读取 */
|
|
21
21
|
ailoToken?: string;
|
|
22
|
-
/**
|
|
23
|
-
* 构建通道静态提示词(connect 时注册)。
|
|
24
|
-
* 逐步废弃:通道指令应迁移到 MCP 工具定义(tool schema description)中。
|
|
25
|
-
*/
|
|
22
|
+
/** 构建通道静态提示词(connect 时注册)。逐步废弃:通道指令应迁移到 MCP 工具定义中。 */
|
|
26
23
|
buildChannelPrompt?: () => string;
|
|
27
24
|
/** 预配置的 MCP Server 实例(已注册好工具) */
|
|
28
25
|
mcpServer: McpServer;
|
|
@@ -38,12 +35,12 @@ export declare function runMcp(mcpServer: McpServer): void;
|
|
|
38
35
|
/**
|
|
39
36
|
* 启动 MCP 通道
|
|
40
37
|
*
|
|
41
|
-
*
|
|
38
|
+
* 流程:
|
|
42
39
|
* 1. stdio-guard 已在 index 入口加载,console.log 等自动重定向到 stderr
|
|
43
40
|
* 2. 启动 MCP stdio server(暴露出站工具)
|
|
44
|
-
* 3.
|
|
45
|
-
* 4.
|
|
46
|
-
* 5.
|
|
41
|
+
* 3. 组装 ChannelContext(accept + storage)
|
|
42
|
+
* 4. 先做 WS 回连(connect 到 Ailo),确认成功后才有出口
|
|
43
|
+
* 5. 确认回连成功后,再 handler.start(ctx) 启动平台连接(如飞书 WebSocket)
|
|
47
44
|
* 6. 注册 SIGINT / SIGTERM 优雅退出
|
|
48
45
|
*/
|
|
49
46
|
export declare function runMcpChannel(config: McpChannelConfig): void;
|
package/dist/bootstrap.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAiC,MAAM,YAAY,CAAC;AAE/E;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,0CAA0C;IAC1C,OAAO,EAAE,aAAa,CAAC;IACvB,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,kBAAkB,CAAC,EAAE,MAAM,MAAM,CAAC;IAClC,iCAAiC;IACjC,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,wBAAgB,yBAAyB,IAAI,MAAM,CAElD;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAQjD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAwE5D"}
|
package/dist/bootstrap.js
CHANGED
|
@@ -10,7 +10,6 @@ export function defaultBuildChannelPrompt() {
|
|
|
10
10
|
* stdout 被 MCP stdio 占用,日志自动重定向到 stderr。
|
|
11
11
|
*/
|
|
12
12
|
export function runMcp(mcpServer) {
|
|
13
|
-
// stdio-guard 需在入口 import,runMcp 使用者应 import 自 @lmcl/ailo-mcp-sdk
|
|
14
13
|
const transport = new StdioServerTransport();
|
|
15
14
|
mcpServer.connect(transport).then(() => {
|
|
16
15
|
console.log("[mcp] MCP stdio server started");
|
|
@@ -22,12 +21,12 @@ export function runMcp(mcpServer) {
|
|
|
22
21
|
/**
|
|
23
22
|
* 启动 MCP 通道
|
|
24
23
|
*
|
|
25
|
-
*
|
|
24
|
+
* 流程:
|
|
26
25
|
* 1. stdio-guard 已在 index 入口加载,console.log 等自动重定向到 stderr
|
|
27
26
|
* 2. 启动 MCP stdio server(暴露出站工具)
|
|
28
|
-
* 3.
|
|
29
|
-
* 4.
|
|
30
|
-
* 5.
|
|
27
|
+
* 3. 组装 ChannelContext(accept + storage)
|
|
28
|
+
* 4. 先做 WS 回连(connect 到 Ailo),确认成功后才有出口
|
|
29
|
+
* 5. 确认回连成功后,再 handler.start(ctx) 启动平台连接(如飞书 WebSocket)
|
|
31
30
|
* 6. 注册 SIGINT / SIGTERM 优雅退出
|
|
32
31
|
*/
|
|
33
32
|
export function runMcpChannel(config) {
|
|
@@ -39,69 +38,62 @@ export function runMcpChannel(config) {
|
|
|
39
38
|
console.error("Missing AILO_WS_URL, AILO_TOKEN or AILO_MCP_NAME. Channel must be started by Ailo MCP.");
|
|
40
39
|
process.exit(1);
|
|
41
40
|
}
|
|
42
|
-
// stdio-guard 已在 index 入口加载,console.log 等已重定向到 stderr
|
|
43
41
|
const tag = `[${channelName}]`;
|
|
44
|
-
const channelPrompt = config.buildChannelPrompt
|
|
45
|
-
? config.buildChannelPrompt()
|
|
46
|
-
: defaultBuildChannelPrompt();
|
|
47
|
-
const displayName = config.displayName;
|
|
42
|
+
const channelPrompt = config.buildChannelPrompt?.() ?? defaultBuildChannelPrompt();
|
|
48
43
|
const defaultRequiresResponse = config.defaultRequiresResponse ?? true;
|
|
49
|
-
const client = new AiloClient(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
console.log(`${tag} ${(msg.text ?? "").slice(0, 80)}`);
|
|
59
|
-
try {
|
|
60
|
-
await client.sendMessage(msg);
|
|
61
|
-
}
|
|
62
|
-
catch (err) {
|
|
63
|
-
console.error(`${tag} send to Ailo failed:`, err);
|
|
64
|
-
}
|
|
44
|
+
const client = new AiloClient({
|
|
45
|
+
url: ailoWsUrl,
|
|
46
|
+
token: ailoToken,
|
|
47
|
+
channel: channelName,
|
|
48
|
+
displayName: config.displayName,
|
|
49
|
+
defaultRequiresResponse,
|
|
50
|
+
channelPrompt,
|
|
65
51
|
});
|
|
66
|
-
// 优雅退出
|
|
67
52
|
const shutdown = () => {
|
|
68
53
|
console.log(`${tag} shutting down...`);
|
|
69
|
-
handler.stop
|
|
54
|
+
Promise.resolve(handler.stop()).catch(() => { });
|
|
70
55
|
client.close();
|
|
71
56
|
process.exit(0);
|
|
72
57
|
};
|
|
73
58
|
process.on("SIGINT", shutdown);
|
|
74
59
|
process.on("SIGTERM", shutdown);
|
|
75
60
|
(async () => {
|
|
76
|
-
// 1.
|
|
61
|
+
// 1. MCP stdio
|
|
77
62
|
const transport = new StdioServerTransport();
|
|
78
63
|
await mcpServer.connect(transport);
|
|
79
64
|
console.log(`${tag} MCP stdio server started`);
|
|
80
|
-
// 2.
|
|
65
|
+
// 2. ChannelContext: accept + storage + log,一次性就绪
|
|
66
|
+
const ctx = {
|
|
67
|
+
accept: async (msg) => {
|
|
68
|
+
const hasContent = (msg.text?.trim() ?? "") !== ""
|
|
69
|
+
|| (msg.attachments?.length ?? 0) > 0
|
|
70
|
+
|| msg.contextTags.length > 0;
|
|
71
|
+
if (!hasContent) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
await client.sendMessage(msg);
|
|
75
|
+
},
|
|
76
|
+
storage: client,
|
|
77
|
+
log: (level, message, data) => client.sendLog(level, message, data),
|
|
78
|
+
};
|
|
79
|
+
// 3. 先做 WS 回连(连接 Ailo),必须确认成功后才有出口
|
|
81
80
|
try {
|
|
82
81
|
await client.connect();
|
|
83
|
-
console.log(`${tag}
|
|
82
|
+
console.log(`${tag} Ailo WebSocket connected`);
|
|
84
83
|
}
|
|
85
84
|
catch (err) {
|
|
86
|
-
console.error(`${tag}
|
|
85
|
+
console.error(`${tag} Ailo WebSocket connect failed:`, err);
|
|
87
86
|
process.exit(1);
|
|
88
87
|
}
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
// 4. 确认回连成功后,再启动平台连接(如飞书 WebSocket)
|
|
89
|
+
console.log(`${tag} starting handler (platform connection)...`);
|
|
90
|
+
try {
|
|
91
|
+
await handler.start(ctx);
|
|
92
|
+
console.log(`${tag} handler started`);
|
|
92
93
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (startResult && typeof startResult.then === "function") {
|
|
97
|
-
try {
|
|
98
|
-
await startResult;
|
|
99
|
-
console.log(`${tag} handler started successfully`);
|
|
100
|
-
}
|
|
101
|
-
catch (err) {
|
|
102
|
-
console.error(`${tag} handler start failed:`, err);
|
|
103
|
-
process.exit(1);
|
|
104
|
-
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
console.error(`${tag} handler start failed:`, err);
|
|
96
|
+
process.exit(1);
|
|
105
97
|
}
|
|
106
98
|
})();
|
|
107
99
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import "./stdio-guard.js";
|
|
2
|
-
export { AiloClient } from "./ailo-client.js";
|
|
2
|
+
export { AiloClient, tagValue } from "./ailo-client.js";
|
|
3
|
+
export type { AiloClientConfig } from "./ailo-client.js";
|
|
3
4
|
export { getWorkDir } from "./workdir.js";
|
|
4
5
|
export { runMcp, runMcpChannel, defaultBuildChannelPrompt } from "./bootstrap.js";
|
|
5
6
|
export type { McpChannelConfig } from "./bootstrap.js";
|
|
6
|
-
export type { Attachment, BridgeHandler, BridgeMessage, ContextTag, } from "./types.js";
|
|
7
|
+
export type { Attachment, BridgeHandler, BridgeMessage, ChannelContext, ChannelStorage, ContextTag, } from "./types.js";
|
|
7
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACxD,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAClF,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,YAAY,EACV,UAAU,EACV,aAAa,EACb,aAAa,EACb,cAAc,EACd,cAAc,EACd,UAAU,GACX,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import "./stdio-guard.js"; // 必须最先:stdout 被 MCP stdio 占用,框架层统一拦截非 JSON-RPC 输出
|
|
2
|
-
export { AiloClient } from "./ailo-client.js";
|
|
2
|
+
export { AiloClient, tagValue } from "./ailo-client.js";
|
|
3
3
|
export { getWorkDir } from "./workdir.js";
|
|
4
4
|
export { runMcp, runMcpChannel, defaultBuildChannelPrompt } from "./bootstrap.js";
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 附件(入站/出站通用)
|
|
3
|
+
*
|
|
3
4
|
* 入站图片:path/url/base64 三选一,直接使用 LLM 多模态。其他类型:path/url/ref+channel/base64。
|
|
4
5
|
* 出站:file_path 或 base64 或 url。
|
|
5
6
|
*/
|
|
@@ -28,7 +29,7 @@ export type ContextTag = {
|
|
|
28
29
|
routing?: boolean;
|
|
29
30
|
};
|
|
30
31
|
/**
|
|
31
|
-
*
|
|
32
|
+
* 入站消息(平台 → Ailo)
|
|
32
33
|
*
|
|
33
34
|
* 时空场模型:通道自己定义,全在 contextTags 里。
|
|
34
35
|
*/
|
|
@@ -39,21 +40,42 @@ export type BridgeMessage = {
|
|
|
39
40
|
/** 本条消息是否需要 LLM 响应(覆盖通道级 defaultRequiresResponse) */
|
|
40
41
|
requiresResponse?: boolean;
|
|
41
42
|
};
|
|
42
|
-
/**
|
|
43
|
-
|
|
43
|
+
/**
|
|
44
|
+
* 通道级 KV 存储(数据持久化在 Ailo 侧)
|
|
45
|
+
*/
|
|
46
|
+
export interface ChannelStorage {
|
|
44
47
|
getData(key: string): Promise<string | null>;
|
|
45
48
|
setData(key: string, value: string): Promise<void>;
|
|
46
49
|
deleteData(key: string): Promise<void>;
|
|
47
|
-
}
|
|
50
|
+
}
|
|
48
51
|
/**
|
|
49
|
-
*
|
|
52
|
+
* 通道运行时上下文
|
|
53
|
+
*
|
|
54
|
+
* SDK 在调用 handler.start() 时注入,handler 通过 ctx 与 Ailo 交互。
|
|
55
|
+
* 替代旧版 setOnMessage + setDataProvider 的 setter 注入模式——
|
|
56
|
+
* 所有依赖在 start() 一次性就绪,无时序歧义。
|
|
57
|
+
*/
|
|
58
|
+
export interface ChannelContext {
|
|
59
|
+
/** 投递入站消息到 Ailo(平台 → Ailo)。SDK 内部做空消息过滤。 */
|
|
60
|
+
accept(msg: BridgeMessage): Promise<void>;
|
|
61
|
+
/** 通道级 KV 存储 */
|
|
62
|
+
storage: ChannelStorage;
|
|
63
|
+
/**
|
|
64
|
+
* 通过 WS 将日志发给 Ailo 代打(MCP 子进程 stdout 被 stdio 占用时使用)。
|
|
65
|
+
* level: "debug" | "info" | "warn" | "error"
|
|
66
|
+
* 不阻塞,失败静默忽略。
|
|
67
|
+
*/
|
|
68
|
+
log(level: "debug" | "info" | "warn" | "error", message: string, data?: Record<string, unknown>): void;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 通道 Handler 接口
|
|
72
|
+
*
|
|
73
|
+
* 通道需实现此接口。SDK 保证 start(ctx) 调用时 ctx 已就绪。
|
|
50
74
|
*/
|
|
51
75
|
export interface BridgeHandler {
|
|
52
|
-
|
|
53
|
-
start(): void | Promise<void>;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
setDataProvider?(storage: ChannelStorage): void;
|
|
76
|
+
/** 启动 Handler。ctx 包含与 Ailo 交互所需的一切。 */
|
|
77
|
+
start(ctx: ChannelContext): void | Promise<void>;
|
|
78
|
+
/** 优雅停止。SDK 在 SIGINT/SIGTERM 时调用。 */
|
|
79
|
+
stop(): void | Promise<void>;
|
|
57
80
|
}
|
|
58
|
-
export {};
|
|
59
81
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,qDAAqD;IACrD,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,4CAA4C;IAC5C,MAAM,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,gBAAgB;IAChB,OAAO,EAAE,cAAc,CAAC;IACxB;;;;OAIG;IACH,GAAG,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACxG;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,uCAAuC;IACvC,KAAK,CAAC,GAAG,EAAE,cAAc,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,qCAAqC;IACrC,IAAI,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B"}
|