@sumeai/sumeclaw 1.0.27 → 1.1.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/index.ts +78 -0
- package/openclaw.plugin.json +11 -28
- package/package.json +22 -43
- package/readme.md +133 -0
- package/setup-entry.ts +4 -0
- package/src/inbound-handler.ts +191 -0
- package/src/outbound-adapter.ts +94 -0
- package/src/plugin.ts +31 -0
- package/src/types.ts +26 -0
- package/src/websocket-client.ts +174 -0
- package/dist/index.d.ts +0 -12
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -47
- package/dist/index.js.map +0 -1
- package/dist/setup-entry.d.ts +0 -3
- package/dist/setup-entry.d.ts.map +0 -1
- package/dist/setup-entry.js +0 -12
- package/dist/setup-entry.js.map +0 -1
- package/dist/src/channel.d.ts +0 -7
- package/dist/src/channel.d.ts.map +0 -1
- package/dist/src/channel.js +0 -117
- package/dist/src/channel.js.map +0 -1
- package/dist/src/gateway.d.ts +0 -26
- package/dist/src/gateway.d.ts.map +0 -1
- package/dist/src/gateway.js +0 -121
- package/dist/src/gateway.js.map +0 -1
package/index.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core";
|
|
2
|
+
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
handleSumeclawInbound,
|
|
6
|
+
isDuplicateMessage,
|
|
7
|
+
type SumeclawInboundContext,
|
|
8
|
+
} from "./src/inbound-handler.js";
|
|
9
|
+
import { resolveAccount, type ResolvedAccount } from "./src/types.js";
|
|
10
|
+
import { sumeclawPlugin } from "./src/plugin.js";
|
|
11
|
+
import { SumeclawWebSocketClient } from "./src/websocket-client.js";
|
|
12
|
+
|
|
13
|
+
// 扩展插件以添加 runtime 钩子
|
|
14
|
+
sumeclawPlugin.runtime = {
|
|
15
|
+
async onActivate(context) {
|
|
16
|
+
const logger = context.logger;
|
|
17
|
+
logger.info("[sumeclaw] Plugin activated", { channel: "sumeclaw" });
|
|
18
|
+
|
|
19
|
+
const account = resolveAccount(context.cfg);
|
|
20
|
+
const wsClient = new SumeclawWebSocketClient(account, context.runtime, logger);
|
|
21
|
+
|
|
22
|
+
wsClient.connect(async (rawMessage) => {
|
|
23
|
+
if (isDuplicateMessage(rawMessage.id)) {
|
|
24
|
+
logger.verbose("[sumeclaw] Duplicate message dropped", {
|
|
25
|
+
channel: "sumeclaw",
|
|
26
|
+
messageId: rawMessage.id,
|
|
27
|
+
});
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const inboundContext: SumeclawInboundContext = {
|
|
32
|
+
cfg: context.cfg,
|
|
33
|
+
runtime: context.runtime,
|
|
34
|
+
logger,
|
|
35
|
+
accountId: account.accountId,
|
|
36
|
+
message: rawMessage,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
await handleSumeclawInbound(inboundContext);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
(context.runtime as any).sumeclawWsClient = wsClient;
|
|
43
|
+
},
|
|
44
|
+
async onDeactivate(context) {
|
|
45
|
+
const logger = context.logger;
|
|
46
|
+
logger.info("[sumeclaw] Plugin deactivated", { channel: "sumeclaw" });
|
|
47
|
+
|
|
48
|
+
const wsClient = (context.runtime as any).sumeclawWsClient;
|
|
49
|
+
if (wsClient) {
|
|
50
|
+
wsClient.disconnect();
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export default defineChannelPluginEntry({
|
|
56
|
+
id: "sumeclaw",
|
|
57
|
+
name: "友虾名片",
|
|
58
|
+
description: "将 OpenClaw 连接到友虾名片平台,通过 AI 名片为客户提供服务",
|
|
59
|
+
plugin: sumeclawPlugin,
|
|
60
|
+
registerCliMetadata(api) {
|
|
61
|
+
api.registerCli(
|
|
62
|
+
({ program }) => {
|
|
63
|
+
program
|
|
64
|
+
.command("sumeclaw")
|
|
65
|
+
.description("友虾名片管理");
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
descriptors: [
|
|
69
|
+
{
|
|
70
|
+
name: "sumeclaw",
|
|
71
|
+
description: "友虾名片管理",
|
|
72
|
+
hasSubcommands: false,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
},
|
|
78
|
+
});
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,28 +1,11 @@
|
|
|
1
|
-
{
|
|
2
|
-
"
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"registrationToken": {
|
|
13
|
-
"type": "string",
|
|
14
|
-
"description": "从友虾小程序获取的注册令牌"
|
|
15
|
-
},
|
|
16
|
-
"platformUrl": {
|
|
17
|
-
"type": "string",
|
|
18
|
-
"description": "友虾名片平台 WebSocket 地址,默认为 wss://api.gixin.cc"
|
|
19
|
-
},
|
|
20
|
-
"enabled": {
|
|
21
|
-
"type": "boolean",
|
|
22
|
-
"description": "是否启用此通道",
|
|
23
|
-
"default": true
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
"required": []
|
|
27
|
-
}
|
|
28
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"id": "sumeclaw",
|
|
3
|
+
"name": "sumeclaw",
|
|
4
|
+
"description": "将 OpenClaw 连接到友虾名片平台,通过 AI 名片为客户提供服务。",
|
|
5
|
+
"contracts": {
|
|
6
|
+
"channels": ["sumeclaw"]
|
|
7
|
+
},
|
|
8
|
+
"activation": {
|
|
9
|
+
"onStartup": true
|
|
10
|
+
}
|
|
11
|
+
}
|
package/package.json
CHANGED
|
@@ -1,43 +1,22 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@sumeai/sumeclaw",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "友虾名片 OpenClaw Channel 插件 — 将 OpenClaw 连接到友虾名片平台,通过 AI 名片为客户提供服务",
|
|
5
|
-
"type": "
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
},
|
|
24
|
-
"devDependencies": {
|
|
25
|
-
"typescript": "^5.7.0",
|
|
26
|
-
"@types/ws": "^8.5.0"
|
|
27
|
-
},
|
|
28
|
-
"engines": {
|
|
29
|
-
"openclaw": ">=2026.3.22"
|
|
30
|
-
},
|
|
31
|
-
"keywords": [
|
|
32
|
-
"openclaw",
|
|
33
|
-
"channel",
|
|
34
|
-
"minicard",
|
|
35
|
-
"友虾名片",
|
|
36
|
-
"ai-card"
|
|
37
|
-
],
|
|
38
|
-
"author": "sumeai",
|
|
39
|
-
"license": "MIT",
|
|
40
|
-
"publishConfig": {
|
|
41
|
-
"access": "public"
|
|
42
|
-
}
|
|
43
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@sumeai/sumeclaw",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "友虾名片 OpenClaw Channel 插件 — 将 OpenClaw 连接到友虾名片平台,通过 AI 名片为客户提供服务",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"openclaw": {
|
|
7
|
+
"extensions": ["./index.ts"],
|
|
8
|
+
"setupEntry": "./setup-entry.ts",
|
|
9
|
+
"channel": {
|
|
10
|
+
"id": "sumeclaw",
|
|
11
|
+
"label": "友虾名片",
|
|
12
|
+
"blurb": "将 OpenClaw 连接到友虾名片平台,通过 AI 名片为客户提供服务"
|
|
13
|
+
},
|
|
14
|
+
"compat": {
|
|
15
|
+
"pluginApi": ">=2026.3.24-beta.2",
|
|
16
|
+
"minGatewayVersion": ">=2026.3.24-beta.2"
|
|
17
|
+
},
|
|
18
|
+
"install": {
|
|
19
|
+
"npmSpec": "@sumeai/sumeclaw"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
# 文件结构
|
|
4
|
+
@sumeai/sumeclaw/
|
|
5
|
+
├── index.ts # 完整插件入口点(包含 runtime 钩子)
|
|
6
|
+
├── setup-entry.ts # 轻量级设置入口
|
|
7
|
+
├── src/
|
|
8
|
+
│ ├── types.ts # 类型定义和 resolveAccount
|
|
9
|
+
│ ├── plugin.ts # 插件配置(createChatChannelPlugin)
|
|
10
|
+
│ ├── outbound-adapter.ts # Outbound 适配器
|
|
11
|
+
│ ├── websocket-client.ts # WebSocket 客户端类
|
|
12
|
+
│ └── inbound-handler.ts # Inbound 消息处理
|
|
13
|
+
├── package.json
|
|
14
|
+
├── readme.md
|
|
15
|
+
└── openclaw.plugin.json
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# 文件说明
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## 《index.ts》
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
- `index.ts` 现在只负责插件注册和生命周期管理,符合 OpenClaw 插件的最佳实践
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## 《setup-entry.ts》
|
|
34
|
+
|
|
35
|
+
### Setup Entry 的作用
|
|
36
|
+
|
|
37
|
+
`setup-entry.ts` 是一个轻量级的入口点,OpenClaw 在以下情况下会加载它而不是完整的 `index.ts`:
|
|
38
|
+
|
|
39
|
+
- 频道被禁用但需要设置/引导界面
|
|
40
|
+
- 频道已启用但未配置
|
|
41
|
+
- 启用了延迟加载(`deferConfiguredChannelFullLoadUntilAfterListen`)
|
|
42
|
+
|
|
43
|
+
### `defineSetupPluginEntry`
|
|
44
|
+
|
|
45
|
+
这个辅助函数只返回 `{ plugin }`,不包含运行时或 CLI 连线。这确保了 setup entry 保持轻量级。
|
|
46
|
+
|
|
47
|
+
### Setup Entry 必须包含
|
|
48
|
+
|
|
49
|
+
- 频道插件对象(通过 `defineSetupPluginEntry`)
|
|
50
|
+
- 网关监听前需要的任何 HTTP 路由
|
|
51
|
+
- 启动期间需要的任何 gateway 方法
|
|
52
|
+
|
|
53
|
+
### Setup Entry 不应包含
|
|
54
|
+
|
|
55
|
+
- CLI 注册
|
|
56
|
+
- 后台服务
|
|
57
|
+
- 重的运行时导入(crypto、SDKs)
|
|
58
|
+
- 仅在启动后需要的 gateway 方法
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
## 《outbound-adapter.ts》
|
|
67
|
+
|
|
68
|
+
### Outbound 适配器结构
|
|
69
|
+
|
|
70
|
+
`createChatChannelPlugin` 的 `outbound` 参数支持两种形式:
|
|
71
|
+
|
|
72
|
+
1. **`attachedResults`** - 包含 `sendText`、`sendMedia`、`sendPoll` 方法,返回结果元数据(如 `messageId`)
|
|
73
|
+
2. **`base`** - 包含其他 outbound 配置,如 `deliveryMode`、`chunker`、`sanitizeText` 等
|
|
74
|
+
|
|
75
|
+
### sendText 方法签名
|
|
76
|
+
|
|
77
|
+
`sendText` 接收的 context 参数包含:
|
|
78
|
+
|
|
79
|
+
- `cfg` - OpenClaw 配置
|
|
80
|
+
- `to` - 目标用户/频道 ID
|
|
81
|
+
- `text` - 要发送的文本
|
|
82
|
+
- `accountId` - 账户 ID
|
|
83
|
+
- `replyToId` - 回复的消息 ID
|
|
84
|
+
- `threadId` - 线程 ID
|
|
85
|
+
|
|
86
|
+
### sendMedia 方法签名
|
|
87
|
+
|
|
88
|
+
`sendMedia` 接收额外的参数:
|
|
89
|
+
|
|
90
|
+
- `mediaUrl` - 媒体文件 URL
|
|
91
|
+
- `mediaAccess` - 媒体访问权限
|
|
92
|
+
- `mediaLocalRoots` - 本地媒体根目录
|
|
93
|
+
- `mediaReadFile` - 读取本地文件的函数
|
|
94
|
+
|
|
95
|
+
### 参考实现
|
|
96
|
+
|
|
97
|
+
- **Slack** 使用 `createAttachedChannelResultAdapter` 包装 send 方法
|
|
98
|
+
- **WhatsApp** 在 sendMedia 中处理媒体上传和发送
|
|
99
|
+
- **Telegram** 定义了 `deliveryCapabilities` 来声明支持的功能
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
## 《websocket-client.ts》
|
|
108
|
+
|
|
109
|
+
### WebSocket 客户端特性
|
|
110
|
+
|
|
111
|
+
1. **连接管理** - 使用 `ws` 包创建 WebSocket 连接,支持握手超时配置
|
|
112
|
+
2. **事件捕获** - 使用 `captureWsEvent` 记录 WebSocket 事件,便于调试和监控
|
|
113
|
+
3. **自动重连** - 连接断开后自动重连,默认 5 秒延迟
|
|
114
|
+
4. **认证处理** - 连接成功后发送认证消息(需要根据友虾名片 API 实现)
|
|
115
|
+
5. **消息处理** - 接收消息并通过回调函数转发
|
|
116
|
+
|
|
117
|
+
### 集成到插件生命周期
|
|
118
|
+
|
|
119
|
+
- **`onActivate`** - 插件激活时创建 WebSocket 连接并存储到运行时上下文
|
|
120
|
+
- **`onDeactivate`** - 插件停用时断开 WebSocket 连接
|
|
121
|
+
- **Outbound** - 从运行时上下文获取 WebSocket 客户端实例发送消息
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
## 《inbound-handler.ts》
|
|
126
|
+
|
|
127
|
+
### Inbound 处理流程
|
|
128
|
+
|
|
129
|
+
1. **消息解析** - `parseSumeclawMessage` 将平台原始消息转换为标准格式
|
|
130
|
+
2. **会话路由** - 使用 `buildAgentSessionKey` 和 `resolveThreadSessionKeys` 构建会话密钥
|
|
131
|
+
3. **Envelope 格式化** - 使用 `formatInboundEnvelope` 构建 OpenClaw 标准消息 envelope
|
|
132
|
+
4. **上下文完成** - 使用 `finalizeInboundContext` 完成 inbound 上下文
|
|
133
|
+
5. **消息去重** - 使用内存缓存防止重复处理消息
|
package/setup-entry.ts
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatInboundEnvelope,
|
|
3
|
+
resolveEnvelopeFormatOptions,
|
|
4
|
+
} from "openclaw/plugin-sdk/channel-inbound";
|
|
5
|
+
import { resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime";
|
|
6
|
+
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-dispatch-runtime";
|
|
7
|
+
import { buildAgentSessionKey, resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
|
|
8
|
+
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
|
9
|
+
import { evaluateSupplementalContextVisibility } from "openclaw/plugin-sdk/security-runtime";
|
|
10
|
+
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-runtime";
|
|
11
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
|
12
|
+
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
|
13
|
+
|
|
14
|
+
// 友虾名片平台消息类型
|
|
15
|
+
export type SumeclawMessage = {
|
|
16
|
+
id: string;
|
|
17
|
+
from: string;
|
|
18
|
+
to: string;
|
|
19
|
+
text: string;
|
|
20
|
+
timestamp: number;
|
|
21
|
+
type: "text" | "media" | "system";
|
|
22
|
+
mediaUrl?: string;
|
|
23
|
+
replyToId?: string;
|
|
24
|
+
threadId?: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Inbound 处理上下文
|
|
28
|
+
export type SumeclawInboundContext = {
|
|
29
|
+
cfg: OpenClawConfig;
|
|
30
|
+
runtime: RuntimeEnv;
|
|
31
|
+
logger: any;
|
|
32
|
+
accountId: string | null;
|
|
33
|
+
message: SumeclawMessage;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// 解析友虾名片消息到 OpenClaw 格式
|
|
37
|
+
export function parseSumeclawMessage(raw: any): SumeclawMessage | null {
|
|
38
|
+
try {
|
|
39
|
+
// 根据友虾名片 API 实际格式解析
|
|
40
|
+
return {
|
|
41
|
+
id: raw.id || String(Date.now()),
|
|
42
|
+
from: raw.from || raw.senderId,
|
|
43
|
+
to: raw.to || raw.receiverId,
|
|
44
|
+
text: raw.text || raw.content || "",
|
|
45
|
+
timestamp: raw.timestamp || Date.now(),
|
|
46
|
+
type: raw.type || "text",
|
|
47
|
+
mediaUrl: raw.mediaUrl,
|
|
48
|
+
replyToId: raw.replyToId,
|
|
49
|
+
threadId: raw.threadId,
|
|
50
|
+
};
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error("[sumeclaw] Failed to parse message", error);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 构建 inbound envelope
|
|
58
|
+
export async function buildSumeclawInboundEnvelope(
|
|
59
|
+
context: SumeclawInboundContext,
|
|
60
|
+
) {
|
|
61
|
+
const { cfg, runtime, logger, accountId, message } = context;
|
|
62
|
+
|
|
63
|
+
// 解析会话密钥
|
|
64
|
+
const baseSessionKey = buildAgentSessionKey({
|
|
65
|
+
agentId: "main", // 默认使用 main agent,可以根据配置路由
|
|
66
|
+
channel: "sumeclaw",
|
|
67
|
+
peer: message.from,
|
|
68
|
+
accountId: accountId || undefined,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// 解析线程密钥(如果有)
|
|
72
|
+
const threadKeys = message.threadId
|
|
73
|
+
? resolveThreadSessionKeys({
|
|
74
|
+
baseSessionKey,
|
|
75
|
+
threadId: String(message.threadId),
|
|
76
|
+
channel: "sumeclaw",
|
|
77
|
+
})
|
|
78
|
+
: undefined;
|
|
79
|
+
|
|
80
|
+
const sessionKey = threadKeys?.threadSessionKey || baseSessionKey;
|
|
81
|
+
|
|
82
|
+
// 构建发送者标签
|
|
83
|
+
const senderLabel = `User ${message.from}`;
|
|
84
|
+
|
|
85
|
+
// 构建消息文本
|
|
86
|
+
const messageText = message.text || "";
|
|
87
|
+
|
|
88
|
+
// 格式化 inbound envelope
|
|
89
|
+
const envelope = formatInboundEnvelope({
|
|
90
|
+
channel: "sumeclaw",
|
|
91
|
+
senderLabel,
|
|
92
|
+
text: messageText,
|
|
93
|
+
commandBody: messageText,
|
|
94
|
+
bodyForAgent: messageText,
|
|
95
|
+
timestamp: message.timestamp,
|
|
96
|
+
messageId: message.id,
|
|
97
|
+
replyToId: message.replyToId,
|
|
98
|
+
threadId: message.threadId,
|
|
99
|
+
sessionKey,
|
|
100
|
+
history: [], // 可以添加历史消息
|
|
101
|
+
media: message.mediaUrl
|
|
102
|
+
? [{ url: message.mediaUrl, type: "image" }]
|
|
103
|
+
: undefined,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
envelope,
|
|
108
|
+
sessionKey,
|
|
109
|
+
baseSessionKey,
|
|
110
|
+
threadKeys,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 处理 inbound 消息并转发到 OpenClaw
|
|
115
|
+
export async function handleSumeclawInbound(
|
|
116
|
+
context: SumeclawInboundContext,
|
|
117
|
+
) {
|
|
118
|
+
const { cfg, runtime, logger, message } = context;
|
|
119
|
+
|
|
120
|
+
logger.info("[sumeclaw] Processing inbound message", {
|
|
121
|
+
channel: "sumeclaw",
|
|
122
|
+
messageId: message.id,
|
|
123
|
+
from: message.from,
|
|
124
|
+
text: message.text?.substring(0, 100),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// 解析消息
|
|
128
|
+
const parsedMessage = parseSumeclawMessage(message);
|
|
129
|
+
if (!parsedMessage) {
|
|
130
|
+
logger.error("[sumeclaw] Failed to parse message", {
|
|
131
|
+
channel: "sumeclaw",
|
|
132
|
+
rawMessage: message,
|
|
133
|
+
});
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 构建 inbound envelope
|
|
138
|
+
const { envelope, sessionKey } = await buildSumeclawInboundEnvelope(context);
|
|
139
|
+
|
|
140
|
+
// 完成 inbound 上下文
|
|
141
|
+
const inboundContext = finalizeInboundContext({
|
|
142
|
+
cfg,
|
|
143
|
+
runtime,
|
|
144
|
+
channel: "sumeclaw",
|
|
145
|
+
sessionKey,
|
|
146
|
+
envelope,
|
|
147
|
+
source: {
|
|
148
|
+
kind: "channel",
|
|
149
|
+
channel: "sumeclaw",
|
|
150
|
+
accountId: context.accountId || undefined,
|
|
151
|
+
peer: message.from,
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// 记录 inbound 消息
|
|
156
|
+
logger.info("[sumeclaw] Inbound message ready for dispatch", {
|
|
157
|
+
channel: "sumeclaw",
|
|
158
|
+
sessionKey,
|
|
159
|
+
messageId: message.id,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// TODO: 将消息转发到 OpenClaw 的 inbound 处理系统
|
|
163
|
+
// 这需要使用 OpenClaw 的 inbound dispatch API
|
|
164
|
+
// 参考 Discord 的实现:extensions/discord/src/monitor/message-handler.ts
|
|
165
|
+
|
|
166
|
+
return inboundContext;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 消息去重缓存
|
|
170
|
+
const messageDedupeCache = new Map<string, number>();
|
|
171
|
+
|
|
172
|
+
// 检查消息是否重复
|
|
173
|
+
export function isDuplicateMessage(messageId: string, ttlMs: number = 60000): boolean {
|
|
174
|
+
const now = Date.now();
|
|
175
|
+
const lastSeen = messageDedupeCache.get(messageId);
|
|
176
|
+
|
|
177
|
+
if (lastSeen && (now - lastSeen) < ttlMs) {
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
messageDedupeCache.set(messageId, now);
|
|
182
|
+
|
|
183
|
+
// 清理过期缓存
|
|
184
|
+
for (const [id, timestamp] of messageDedupeCache.entries()) {
|
|
185
|
+
if (now - timestamp > ttlMs) {
|
|
186
|
+
messageDedupeCache.delete(id);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { ChannelOutboundAdapter } from "openclaw/plugin-sdk/channel-send-result";
|
|
2
|
+
import { createAttachedChannelResultAdapter } from "openclaw/plugin-sdk/channel-send-result";
|
|
3
|
+
import type { ResolvedAccount } from "./types.js";
|
|
4
|
+
import type { SumeclawWebSocketClient } from "./websocket-client.js";
|
|
5
|
+
|
|
6
|
+
async function sendSumeclawMessage(params: {
|
|
7
|
+
to: string;
|
|
8
|
+
text: string;
|
|
9
|
+
token: string;
|
|
10
|
+
wsUrl: string;
|
|
11
|
+
replyToId?: string | null;
|
|
12
|
+
threadId?: string | number | null;
|
|
13
|
+
wsClient?: SumeclawWebSocketClient;
|
|
14
|
+
}): Promise<{ messageId: string }> {
|
|
15
|
+
if (params.wsClient && params.wsClient.isReady()) {
|
|
16
|
+
const message = {
|
|
17
|
+
type: "message",
|
|
18
|
+
to: params.to,
|
|
19
|
+
text: params.text,
|
|
20
|
+
replyToId: params.replyToId,
|
|
21
|
+
threadId: params.threadId,
|
|
22
|
+
};
|
|
23
|
+
params.wsClient.send(message);
|
|
24
|
+
return { messageId: `msg-${Date.now()}` };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log("[sumeclaw] Sending message via HTTP fallback", {
|
|
28
|
+
to: params.to,
|
|
29
|
+
text: params.text
|
|
30
|
+
});
|
|
31
|
+
return { messageId: `msg-${Date.now()}` };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function sendSumeclawMedia(params: {
|
|
35
|
+
to: string;
|
|
36
|
+
text: string;
|
|
37
|
+
mediaUrl: string;
|
|
38
|
+
token: string;
|
|
39
|
+
wsUrl: string;
|
|
40
|
+
replyToId?: string | null;
|
|
41
|
+
threadId?: string | number | null;
|
|
42
|
+
wsClient?: SumeclawWebSocketClient;
|
|
43
|
+
}): Promise<{ messageId: string }> {
|
|
44
|
+
console.log("[sumeclaw] Sending media", { to: params, text: params.text, mediaUrl: params.mediaUrl });
|
|
45
|
+
return { messageId: `media-${Date.now()}` };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const sumeclawOutbound: ChannelOutboundAdapter = {
|
|
49
|
+
deliveryMode: "direct",
|
|
50
|
+
textChunkLimit: 4000,
|
|
51
|
+
chunkerMode: "text",
|
|
52
|
+
sanitizeText: ({ text }) => text,
|
|
53
|
+
...createAttachedChannelResultAdapter({
|
|
54
|
+
channel: "sumeclaw",
|
|
55
|
+
sendText: async ({ cfg, to, text, accountId, replyToId, threadId, runtime }) => {
|
|
56
|
+
const account = (cfg.channels as Record<string, any>)?.["sumeclaw"];
|
|
57
|
+
const wsClient = (runtime as any).sumeclawWsClient;
|
|
58
|
+
const result = await sendSumeclawMessage({
|
|
59
|
+
to,
|
|
60
|
+
text,
|
|
61
|
+
token: account?.token,
|
|
62
|
+
wsUrl: account?.wsUrl ?? "wss://api.gixin.cc",
|
|
63
|
+
replyToId,
|
|
64
|
+
threadId,
|
|
65
|
+
wsClient,
|
|
66
|
+
});
|
|
67
|
+
return { messageId: result.messageId };
|
|
68
|
+
},
|
|
69
|
+
sendMedia: async ({
|
|
70
|
+
cfg,
|
|
71
|
+
to,
|
|
72
|
+
text,
|
|
73
|
+
mediaUrl,
|
|
74
|
+
accountId,
|
|
75
|
+
replyToId,
|
|
76
|
+
threadId,
|
|
77
|
+
runtime,
|
|
78
|
+
}) => {
|
|
79
|
+
const account = (cfg.channels as Record<string, any>)?.["sumeclaw"];
|
|
80
|
+
const wsClient = (runtime as any).sumeclawWsClient;
|
|
81
|
+
const result = await sendSumeclawMedia({
|
|
82
|
+
to,
|
|
83
|
+
text,
|
|
84
|
+
mediaUrl,
|
|
85
|
+
token: account?.token,
|
|
86
|
+
wsUrl: account?.wsUrl ?? "wss://api.gixin.cc",
|
|
87
|
+
replyToId,
|
|
88
|
+
threadId,
|
|
89
|
+
wsClient,
|
|
90
|
+
});
|
|
91
|
+
return { messageId: result.messageId };
|
|
92
|
+
},
|
|
93
|
+
}),
|
|
94
|
+
};
|
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createChatChannelPlugin, createChannelPluginBase } from "openclaw/plugin-sdk/channel-core";
|
|
2
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core";
|
|
3
|
+
import { resolveAccount, type ResolvedAccount } from "./types.js";
|
|
4
|
+
import { sumeclawOutbound } from "./outbound-adapter.js";
|
|
5
|
+
|
|
6
|
+
export const sumeclawPlugin = createChatChannelPlugin<ResolvedAccount>({
|
|
7
|
+
base: createChannelPluginBase({
|
|
8
|
+
id: "sumeclaw",
|
|
9
|
+
setup: {
|
|
10
|
+
resolveAccount,
|
|
11
|
+
inspectAccount(cfg, accountId) {
|
|
12
|
+
const section = (cfg.channels as Record<string, any>)?.["sumeclaw"];
|
|
13
|
+
return {
|
|
14
|
+
enabled: Boolean(section?.token),
|
|
15
|
+
configured: Boolean(section?.token),
|
|
16
|
+
tokenStatus: section?.token ? "available" : "missing",
|
|
17
|
+
};
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
}),
|
|
21
|
+
security: {
|
|
22
|
+
dm: {
|
|
23
|
+
channelKey: "sumeclaw",
|
|
24
|
+
resolvePolicy: (account) => account.dmPolicy,
|
|
25
|
+
resolveAllowFrom: (account) => account.allowFrom,
|
|
26
|
+
defaultPolicy: "allowlist",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
threading: { topLevelReplyToMode: "reply" },
|
|
30
|
+
outbound: sumeclawOutbound,
|
|
31
|
+
});
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
|
2
|
+
|
|
3
|
+
export type ResolvedAccount = {
|
|
4
|
+
accountId: string | null;
|
|
5
|
+
token: string;
|
|
6
|
+
wsUrl: string;
|
|
7
|
+
allowFrom: string[];
|
|
8
|
+
dmPolicy: string | undefined;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function resolveAccount(
|
|
12
|
+
cfg: OpenClawConfig,
|
|
13
|
+
accountId?: string | null,
|
|
14
|
+
): ResolvedAccount {
|
|
15
|
+
const section = (cfg.channels as Record<string, any>)?.["sumeclaw"];
|
|
16
|
+
const token = section?.token;
|
|
17
|
+
const wsUrl = section?.wsUrl ?? "wss://api.gixin.cc";
|
|
18
|
+
if (!token) throw new Error("sumeclaw: token is required");
|
|
19
|
+
return {
|
|
20
|
+
accountId: accountId ?? null,
|
|
21
|
+
token,
|
|
22
|
+
wsUrl,
|
|
23
|
+
allowFrom: section?.allowFrom ?? [],
|
|
24
|
+
dmPolicy: section?.dmSecurity,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { captureWsEvent } from "openclaw/plugin-sdk/proxy-capture";
|
|
2
|
+
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
|
3
|
+
import * as ws from "ws";
|
|
4
|
+
|
|
5
|
+
import type { ResolvedAccount } from "./types.js";
|
|
6
|
+
|
|
7
|
+
export class SumeclawWebSocketClient {
|
|
8
|
+
private ws: ws.WebSocket | null = null;
|
|
9
|
+
private reconnectTimer: NodeJS.Timeout | null = null;
|
|
10
|
+
private logger: any;
|
|
11
|
+
private runtime: RuntimeEnv;
|
|
12
|
+
private account: ResolvedAccount;
|
|
13
|
+
private onMessageCallback?: (data: any) => void;
|
|
14
|
+
private isConnected: boolean = false;
|
|
15
|
+
|
|
16
|
+
constructor(account: ResolvedAccount, runtime: RuntimeEnv, logger: any) {
|
|
17
|
+
this.account = account;
|
|
18
|
+
this.runtime = runtime;
|
|
19
|
+
this.logger = logger;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
connect(onMessage?: (data: any) => void) {
|
|
23
|
+
this.onMessageCallback = onMessage;
|
|
24
|
+
this.logger.info("[sumeclaw] Connecting to WebSocket", {
|
|
25
|
+
channel: "sumeclaw",
|
|
26
|
+
wsUrl: this.account.wsUrl,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
this.ws = new ws.WebSocket(this.account.wsUrl, {
|
|
31
|
+
handshakeTimeout: 30000,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const wsFlowId = `${Date.now()}-${Math.random()}`;
|
|
35
|
+
|
|
36
|
+
this.ws.on("open", () => {
|
|
37
|
+
this.isConnected = true;
|
|
38
|
+
this.logger.info("[sumeclaw] WebSocket connected", {
|
|
39
|
+
channel: "sumeclaw",
|
|
40
|
+
});
|
|
41
|
+
captureWsEvent({
|
|
42
|
+
url: this.account.wsUrl,
|
|
43
|
+
direction: "local",
|
|
44
|
+
kind: "ws-open",
|
|
45
|
+
flowId: wsFlowId,
|
|
46
|
+
meta: { subsystem: "sumeclaw-gateway" },
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
this.sendAuth();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
this.ws.on("message", (data: ws.Data) => {
|
|
53
|
+
captureWsEvent({
|
|
54
|
+
url: this.account.wsUrl,
|
|
55
|
+
direction: "inbound",
|
|
56
|
+
kind: "ws-frame",
|
|
57
|
+
flowId: wsFlowId,
|
|
58
|
+
payload: Buffer.isBuffer(data) ? data : Buffer.from(String(data)),
|
|
59
|
+
meta: { subsystem: "sumeclaw-gateway" },
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (this.onMessageCallback) {
|
|
63
|
+
try {
|
|
64
|
+
const message = JSON.parse(data.toString());
|
|
65
|
+
this.onMessageCallback(message);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
this.logger.error("[sumeclaw] Failed to parse message", {
|
|
68
|
+
channel: "sumeclaw",
|
|
69
|
+
error: String(error),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
this.ws.on("close", (code: number, reason: Buffer) => {
|
|
76
|
+
this.isConnected = false;
|
|
77
|
+
this.logger.warn("[sumeclaw] WebSocket disconnected", {
|
|
78
|
+
channel: "sumeclaw",
|
|
79
|
+
code,
|
|
80
|
+
reason: reason.toString(),
|
|
81
|
+
});
|
|
82
|
+
captureWsEvent({
|
|
83
|
+
url: this.account.wsUrl,
|
|
84
|
+
direction: "local",
|
|
85
|
+
kind: "ws-close",
|
|
86
|
+
flowId: wsFlowId,
|
|
87
|
+
closeCode: code,
|
|
88
|
+
payload: reason,
|
|
89
|
+
meta: { subsystem: "sumeclaw-gateway" },
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
this.scheduleReconnect();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
this.ws.on("error", (error: Error) => {
|
|
96
|
+
this.logger.error("[sumeclaw] WebSocket error", {
|
|
97
|
+
channel: "sumeclaw",
|
|
98
|
+
error: error.message,
|
|
99
|
+
});
|
|
100
|
+
captureWsEvent({
|
|
101
|
+
url: this.account.wsUrl,
|
|
102
|
+
direction: "local",
|
|
103
|
+
kind: "error",
|
|
104
|
+
flowId: wsFlowId,
|
|
105
|
+
errorText: error.message,
|
|
106
|
+
meta: { subsystem: "sumeclaw-gateway" },
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
} catch (error) {
|
|
110
|
+
this.logger.error("[sumeclaw] Failed to create WebSocket", {
|
|
111
|
+
channel: "sumeclaw",
|
|
112
|
+
error: String(error),
|
|
113
|
+
});
|
|
114
|
+
this.scheduleReconnect();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private sendAuth() {
|
|
119
|
+
if (this.ws && this.ws.readyState === ws.WebSocket.OPEN) {
|
|
120
|
+
const authMessage = {
|
|
121
|
+
type: "auth",
|
|
122
|
+
token: this.account.token,
|
|
123
|
+
};
|
|
124
|
+
this.ws.send(JSON.stringify(authMessage));
|
|
125
|
+
this.logger.info("[sumeclaw] Auth message sent", { channel: "sumeclaw" });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
send(data: any) {
|
|
130
|
+
if (this.ws && this.ws.readyState === ws.WebSocket.OPEN) {
|
|
131
|
+
this.ws.send(JSON.stringify(data));
|
|
132
|
+
} else {
|
|
133
|
+
this.logger.warn("[sumeclaw] Cannot send message, WebSocket not connected", {
|
|
134
|
+
channel: "sumeclaw",
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private scheduleReconnect() {
|
|
140
|
+
if (this.reconnectTimer) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const delay = 5000;
|
|
145
|
+
this.logger.info("[sumeclaw] Scheduling reconnect", {
|
|
146
|
+
channel: "sumeclaw",
|
|
147
|
+
delay,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
this.reconnectTimer = setTimeout(() => {
|
|
151
|
+
this.reconnectTimer = null;
|
|
152
|
+
this.connect(this.onMessageCallback);
|
|
153
|
+
}, delay);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
disconnect() {
|
|
157
|
+
if (this.reconnectTimer) {
|
|
158
|
+
clearTimeout(this.reconnectTimer);
|
|
159
|
+
this.reconnectTimer = null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (this.ws) {
|
|
163
|
+
this.ws.close();
|
|
164
|
+
this.ws = null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
this.isConnected = false;
|
|
168
|
+
this.logger.info("[sumeclaw] WebSocket disconnected", { channel: "sumeclaw" });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
isReady(): boolean {
|
|
172
|
+
return this.isConnected && this.ws?.readyState === ws.WebSocket.OPEN;
|
|
173
|
+
}
|
|
174
|
+
}
|
package/dist/index.d.ts
DELETED
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAGA;;GAEG;;;;;;iBAYkB,GAAG;;AARxB,wBAiDE"}
|
package/dist/index.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* @sumeai/sumeclaw — 稳定版(强制 channel 识别)
|
|
4
|
-
*/
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const gateway_js_1 = require("./src/gateway.js");
|
|
7
|
-
exports.default = {
|
|
8
|
-
// 🔥 核心:手动声明 channel 类型(解决 missing id)
|
|
9
|
-
type: "channel",
|
|
10
|
-
id: "sumeclaw",
|
|
11
|
-
name: "友虾名片",
|
|
12
|
-
description: "友虾名片 Channel",
|
|
13
|
-
async onReady(ctx) {
|
|
14
|
-
const { api, cfg } = ctx;
|
|
15
|
-
console.log("[sumeclaw] 🚀 插件启动");
|
|
16
|
-
// 🔍 调试用(可以先保留)
|
|
17
|
-
console.log("[sumeclaw] cfg =", JSON.stringify(cfg, null, 2));
|
|
18
|
-
// ✅ 兼容不同 OpenClaw 版本
|
|
19
|
-
const accounts = cfg?.channels?.sumeclaw?.accounts ||
|
|
20
|
-
cfg?.channel?.accounts || // ⚠️ 有些版本是扁平的
|
|
21
|
-
{};
|
|
22
|
-
if (!accounts || Object.keys(accounts).length === 0) {
|
|
23
|
-
console.warn("[sumeclaw] ⚠️ 没有配置 accounts");
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
for (const accountId of Object.keys(accounts)) {
|
|
27
|
-
const account = accounts[accountId];
|
|
28
|
-
if (!account?.registrationToken) {
|
|
29
|
-
console.error(`[sumeclaw] ❌ account ${accountId} 缺少 registrationToken`);
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
console.log(`[sumeclaw] 🔗 正在连接 account: ${accountId}`);
|
|
33
|
-
try {
|
|
34
|
-
(0, gateway_js_1.connectGateway)({
|
|
35
|
-
accountId,
|
|
36
|
-
platformUrl: account.platformUrl,
|
|
37
|
-
registrationToken: account.registrationToken,
|
|
38
|
-
api
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
catch (err) {
|
|
42
|
-
console.error("[sumeclaw] ❌ 连接失败:", err);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";AAGA;;GAEG;;AAEH,iDAAkD;AAElD,kBAAe;IACb,uCAAuC;IACvC,IAAI,EAAE,SAAS;IAEf,EAAE,EAAE,UAAU;IACd,IAAI,EAAE,MAAM;IACZ,WAAW,EAAE,cAAc;IAE3B,KAAK,CAAC,OAAO,CAAC,GAAQ;QACpB,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC;QAEzB,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;QAElC,gBAAgB;QAChB,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAE9D,qBAAqB;QACrB,MAAM,QAAQ,GACZ,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ;YACjC,GAAG,EAAE,OAAO,EAAE,QAAQ,IAAM,cAAc;YAC1C,EAAE,CAAC;QAEL,IAAI,CAAC,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpD,OAAO,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;YAC5C,OAAO;QACT,CAAC;QAED,KAAK,MAAM,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC9C,MAAM,OAAO,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;YAEpC,IAAI,CAAC,OAAO,EAAE,iBAAiB,EAAE,CAAC;gBAChC,OAAO,CAAC,KAAK,CAAC,wBAAwB,SAAS,uBAAuB,CAAC,CAAC;gBACxE,SAAS;YACX,CAAC;YAED,OAAO,CAAC,GAAG,CAAC,+BAA+B,SAAS,EAAE,CAAC,CAAC;YAExD,IAAI,CAAC;gBACH,IAAA,2BAAc,EAAC;oBACb,SAAS;oBACT,WAAW,EAAE,OAAO,CAAC,WAAW;oBAChC,iBAAiB,EAAE,OAAO,CAAC,iBAAiB;oBAC5C,GAAG;iBACJ,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,oBAAoB,EAAE,GAAG,CAAC,CAAC;YAC3C,CAAC;QACH,CAAC;IACH,CAAC;CACF,CAAC"}
|
package/dist/setup-entry.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"setup-entry.d.ts","sourceRoot":"","sources":["../setup-entry.ts"],"names":[],"mappings":";AASA,wBAAsD"}
|
package/dist/setup-entry.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
/**
|
|
4
|
-
* 友虾名片 Channel 插件 — 轻量设置入口
|
|
5
|
-
*
|
|
6
|
-
* 此入口在 openclaw plugins install 阶段加载,
|
|
7
|
-
* 用于展示配置向导和验证 Token 有效性。
|
|
8
|
-
*/
|
|
9
|
-
const channel_core_1 = require("openclaw/plugin-sdk/channel-core");
|
|
10
|
-
const channel_js_1 = require("./src/channel.js");
|
|
11
|
-
exports.default = (0, channel_core_1.defineSetupPluginEntry)(channel_js_1.sumeclawPlugin);
|
|
12
|
-
//# sourceMappingURL=setup-entry.js.map
|
package/dist/setup-entry.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"setup-entry.js","sourceRoot":"","sources":["../setup-entry.ts"],"names":[],"mappings":";;AAAA;;;;;GAKG;AACH,mEAA0E;AAC1E,iDAAkD;AAElD,kBAAe,IAAA,qCAAsB,EAAC,2BAAc,CAAC,CAAC"}
|
package/dist/src/channel.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../src/channel.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,cAAc;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AA4CD,eAAO,MAAM,cAAc,KAkFzB,CAAC"}
|
package/dist/src/channel.js
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.sumeclawPlugin = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* 友虾名片 ChannelPlugin 核心定义
|
|
6
|
-
*
|
|
7
|
-
* 基于新版 plugin-sdk(2026.3.22+)的 createChatChannelPlugin API。
|
|
8
|
-
*/
|
|
9
|
-
const channel_core_1 = require("openclaw/plugin-sdk/channel-core");
|
|
10
|
-
const gateway_js_1 = require("./gateway.js");
|
|
11
|
-
/**
|
|
12
|
-
* 安装/配置向导 — 交互式提示用户输入 registrationToken
|
|
13
|
-
*/
|
|
14
|
-
const channelKey = "sumeclaw";
|
|
15
|
-
const setupWizard = {
|
|
16
|
-
channel: channelKey,
|
|
17
|
-
/** 根据当前配置显示连接状态 */
|
|
18
|
-
status: {
|
|
19
|
-
configuredLabel: "已连接友虾名片",
|
|
20
|
-
unconfiguredLabel: "尚未配置",
|
|
21
|
-
resolveConfigured: ({ cfg }) => {
|
|
22
|
-
const accounts = cfg.channels?.[channelKey]?.accounts ?? {};
|
|
23
|
-
return Object.values(accounts).some((acct) => acct?.registrationToken);
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
/** 交互式凭据收集 — 引导用户输入 registrationToken */
|
|
27
|
-
credentials: [
|
|
28
|
-
{
|
|
29
|
-
inputKey: "registrationToken",
|
|
30
|
-
credentialLabel: "友虾名片注册令牌",
|
|
31
|
-
preferredEnvVar: "SUMECLAW_REGISTRATION_TOKEN",
|
|
32
|
-
envPrompt: "是否使用环境变量 SUMECLAW_REGISTRATION_TOKEN?",
|
|
33
|
-
keepPrompt: "保留当前 registrationToken?",
|
|
34
|
-
inputPrompt: "请输入从友虾小程序获取的 registrationToken:",
|
|
35
|
-
inspect: ({ cfg, accountId }) => {
|
|
36
|
-
const token = cfg.channels?.[channelKey]?.accounts?.[accountId ?? "default"]?.registrationToken;
|
|
37
|
-
return {
|
|
38
|
-
accountConfigured: Boolean(token),
|
|
39
|
-
hasConfiguredValue: Boolean(token),
|
|
40
|
-
};
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
],
|
|
44
|
-
};
|
|
45
|
-
exports.sumeclawPlugin = (0, channel_core_1.createChatChannelPlugin)({
|
|
46
|
-
base: (0, channel_core_1.createChannelPluginBase)({
|
|
47
|
-
id: channelKey,
|
|
48
|
-
setupWizard,
|
|
49
|
-
setup: {
|
|
50
|
-
/**
|
|
51
|
-
* 从用户配置中解析账户信息
|
|
52
|
-
*/
|
|
53
|
-
resolveAccount(cfg, accountId) {
|
|
54
|
-
const accounts = cfg.channels?.[channelKey]?.accounts ?? {};
|
|
55
|
-
return (accounts[accountId ?? "default"] ?? {});
|
|
56
|
-
},
|
|
57
|
-
/**
|
|
58
|
-
* 检查账户配置状态
|
|
59
|
-
*/
|
|
60
|
-
inspectAccount(cfg, accountId) {
|
|
61
|
-
const account = cfg.channels?.[channelKey]?.accounts?.[accountId ?? "default"];
|
|
62
|
-
return {
|
|
63
|
-
enabled: !!account?.registrationToken,
|
|
64
|
-
configured: !!account?.registrationToken,
|
|
65
|
-
};
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
}),
|
|
69
|
-
/**
|
|
70
|
-
* 渠道配置适配器 (ChannelConfigAdapter)
|
|
71
|
-
*/
|
|
72
|
-
config: {
|
|
73
|
-
listAccountIds(cfg) {
|
|
74
|
-
const accounts = cfg.channels?.[channelKey]?.accounts ?? {};
|
|
75
|
-
return Object.keys(accounts);
|
|
76
|
-
},
|
|
77
|
-
resolveAccount(cfg, accountId) {
|
|
78
|
-
const accounts = cfg.channels?.[channelKey]?.accounts ?? {};
|
|
79
|
-
return (accounts[accountId ?? "default"] ?? {});
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
/**
|
|
83
|
-
* DM 安全策略
|
|
84
|
-
*
|
|
85
|
-
* 友虾名片场景下,所有通过名片访问的用户都允许对话。
|
|
86
|
-
* 名片主可在小程序端管理黑名单(后续扩展)。
|
|
87
|
-
*/
|
|
88
|
-
security: {
|
|
89
|
-
dm: {
|
|
90
|
-
channelKey: "sumeclaw",
|
|
91
|
-
resolvePolicy: () => "open",
|
|
92
|
-
defaultPolicy: "open",
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
/**
|
|
96
|
-
* 出站消息处理
|
|
97
|
-
*
|
|
98
|
-
* 当 OpenClaw Agent 生成回复后,通过 WebSocket 推回友虾名片平台。
|
|
99
|
-
*/
|
|
100
|
-
outbound: {
|
|
101
|
-
attachedResults: {
|
|
102
|
-
sendText: async ({ text, to, accountId }) => {
|
|
103
|
-
const ws = (0, gateway_js_1.getWebSocket)(accountId ?? "default");
|
|
104
|
-
if (!ws || ws.readyState !== 1) {
|
|
105
|
-
throw new Error("WebSocket 未连接到友虾名片平台");
|
|
106
|
-
}
|
|
107
|
-
ws.send(JSON.stringify({
|
|
108
|
-
type: "agent_reply",
|
|
109
|
-
userId: to,
|
|
110
|
-
text,
|
|
111
|
-
}));
|
|
112
|
-
return {};
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
});
|
|
117
|
-
//# sourceMappingURL=channel.js.map
|
package/dist/src/channel.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"channel.js","sourceRoot":"","sources":["../../src/channel.ts"],"names":[],"mappings":"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"}
|
package/dist/src/gateway.d.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 友虾名片 OpenClaw Channel 插件 — Gateway 层
|
|
3
|
-
*
|
|
4
|
-
* WebSocket 连接管理:
|
|
5
|
-
* - 出站连接到 wss://api.gixin.cc/api/channel/{registrationToken}
|
|
6
|
-
* - 心跳保活(30s 间隔)
|
|
7
|
-
* - 断线自动重连(指数退避,最大 60s)
|
|
8
|
-
* - 平台下发的消息注入 OpenClaw Agent
|
|
9
|
-
* - Agent 回复通过 WS 推回平台
|
|
10
|
-
*/
|
|
11
|
-
import WebSocket from "ws";
|
|
12
|
-
export declare function getWebSocket(accountId: string): WebSocket | undefined;
|
|
13
|
-
export interface MinicardAccount {
|
|
14
|
-
registrationToken?: string;
|
|
15
|
-
platformUrl?: string;
|
|
16
|
-
enabled?: boolean;
|
|
17
|
-
}
|
|
18
|
-
type ConnectOptions = {
|
|
19
|
-
accountId: string;
|
|
20
|
-
platformUrl: string;
|
|
21
|
-
registrationToken: string;
|
|
22
|
-
api: any;
|
|
23
|
-
};
|
|
24
|
-
export declare function connectGateway(opts: ConnectOptions): void;
|
|
25
|
-
export {};
|
|
26
|
-
//# sourceMappingURL=gateway.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../../src/gateway.ts"],"names":[],"mappings":"AACA;;;;;;;;;GASG;AAEH,OAAO,SAAS,MAAM,IAAI,CAAC;AA4B3B,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAErE;AAID,MAAM,WAAW,eAAe;IAC9B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAKD,KAAK,cAAc,GAAG;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,GAAG,EAAE,GAAG,CAAC;CACV,CAAC;AAEF,wBAAgB,cAAc,CAAC,IAAI,EAAE,cAAc,QA0GlD"}
|
package/dist/src/gateway.js
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* 友虾名片 OpenClaw Channel 插件 — Gateway 层
|
|
4
|
-
*
|
|
5
|
-
* WebSocket 连接管理:
|
|
6
|
-
* - 出站连接到 wss://api.gixin.cc/api/channel/{registrationToken}
|
|
7
|
-
* - 心跳保活(30s 间隔)
|
|
8
|
-
* - 断线自动重连(指数退避,最大 60s)
|
|
9
|
-
* - 平台下发的消息注入 OpenClaw Agent
|
|
10
|
-
* - Agent 回复通过 WS 推回平台
|
|
11
|
-
*/
|
|
12
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
13
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
14
|
-
};
|
|
15
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.getWebSocket = getWebSocket;
|
|
17
|
-
exports.connectGateway = connectGateway;
|
|
18
|
-
const ws_1 = __importDefault(require("ws"));
|
|
19
|
-
/**
|
|
20
|
-
* 全局 WS 管理
|
|
21
|
-
*/
|
|
22
|
-
const wsMap = {};
|
|
23
|
-
// const __filename = fileURLToPath(import.meta.url);
|
|
24
|
-
// const __dirname = dirname(__filename);
|
|
25
|
-
// const pkg = JSON.parse(readFileSync(join(__dirname, "..", "..", "package.json"), "utf-8"));
|
|
26
|
-
// ─── 配置常量 ────────────────────────────────────────────────────────────────
|
|
27
|
-
const DEFAULT_PLATFORM_URL = "wss://api.gixin.cc";
|
|
28
|
-
const HEARTBEAT_MS = 30_000;
|
|
29
|
-
const MAX_RECONNECT_MS = 60_000;
|
|
30
|
-
const BASE_BACKOFF_MS = 1_000;
|
|
31
|
-
// ─── 连接池 ──────────────────────────────────────────────────────────────────
|
|
32
|
-
/** key: accountId → WebSocket */
|
|
33
|
-
const connections = new Map();
|
|
34
|
-
function getWebSocket(accountId) {
|
|
35
|
-
return connections.get(accountId);
|
|
36
|
-
}
|
|
37
|
-
function connectGateway(opts) {
|
|
38
|
-
const { accountId, platformUrl, registrationToken, api } = opts;
|
|
39
|
-
let ws = null;
|
|
40
|
-
let heartbeatTimer = null;
|
|
41
|
-
let reconnectTimer = null;
|
|
42
|
-
const HEARTBEAT_INTERVAL = 30_000;
|
|
43
|
-
const RECONNECT_DELAY = 5_000;
|
|
44
|
-
function start() {
|
|
45
|
-
console.log(`[sumeclaw] 🌐 connecting -> ${platformUrl} (${accountId})`);
|
|
46
|
-
ws = new ws_1.default(platformUrl);
|
|
47
|
-
ws.on("open", () => {
|
|
48
|
-
console.log(`[sumeclaw] ✅ connected: ${accountId}`);
|
|
49
|
-
// 🔐 注册
|
|
50
|
-
ws?.send(JSON.stringify({
|
|
51
|
-
type: "register",
|
|
52
|
-
token: registrationToken
|
|
53
|
-
}));
|
|
54
|
-
// ❤️ 心跳
|
|
55
|
-
heartbeatTimer = setInterval(() => {
|
|
56
|
-
if (ws?.readyState === ws_1.default.OPEN) {
|
|
57
|
-
ws.send(JSON.stringify({ type: "ping" }));
|
|
58
|
-
}
|
|
59
|
-
}, HEARTBEAT_INTERVAL);
|
|
60
|
-
});
|
|
61
|
-
ws.on("message", (data) => {
|
|
62
|
-
try {
|
|
63
|
-
const msg = JSON.parse(data.toString());
|
|
64
|
-
// 🔍 调试
|
|
65
|
-
console.log("[sumeclaw] 📩 message:", msg);
|
|
66
|
-
handleMessage(msg);
|
|
67
|
-
}
|
|
68
|
-
catch (err) {
|
|
69
|
-
console.error("[sumeclaw] ❌ parse message error", err);
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
ws.on("close", () => {
|
|
73
|
-
console.warn(`[sumeclaw] ⚠️ disconnected: ${accountId}`);
|
|
74
|
-
cleanup();
|
|
75
|
-
scheduleReconnect();
|
|
76
|
-
});
|
|
77
|
-
ws.on("error", (err) => {
|
|
78
|
-
console.error(`[sumeclaw] ❌ ws error: ${accountId}`, err);
|
|
79
|
-
ws?.close();
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
function handleMessage(msg) {
|
|
83
|
-
switch (msg.type) {
|
|
84
|
-
case "pong":
|
|
85
|
-
// 心跳回应
|
|
86
|
-
break;
|
|
87
|
-
case "message":
|
|
88
|
-
// 👉 这里接入 OpenClaw
|
|
89
|
-
console.log("[sumeclaw] 💬 收到消息:", msg);
|
|
90
|
-
// 示例:推给 agent
|
|
91
|
-
api?.emit?.("message", {
|
|
92
|
-
accountId,
|
|
93
|
-
text: msg.text,
|
|
94
|
-
raw: msg
|
|
95
|
-
});
|
|
96
|
-
break;
|
|
97
|
-
case "registered":
|
|
98
|
-
console.log(`[sumeclaw] 🔐 注册成功: ${accountId}`);
|
|
99
|
-
break;
|
|
100
|
-
default:
|
|
101
|
-
console.log("[sumeclaw] ❓ 未处理消息:", msg);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
function cleanup() {
|
|
105
|
-
if (heartbeatTimer) {
|
|
106
|
-
clearInterval(heartbeatTimer);
|
|
107
|
-
heartbeatTimer = null;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
function scheduleReconnect() {
|
|
111
|
-
if (reconnectTimer)
|
|
112
|
-
return;
|
|
113
|
-
console.log(`[sumeclaw] 🔁 reconnecting in ${RECONNECT_DELAY / 1000}s`);
|
|
114
|
-
reconnectTimer = setTimeout(() => {
|
|
115
|
-
reconnectTimer = null;
|
|
116
|
-
start();
|
|
117
|
-
}, RECONNECT_DELAY);
|
|
118
|
-
}
|
|
119
|
-
start();
|
|
120
|
-
}
|
|
121
|
-
//# sourceMappingURL=gateway.js.map
|
package/dist/src/gateway.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"gateway.js","sourceRoot":"","sources":["../../src/gateway.ts"],"names":[],"mappings":"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"}
|