@workclaw/openclaw-workclaw 1.0.18 → 1.0.19
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 +364 -345
- package/dist/api.d.ts +3 -0
- package/dist/api.js +3 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +263 -0
- package/dist/setup-entry.d.ts +2 -0
- package/dist/setup-entry.js +12 -0
- package/dist/src/accounts.d.ts +66 -0
- package/dist/src/accounts.js +279 -0
- package/dist/src/api/accounts-api.d.ts +2 -0
- package/dist/src/api/accounts-api.js +130 -0
- package/dist/src/api/prompts-api.d.ts +2 -0
- package/dist/src/api/prompts-api.js +103 -0
- package/dist/src/api/session-api.d.ts +2 -0
- package/dist/src/api/session-api.js +207 -0
- package/dist/src/api/skills-api.d.ts +2 -0
- package/dist/src/api/skills-api.js +64 -0
- package/dist/src/api/workspace.d.ts +3 -0
- package/dist/src/api/workspace.js +28 -0
- package/dist/src/channel.d.ts +11 -0
- package/{src/channel.ts → dist/src/channel.js} +198 -225
- package/dist/src/config-schema.d.ts +93 -0
- package/dist/src/config-schema.js +56 -0
- package/dist/src/connection/workclaw-client.d.ts +147 -0
- package/dist/src/connection/workclaw-client.js +351 -0
- package/dist/src/gateway/agent-handlers.d.ts +12 -0
- package/{src/gateway/agent-handlers.ts → dist/src/gateway/agent-handlers.js} +433 -523
- package/dist/src/gateway/config-writer.d.ts +71 -0
- package/dist/src/gateway/config-writer.js +312 -0
- package/dist/src/gateway/message-context.d.ts +76 -0
- package/{src/gateway/message-context.ts → dist/src/gateway/message-context.js} +499 -594
- package/dist/src/gateway/message-dispatcher.d.ts +19 -0
- package/{src/gateway/message-dispatcher.ts → dist/src/gateway/message-dispatcher.js} +512 -641
- package/dist/src/gateway/reconnect.d.ts +27 -0
- package/{src/gateway/reconnect.ts → dist/src/gateway/reconnect.js} +206 -253
- package/dist/src/gateway/skills-handler.d.ts +29 -0
- package/dist/src/gateway/skills-handler.js +576 -0
- package/dist/src/gateway/skills-list-handler.d.ts +27 -0
- package/dist/src/gateway/skills-list-handler.js +233 -0
- package/dist/src/gateway/tools-list-handler.d.ts +30 -0
- package/dist/src/gateway/tools-list-handler.js +101 -0
- package/dist/src/gateway/workclaw-gateway.d.ts +14 -0
- package/dist/src/gateway/workclaw-gateway.js +223 -0
- package/dist/src/media/upload.d.ts +13 -0
- package/dist/src/media/upload.js +125 -0
- package/dist/src/outbound/index.d.ts +36 -0
- package/dist/src/outbound/index.js +123 -0
- package/dist/src/outbound/workclaw-sender.d.ts +36 -0
- package/{src/outbound/workclaw-sender.ts → dist/src/outbound/workclaw-sender.js} +95 -158
- package/dist/src/runtime.d.ts +116 -0
- package/dist/src/runtime.js +374 -0
- package/dist/src/secret-contract-api.d.ts +4 -0
- package/dist/src/secret-contract-api.js +4 -0
- package/{src/send.ts → dist/src/send.d.ts} +1 -1
- package/dist/src/send.js +1 -0
- package/dist/src/setup-api.d.ts +3 -0
- package/dist/src/setup-api.js +3 -0
- package/dist/src/setup-core.d.ts +3 -0
- package/dist/src/setup-core.js +13 -0
- package/dist/src/setup-surface.d.ts +7 -0
- package/dist/src/setup-surface.js +363 -0
- package/dist/src/tools/openclaw-workclaw-cron/api/index.d.ts +93 -0
- package/dist/src/tools/openclaw-workclaw-cron/api/index.js +209 -0
- package/dist/src/tools/openclaw-workclaw-cron/index.d.ts +10 -0
- package/{src/tools/openclaw-workclaw-cron/index.ts → dist/src/tools/openclaw-workclaw-cron/index.js} +33 -39
- package/dist/src/tools/openclaw-workclaw-cron/src/add/params.d.ts +16 -0
- package/dist/src/tools/openclaw-workclaw-cron/src/add/params.js +152 -0
- package/dist/src/tools/openclaw-workclaw-cron/src/add/sync.d.ts +18 -0
- package/dist/src/tools/openclaw-workclaw-cron/src/add/sync.js +162 -0
- package/dist/src/tools/openclaw-workclaw-cron/src/disable/params.d.ts +14 -0
- package/dist/src/tools/openclaw-workclaw-cron/src/disable/params.js +80 -0
- package/dist/src/tools/openclaw-workclaw-cron/src/disable/sync.d.ts +14 -0
- package/dist/src/tools/openclaw-workclaw-cron/src/disable/sync.js +107 -0
- package/dist/src/tools/openclaw-workclaw-cron/src/enable/params.d.ts +14 -0
- package/dist/src/tools/openclaw-workclaw-cron/src/enable/params.js +80 -0
- package/dist/src/tools/openclaw-workclaw-cron/src/enable/sync.d.ts +14 -0
- package/dist/src/tools/openclaw-workclaw-cron/src/enable/sync.js +107 -0
- package/dist/src/tools/openclaw-workclaw-cron/src/notify/sync.d.ts +17 -0
- package/dist/src/tools/openclaw-workclaw-cron/src/notify/sync.js +127 -0
- package/dist/src/tools/openclaw-workclaw-cron/src/remove/params.d.ts +14 -0
- package/dist/src/tools/openclaw-workclaw-cron/src/remove/params.js +87 -0
- package/dist/src/tools/openclaw-workclaw-cron/src/remove/sync.d.ts +14 -0
- package/dist/src/tools/openclaw-workclaw-cron/src/remove/sync.js +107 -0
- package/dist/src/tools/openclaw-workclaw-cron/src/update/params.d.ts +17 -0
- package/dist/src/tools/openclaw-workclaw-cron/src/update/params.js +164 -0
- package/dist/src/tools/openclaw-workclaw-cron/src/update/sync.d.ts +18 -0
- package/dist/src/tools/openclaw-workclaw-cron/src/update/sync.js +135 -0
- package/dist/src/tools/openclaw-workclaw-cron/types/index.d.ts +52 -0
- package/dist/src/tools/openclaw-workclaw-cron/types/index.js +4 -0
- package/dist/src/tools/openclaw-workclaw-cron/utils/index.d.ts +40 -0
- package/dist/src/tools/openclaw-workclaw-cron/utils/index.js +122 -0
- package/dist/src/tools/openclaw-workclaw-system/index.d.ts +10 -0
- package/{src/tools/openclaw-workclaw-system/index.ts → dist/src/tools/openclaw-workclaw-system/index.js} +15 -17
- package/dist/src/tools/openclaw-workclaw-system/src/get/index.d.ts +8 -0
- package/dist/src/tools/openclaw-workclaw-system/src/get/index.js +64 -0
- package/dist/src/tools/openclaw-workclaw-system/src/token/index.d.ts +8 -0
- package/dist/src/tools/openclaw-workclaw-system/src/token/index.js +78 -0
- package/{src/types.ts → dist/src/types.d.ts} +39 -58
- package/dist/src/types.js +1 -0
- package/dist/src/utils/content.d.ts +15 -0
- package/{src/utils/content.ts → dist/src/utils/content.js} +38 -35
- package/package.json +62 -49
- package/index.ts +0 -302
- package/setup-entry.ts +0 -6
- package/src/accounts.ts +0 -312
- package/src/api/accounts-api.ts +0 -156
- package/src/api/prompts-api.ts +0 -116
- package/src/api/session-api.ts +0 -238
- package/src/api/skills-api.ts +0 -72
- package/src/api/workspace.ts +0 -41
- package/src/config-schema.ts +0 -110
- package/src/connection/workclaw-client.ts +0 -643
- package/src/gateway/config-writer.ts +0 -296
- package/src/gateway/skills-handler.ts +0 -741
- package/src/gateway/skills-list-handler.ts +0 -332
- package/src/gateway/tools-list-handler.ts +0 -160
- package/src/gateway/workclaw-gateway.ts +0 -367
- package/src/media/upload.ts +0 -157
- package/src/outbound/index.ts +0 -185
- package/src/runtime.ts +0 -497
- package/src/tools/openclaw-workclaw-cron/api/index.ts +0 -326
- package/src/tools/openclaw-workclaw-cron/src/add/params.ts +0 -177
- package/src/tools/openclaw-workclaw-cron/src/add/sync.ts +0 -188
- package/src/tools/openclaw-workclaw-cron/src/disable/params.ts +0 -100
- package/src/tools/openclaw-workclaw-cron/src/disable/sync.ts +0 -127
- package/src/tools/openclaw-workclaw-cron/src/enable/params.ts +0 -100
- package/src/tools/openclaw-workclaw-cron/src/enable/sync.ts +0 -127
- package/src/tools/openclaw-workclaw-cron/src/notify/sync.ts +0 -148
- package/src/tools/openclaw-workclaw-cron/src/remove/params.ts +0 -109
- package/src/tools/openclaw-workclaw-cron/src/remove/sync.ts +0 -127
- package/src/tools/openclaw-workclaw-cron/src/update/params.ts +0 -195
- package/src/tools/openclaw-workclaw-cron/src/update/sync.ts +0 -161
- package/src/tools/openclaw-workclaw-cron/types/index.ts +0 -55
- package/src/tools/openclaw-workclaw-cron/utils/index.ts +0 -141
- package/src/tools/openclaw-workclaw-system/src/get/index.ts +0 -77
- package/src/tools/openclaw-workclaw-system/src/token/index.ts +0 -93
- package/tests/accounts.test.ts +0 -285
- package/tests/message-context.test.ts +0 -313
- package/tests/reconnect.test.ts +0 -257
- package/tests/workclaw-client.test.ts +0 -112
- package/tsconfig.json +0 -14
- package/vitest.config.ts +0 -8
|
@@ -1,367 +0,0 @@
|
|
|
1
|
-
import type { ResolvedWorkclawAccount, WorkclawConfig, WorkclawAccountConfig, WorkClawBaseConfig } from "../types.js";
|
|
2
|
-
import {
|
|
3
|
-
getWorkclawWsConnection,
|
|
4
|
-
clearWorkclawWsConnection,
|
|
5
|
-
createLogger,
|
|
6
|
-
setWorkclawLoggerFromContext,
|
|
7
|
-
registerAccountContext,
|
|
8
|
-
unregisterAccountContext,
|
|
9
|
-
getAppKeyByAccountId,
|
|
10
|
-
getAllDispatchersByAppKey,
|
|
11
|
-
getDispatcherByAppKeyAndAccountId,
|
|
12
|
-
getReconnectScheduler,
|
|
13
|
-
setReconnectScheduler,
|
|
14
|
-
clearReconnectScheduler,
|
|
15
|
-
tryStartConnecting,
|
|
16
|
-
finishConnecting,
|
|
17
|
-
setWorkclawConnectionConfig,
|
|
18
|
-
clearWorkclawConnectionConfig,
|
|
19
|
-
type TryStartConnectingResult,
|
|
20
|
-
} from "../runtime.js";
|
|
21
|
-
import { sendMessageWorkclaw } from "../outbound/index.js";
|
|
22
|
-
import { clearWorkclawTokenCache, getWorkclawAccessToken, getWorkclawModelConfigByAppKey } from "../connection/workclaw-client.js";
|
|
23
|
-
import { buildAccountMap, resolveAccountByUserIdAndAgentId } from "../accounts.js";
|
|
24
|
-
import { dispatchWorkclawMessage, type MessageDispatcherContext } from "./message-dispatcher.js";
|
|
25
|
-
import { parseWorkClawMessage } from "./message-context.js";
|
|
26
|
-
import { createReconnectScheduler } from "./reconnect.js";
|
|
27
|
-
|
|
28
|
-
export interface WorkclawGatewayOptions {
|
|
29
|
-
accountId: string;
|
|
30
|
-
account: ResolvedWorkclawAccount;
|
|
31
|
-
cfg: any;
|
|
32
|
-
log?: {
|
|
33
|
-
info?: (msg: string) => void;
|
|
34
|
-
warn?: (msg: string) => void;
|
|
35
|
-
error?: (msg: string) => void;
|
|
36
|
-
debug?: (msg: string) => void;
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export async function startWorkclawGateway(options: WorkclawGatewayOptions): Promise<void> {
|
|
41
|
-
const { accountId, account, cfg, log } = options;
|
|
42
|
-
|
|
43
|
-
const logger = createLogger("", log);
|
|
44
|
-
setWorkclawLoggerFromContext(logger);
|
|
45
|
-
|
|
46
|
-
const rawConfig = account.config as unknown as WorkclawConfig & WorkclawAccountConfig;
|
|
47
|
-
const connectionMode = rawConfig.connectionMode || "websocket";
|
|
48
|
-
const wsStrategy = rawConfig.wsConnectionStrategy || "per-account";
|
|
49
|
-
|
|
50
|
-
if (connectionMode !== "websocket") {
|
|
51
|
-
throw new Error(`Unsupported connectionMode: ${connectionMode}`);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (wsStrategy === "per-appKey") {
|
|
55
|
-
await startSharedWebSocket(options);
|
|
56
|
-
} else {
|
|
57
|
-
await startPerAccountWebSocket(options);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* per-account 模式: 每个账号独立 WebSocket 连接
|
|
63
|
-
*/
|
|
64
|
-
async function startPerAccountWebSocket(options: WorkclawGatewayOptions): Promise<void> {
|
|
65
|
-
const { accountId, account, cfg, log } = options;
|
|
66
|
-
|
|
67
|
-
const logger = createLogger("", log);
|
|
68
|
-
const rawConfig = account.config as unknown as WorkclawConfig & WorkclawAccountConfig;
|
|
69
|
-
|
|
70
|
-
const baseConfig: WorkClawBaseConfig = {
|
|
71
|
-
baseUrl: rawConfig.baseUrl,
|
|
72
|
-
websocketUrl: rawConfig.websocketUrl,
|
|
73
|
-
appKey: rawConfig.appKey,
|
|
74
|
-
appSecret: rawConfig.appSecret,
|
|
75
|
-
localIp: rawConfig.localIp,
|
|
76
|
-
allowInsecureTls: rawConfig.allowInsecureTls,
|
|
77
|
-
requestTimeout: rawConfig.requestTimeout,
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const accountConfig: WorkclawAccountConfig = {
|
|
81
|
-
agentId: rawConfig.agentId,
|
|
82
|
-
userId: rawConfig.userId,
|
|
83
|
-
openConversationId: rawConfig.openConversationId,
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const appKeyRaw = baseConfig.appKey ?? "";
|
|
87
|
-
const appSecretRaw = baseConfig.appSecret ?? "";
|
|
88
|
-
|
|
89
|
-
buildAccountMap(cfg);
|
|
90
|
-
|
|
91
|
-
logger.info(
|
|
92
|
-
`Gateway start (per-account) accountId=${accountId} baseUrl=${baseConfig.baseUrl ?? ""} websocketUrl=${baseConfig.websocketUrl ?? ""} localIp=${baseConfig.localIp ?? ""} allowInsecureTls=${baseConfig.allowInsecureTls} requestTimeout=${baseConfig.requestTimeout ?? ""} appKeyPrefix=${appKeyRaw ? `${appKeyRaw.slice(0, 8)}...` : "missing"} appSecretLen=${appSecretRaw.length}`,
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
const ctx: MessageDispatcherContext = {
|
|
96
|
-
accountId,
|
|
97
|
-
account,
|
|
98
|
-
cfg,
|
|
99
|
-
baseConfig,
|
|
100
|
-
accountConfig,
|
|
101
|
-
log: logger,
|
|
102
|
-
scheduleReconnect: () => scheduler.scheduleReconnect(),
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const handleMessage = (data: string) => {
|
|
106
|
-
dispatchWorkclawMessage(data, ctx).catch((err) => {
|
|
107
|
-
logger.error(`Dispatch error: ${String(err)}`);
|
|
108
|
-
});
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const scheduler = createReconnectScheduler({
|
|
112
|
-
key: accountId,
|
|
113
|
-
config: baseConfig,
|
|
114
|
-
log: logger,
|
|
115
|
-
onMessage: handleMessage,
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
await scheduler.connect();
|
|
119
|
-
|
|
120
|
-
// Cache the connection config to prevent cfg mutation issues during message sends
|
|
121
|
-
setWorkclawConnectionConfig(accountId, {
|
|
122
|
-
appKey: baseConfig.appKey ?? "",
|
|
123
|
-
appSecret: baseConfig.appSecret ?? "",
|
|
124
|
-
baseUrl: baseConfig.baseUrl,
|
|
125
|
-
websocketUrl: baseConfig.websocketUrl,
|
|
126
|
-
localIp: baseConfig.localIp,
|
|
127
|
-
allowInsecureTls: baseConfig.allowInsecureTls,
|
|
128
|
-
requestTimeout: baseConfig.requestTimeout,
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* per-appKey 模式: 多个账号共享一个 WebSocket 连接
|
|
134
|
-
*/
|
|
135
|
-
async function startSharedWebSocket(options: WorkclawGatewayOptions): Promise<void> {
|
|
136
|
-
const { accountId, account, cfg, log } = options;
|
|
137
|
-
|
|
138
|
-
const logger = createLogger("", log);
|
|
139
|
-
const rawConfig = account.config as unknown as WorkclawConfig & WorkclawAccountConfig;
|
|
140
|
-
|
|
141
|
-
const baseConfig: WorkClawBaseConfig = {
|
|
142
|
-
baseUrl: rawConfig.baseUrl,
|
|
143
|
-
websocketUrl: rawConfig.websocketUrl,
|
|
144
|
-
appKey: rawConfig.appKey,
|
|
145
|
-
appSecret: rawConfig.appSecret,
|
|
146
|
-
localIp: rawConfig.localIp,
|
|
147
|
-
allowInsecureTls: rawConfig.allowInsecureTls,
|
|
148
|
-
requestTimeout: rawConfig.requestTimeout,
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
const accountConfig: WorkclawAccountConfig = {
|
|
152
|
-
agentId: rawConfig.agentId,
|
|
153
|
-
userId: rawConfig.userId,
|
|
154
|
-
openConversationId: rawConfig.openConversationId,
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
const appKey = baseConfig.appKey || accountId;
|
|
158
|
-
const appKeyRaw = baseConfig.appKey ?? "";
|
|
159
|
-
|
|
160
|
-
buildAccountMap(cfg);
|
|
161
|
-
|
|
162
|
-
logger.info(
|
|
163
|
-
`Gateway start (per-appKey) accountId=${accountId} appKey=${appKeyRaw ? `${appKeyRaw.slice(0, 8)}...` : "missing"} baseUrl=${baseConfig.baseUrl ?? ""} websocketUrl=${baseConfig.websocketUrl ?? ""}`,
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
// 注册账号上下文
|
|
167
|
-
const ctx: MessageDispatcherContext = {
|
|
168
|
-
accountId,
|
|
169
|
-
account,
|
|
170
|
-
cfg,
|
|
171
|
-
baseConfig,
|
|
172
|
-
accountConfig,
|
|
173
|
-
log: logger,
|
|
174
|
-
scheduleReconnect: () => {
|
|
175
|
-
const scheduler = getReconnectScheduler(appKey);
|
|
176
|
-
scheduler?.scheduleReconnect();
|
|
177
|
-
},
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
registerAccountContext(appKey, accountId, ctx);
|
|
181
|
-
|
|
182
|
-
// 检查是否已存在该 appKey 的 WebSocket
|
|
183
|
-
const existingWs = getWorkclawWsConnection(appKey);
|
|
184
|
-
if (existingWs) {
|
|
185
|
-
logger.info(`Gateway (per-appKey) using existing WebSocket for appKey=${appKey}`);
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// 尝试获取连接锁,防止竞态条件
|
|
190
|
-
const startResult = tryStartConnecting(appKey);
|
|
191
|
-
|
|
192
|
-
if (!startResult.isConnector) {
|
|
193
|
-
// 另一个账号正在连接(成功或失败中),等待共享的 connectingPromise
|
|
194
|
-
logger.info(`Gateway (per-appKey) another account is connecting, waiting... appKey=${appKey}`);
|
|
195
|
-
const resultWs = await startResult.connectingPromise;
|
|
196
|
-
if (resultWs) {
|
|
197
|
-
logger.info(`Gateway (per-appKey) connection established by another account, using it appKey=${appKey}`);
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
// 连接失败(resultWs === null),当前账号作为新 connector 重试
|
|
201
|
-
logger.warn(`Gateway (per-appKey) connection attempt failed, will create own connection appKey=${appKey}`);
|
|
202
|
-
} else {
|
|
203
|
-
// 是 connector,创建连接
|
|
204
|
-
logger.info(`Gateway (per-appKey) acquired connector lock, creating connection appKey=${appKey}`);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// 需要创建新的 WebSocket(只有获取到锁的账号才执行到这里)
|
|
208
|
-
const scheduler = createReconnectScheduler({
|
|
209
|
-
key: appKey,
|
|
210
|
-
config: baseConfig,
|
|
211
|
-
log: logger,
|
|
212
|
-
onMessage: (data: string) => {
|
|
213
|
-
handleSharedMessage(appKey, data, cfg, logger);
|
|
214
|
-
},
|
|
215
|
-
onClose: () => {
|
|
216
|
-
scheduler.scheduleReconnect();
|
|
217
|
-
},
|
|
218
|
-
// 旧代码在 setWorkclawWsConnection 后、ws.onopen 前就调用 finishConnecting,
|
|
219
|
-
// 这个回调模拟同样的时机:ws 已存入 runtime,通知所有等待者
|
|
220
|
-
onConnectingStarted: (ws: any) => {
|
|
221
|
-
finishConnecting(appKey, ws);
|
|
222
|
-
},
|
|
223
|
-
});
|
|
224
|
-
setReconnectScheduler(appKey, scheduler);
|
|
225
|
-
|
|
226
|
-
try {
|
|
227
|
-
await scheduler.connect();
|
|
228
|
-
} catch (err) {
|
|
229
|
-
// 连接失败,通知所有等待者重试(不传 ws 表示失败)
|
|
230
|
-
finishConnecting(appKey);
|
|
231
|
-
throw err;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Cache the connection config to prevent cfg mutation issues during message sends
|
|
235
|
-
// In per-appKey mode, each account still has its own config cached by accountId
|
|
236
|
-
setWorkclawConnectionConfig(accountId, {
|
|
237
|
-
appKey: baseConfig.appKey ?? "",
|
|
238
|
-
appSecret: baseConfig.appSecret ?? "",
|
|
239
|
-
baseUrl: baseConfig.baseUrl,
|
|
240
|
-
websocketUrl: baseConfig.websocketUrl,
|
|
241
|
-
localIp: baseConfig.localIp,
|
|
242
|
-
allowInsecureTls: baseConfig.allowInsecureTls,
|
|
243
|
-
requestTimeout: baseConfig.requestTimeout,
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* per-appKey 模式下处理共享 WebSocket 消息
|
|
249
|
-
*/
|
|
250
|
-
function handleSharedMessage(appKey: string, data: string, cfg: any, logger: any): void {
|
|
251
|
-
const parsed = parseWorkClawMessage(data, logger);
|
|
252
|
-
|
|
253
|
-
if (!parsed) {
|
|
254
|
-
logger.warn?.(`Gateway (per-appKey) failed to parse message`);
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// ping/pong/disconnect 无需路由
|
|
259
|
-
if (parsed.type === "ping") {
|
|
260
|
-
const ws = getWorkclawWsConnection(appKey);
|
|
261
|
-
if (ws && ws.readyState === 1) { // OPEN
|
|
262
|
-
const pongResponse = {
|
|
263
|
-
code: 200,
|
|
264
|
-
message: "pong",
|
|
265
|
-
metadata: { contentType: "application/json" },
|
|
266
|
-
data: JSON.stringify(parsed.pongData),
|
|
267
|
-
};
|
|
268
|
-
ws.send(JSON.stringify(pongResponse));
|
|
269
|
-
}
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
if (parsed.type === "disconnect") {
|
|
274
|
-
const ws = getWorkclawWsConnection(appKey);
|
|
275
|
-
ws?.close();
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// logger.info?.(`Gateway (per-appKey) received message: ${data}`);
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
// 根据消息类型确定 userId 和 agentId
|
|
283
|
-
let userId: string = "";
|
|
284
|
-
let agentId: string = "";
|
|
285
|
-
|
|
286
|
-
if (parsed.type === "agent_message" && parsed.message) {
|
|
287
|
-
userId = String(parsed.message.userId || "");
|
|
288
|
-
agentId = String(parsed.message.agentId || "");
|
|
289
|
-
} else if (parsed.type === "agent_created") {
|
|
290
|
-
// agent_created 触发新账号创建
|
|
291
|
-
const allDispatchers = getAllDispatchersByAppKey(appKey);
|
|
292
|
-
if (allDispatchers && allDispatchers.size > 0) {
|
|
293
|
-
const firstDispatcher = allDispatchers.values().next().value;
|
|
294
|
-
dispatchWorkclawMessage(data, firstDispatcher.ctx).catch((err) => {
|
|
295
|
-
logger.error?.(`Dispatch error: ${String(err)}`);
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
return;
|
|
299
|
-
} else if (parsed.type === "agent_updated" || parsed.type === "agent_deleted") {
|
|
300
|
-
userId = String(parsed.eventData?.futureId || parsed.eventData?.userId || "");
|
|
301
|
-
agentId = String(parsed.eventData?.id || parsed.eventData?.agentId || "");
|
|
302
|
-
} else if (parsed.type === "tools_list" || parsed.type === "skills_list" || parsed.type === "skills_event") {
|
|
303
|
-
userId = String(parsed.eventData?.userId || "");
|
|
304
|
-
agentId = String(parsed.eventData?.agentId || "");
|
|
305
|
-
} else if (parsed.type === "init_agent") {
|
|
306
|
-
userId = String(parsed.eventData?.futureId || parsed.eventData?.userId || "");
|
|
307
|
-
agentId = String(parsed.eventData?.id || parsed.eventData?.agentId || "");
|
|
308
|
-
/**
|
|
309
|
-
* 在 init_agent 中,配置文件的agentId 为空,所有无法通过 resolveAccountByUserIdAndAgentId 查找账户
|
|
310
|
-
*/
|
|
311
|
-
|
|
312
|
-
const accountId = "default";
|
|
313
|
-
const dispatcher = getDispatcherByAppKeyAndAccountId(appKey, accountId);
|
|
314
|
-
if (dispatcher) {
|
|
315
|
-
dispatchWorkclawMessage(data, dispatcher.ctx).catch((err) => {
|
|
316
|
-
logger.error?.(`Dispatch error: ${String(err)}`);
|
|
317
|
-
});
|
|
318
|
-
} else {
|
|
319
|
-
logger.warn?.(`Gateway (per-appKey) no dispatcher for accountId=${accountId}`);
|
|
320
|
-
}
|
|
321
|
-
return;
|
|
322
|
-
} else {
|
|
323
|
-
// 未知消息类型,忽略
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
const accountId = resolveAccountByUserIdAndAgentId(cfg, userId, agentId);
|
|
328
|
-
if (!accountId) {
|
|
329
|
-
logger.warn?.(`Gateway (per-appKey) no account found for userId=${userId} agentId=${agentId}`);
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const dispatcher = getDispatcherByAppKeyAndAccountId(appKey, accountId);
|
|
334
|
-
if (dispatcher) {
|
|
335
|
-
dispatchWorkclawMessage(data, dispatcher.ctx).catch((err) => {
|
|
336
|
-
logger.error?.(`Dispatch error: ${String(err)}`);
|
|
337
|
-
});
|
|
338
|
-
} else {
|
|
339
|
-
logger.warn?.(`Gateway (per-appKey) no dispatcher for accountId=${accountId}`);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
export function stopWorkclawGateway(accountId: string, wsStrategy?: string): void {
|
|
344
|
-
if (wsStrategy === "per-appKey") {
|
|
345
|
-
const appKey = getAppKeyByAccountId(accountId);
|
|
346
|
-
if (!appKey) return;
|
|
347
|
-
|
|
348
|
-
unregisterAccountContext(appKey, accountId);
|
|
349
|
-
|
|
350
|
-
if (getAllDispatchersByAppKey(appKey)?.size === 0) {
|
|
351
|
-
const ws = getWorkclawWsConnection(appKey);
|
|
352
|
-
ws?.close();
|
|
353
|
-
clearWorkclawWsConnection(appKey);
|
|
354
|
-
|
|
355
|
-
const scheduler = getReconnectScheduler(appKey);
|
|
356
|
-
scheduler?.stopReconnect();
|
|
357
|
-
clearReconnectScheduler(appKey);
|
|
358
|
-
}
|
|
359
|
-
clearWorkclawConnectionConfig(accountId);
|
|
360
|
-
} else {
|
|
361
|
-
const ws = getWorkclawWsConnection(accountId);
|
|
362
|
-
if (ws) ws.close();
|
|
363
|
-
clearWorkclawWsConnection(accountId);
|
|
364
|
-
clearWorkclawTokenCache(accountId);
|
|
365
|
-
clearWorkclawConnectionConfig(accountId);
|
|
366
|
-
}
|
|
367
|
-
}
|
package/src/media/upload.ts
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { readFile, stat } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { Agent, fetch as undiciFetch } from "undici";
|
|
6
|
-
|
|
7
|
-
export function isLocalMediaSource(value: string): boolean {
|
|
8
|
-
const trimmed = value.trim();
|
|
9
|
-
return (
|
|
10
|
-
trimmed.startsWith("/") ||
|
|
11
|
-
trimmed.startsWith("./") ||
|
|
12
|
-
trimmed.startsWith("../") ||
|
|
13
|
-
trimmed.startsWith("~") ||
|
|
14
|
-
trimmed.startsWith("file://")
|
|
15
|
-
);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function resolveLocalPath(input: string): string {
|
|
19
|
-
if (input.startsWith("file://")) {
|
|
20
|
-
return fileURLToPath(input);
|
|
21
|
-
}
|
|
22
|
-
if (input.startsWith("~")) {
|
|
23
|
-
return path.join(os.homedir(), input.slice(1));
|
|
24
|
-
}
|
|
25
|
-
return path.resolve(input);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function readResponseUrlPath(
|
|
29
|
-
data: Record<string, unknown>,
|
|
30
|
-
pathValue?: string,
|
|
31
|
-
): string | undefined {
|
|
32
|
-
if (!pathValue) return undefined;
|
|
33
|
-
const parts = pathValue.split(".").filter(Boolean);
|
|
34
|
-
let current: unknown = data;
|
|
35
|
-
for (const part of parts) {
|
|
36
|
-
if (!current || typeof current !== "object") return undefined;
|
|
37
|
-
current = (current as Record<string, unknown>)[part];
|
|
38
|
-
}
|
|
39
|
-
return typeof current === "string" ? current : undefined;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function extractUploadedUrl(
|
|
43
|
-
responseText: string,
|
|
44
|
-
responseData: Record<string, unknown>,
|
|
45
|
-
pathValue?: string,
|
|
46
|
-
): string | undefined {
|
|
47
|
-
const fromPath = readResponseUrlPath(responseData, pathValue);
|
|
48
|
-
if (fromPath) return fromPath;
|
|
49
|
-
|
|
50
|
-
const direct =
|
|
51
|
-
(responseData.url as string | undefined) ??
|
|
52
|
-
(responseData.mediaUrl as string | undefined);
|
|
53
|
-
if (typeof direct === "string" && direct.trim()) return direct;
|
|
54
|
-
|
|
55
|
-
const dataObj = responseData.data;
|
|
56
|
-
if (dataObj && typeof dataObj === "object") {
|
|
57
|
-
const dataUrl = (dataObj as Record<string, unknown>).url;
|
|
58
|
-
const dataMedia = (dataObj as Record<string, unknown>).mediaUrl;
|
|
59
|
-
if (typeof dataUrl === "string" && dataUrl.trim()) return dataUrl;
|
|
60
|
-
if (typeof dataMedia === "string" && dataMedia.trim()) return dataMedia;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const resultObj = responseData.result;
|
|
64
|
-
if (resultObj && typeof resultObj === "object") {
|
|
65
|
-
const resultUrl = (resultObj as Record<string, unknown>).url;
|
|
66
|
-
const resultMedia = (resultObj as Record<string, unknown>).mediaUrl;
|
|
67
|
-
if (typeof resultUrl === "string" && resultUrl.trim()) return resultUrl;
|
|
68
|
-
if (typeof resultMedia === "string" && resultMedia.trim()) return resultMedia;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (responseText.trim() && /^https?:\/\//i.test(responseText.trim())) {
|
|
72
|
-
return responseText.trim();
|
|
73
|
-
}
|
|
74
|
-
return undefined;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export interface UploadLocalMediaParams {
|
|
78
|
-
uploadUrl: string;
|
|
79
|
-
filePath: string;
|
|
80
|
-
uploadFieldName?: string;
|
|
81
|
-
uploadHeaders?: Record<string, string>;
|
|
82
|
-
uploadFormFields?: Record<string, string | number | boolean>;
|
|
83
|
-
uploadResponseUrlPath?: string;
|
|
84
|
-
requestTimeout: number;
|
|
85
|
-
allowInsecureTls?: boolean;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export async function uploadLocalMedia(params: UploadLocalMediaParams): Promise<string> {
|
|
89
|
-
const stats = await stat(params.filePath);
|
|
90
|
-
if (!stats.isFile()) {
|
|
91
|
-
throw new Error(`mediaUrl is not a file: ${params.filePath}`);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const content = await readFile(params.filePath);
|
|
95
|
-
const form = new FormData();
|
|
96
|
-
const fieldName = params.uploadFieldName?.trim() || "file";
|
|
97
|
-
const fileName = path.basename(params.filePath);
|
|
98
|
-
form.set(fieldName, new Blob([content], { type: "application/octet-stream" }), fileName);
|
|
99
|
-
|
|
100
|
-
if (params.uploadFormFields) {
|
|
101
|
-
for (const [key, value] of Object.entries(params.uploadFormFields)) {
|
|
102
|
-
form.set(key, String(value));
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const controller = new AbortController();
|
|
107
|
-
const timeoutId = setTimeout(() => controller.abort(), params.requestTimeout);
|
|
108
|
-
|
|
109
|
-
let dispatcher: unknown = undefined;
|
|
110
|
-
let doFetch: any = globalThis.fetch.bind(globalThis);
|
|
111
|
-
|
|
112
|
-
if (params.allowInsecureTls) {
|
|
113
|
-
try {
|
|
114
|
-
dispatcher = new Agent({ connect: { rejectUnauthorized: false } });
|
|
115
|
-
doFetch = undiciFetch as any;
|
|
116
|
-
} catch (error) {
|
|
117
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
118
|
-
throw new Error(`allowInsecureTls requires undici. ${message}`);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
const response = await doFetch(params.uploadUrl, {
|
|
124
|
-
method: "POST",
|
|
125
|
-
headers: params.uploadHeaders ?? {},
|
|
126
|
-
body: form,
|
|
127
|
-
signal: controller.signal,
|
|
128
|
-
...(dispatcher ? { dispatcher } : {}),
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
const responseText = await response.text().catch(() => "");
|
|
132
|
-
if (!response.ok) {
|
|
133
|
-
throw new Error(`Upload failed: ${response.status} ${responseText}`);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
let data: Record<string, unknown> = {};
|
|
137
|
-
if (responseText) {
|
|
138
|
-
try {
|
|
139
|
-
data = JSON.parse(responseText) as Record<string, unknown>;
|
|
140
|
-
} catch {
|
|
141
|
-
data = {};
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const uploadedUrl = extractUploadedUrl(
|
|
146
|
-
responseText,
|
|
147
|
-
data,
|
|
148
|
-
params.uploadResponseUrlPath,
|
|
149
|
-
);
|
|
150
|
-
if (!uploadedUrl) {
|
|
151
|
-
throw new Error("Upload response missing file URL");
|
|
152
|
-
}
|
|
153
|
-
return uploadedUrl;
|
|
154
|
-
} finally {
|
|
155
|
-
clearTimeout(timeoutId);
|
|
156
|
-
}
|
|
157
|
-
}
|
package/src/outbound/index.ts
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
2
|
-
import type { WorkclawSendResult, WorkclawConfig } from "../types.js";
|
|
3
|
-
import { resolveWorkclawAccountWithCache } from "../accounts.js";
|
|
4
|
-
import { resolveAccountByUserIdAndAgentId } from "../accounts.js";
|
|
5
|
-
import { isLocalMediaSource, resolveLocalPath, uploadLocalMedia } from "../media/upload.js";
|
|
6
|
-
import {
|
|
7
|
-
sendWorkclawOutboundMessage,
|
|
8
|
-
} from "./workclaw-sender.js";
|
|
9
|
-
import { getWorkclawLogger, setLastOutboundAt } from "../runtime.js";
|
|
10
|
-
import { stat } from "node:fs/promises";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* 根据 target (userId) 和可选的 agentId 查找匹配的账户
|
|
14
|
-
* 如果同时提供 userId 和 agentId,走 O(1) 查找
|
|
15
|
-
* 否则遍历所有账户,查找 userId 等于 target 的账户
|
|
16
|
-
*/
|
|
17
|
-
function findAccountIdByTarget(
|
|
18
|
-
cfg: OpenClawConfig,
|
|
19
|
-
target: string,
|
|
20
|
-
agentId?: string | number,
|
|
21
|
-
): string | undefined {
|
|
22
|
-
// O(1) 查找:userId + agentId 唯一确定一个账户
|
|
23
|
-
if (agentId !== undefined && agentId !== null) {
|
|
24
|
-
return resolveAccountByUserIdAndAgentId(cfg, target, String(agentId)) ?? undefined
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// 遍历:只有 userId 时,无法唯一确定账户(一个 userId 对应多个 agentId)
|
|
28
|
-
// 需要通过 bindings 或其他方式 resolve,这里暂不支持
|
|
29
|
-
return undefined
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export type SendWorkclawMessageParams = {
|
|
33
|
-
cfg: OpenClawConfig;
|
|
34
|
-
to: string;
|
|
35
|
-
text: string;
|
|
36
|
-
mediaUrl?: string;
|
|
37
|
-
replyToMessageId?: string;
|
|
38
|
-
accountId?: string;
|
|
39
|
-
openConversationId?: string;
|
|
40
|
-
agentId?: string | number;
|
|
41
|
-
/**
|
|
42
|
-
* 消息类型:
|
|
43
|
-
* - "reply": 回复消息(用户触发)
|
|
44
|
-
* - "push": 主动推送(定时任务触发)
|
|
45
|
-
*/
|
|
46
|
-
messageType?: 'reply' | 'push'
|
|
47
|
-
msgType?: string
|
|
48
|
-
last?: boolean
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async function resolveMediaUrl(
|
|
52
|
-
mediaUrl: string | undefined,
|
|
53
|
-
config: WorkclawConfig,
|
|
54
|
-
): Promise<string | undefined> {
|
|
55
|
-
if (!mediaUrl)
|
|
56
|
-
return undefined
|
|
57
|
-
if (!isLocalMediaSource(mediaUrl))
|
|
58
|
-
return mediaUrl
|
|
59
|
-
|
|
60
|
-
const uploadUrl = config.uploadUrl
|
|
61
|
-
if (!uploadUrl) {
|
|
62
|
-
throw new Error('uploadUrl not configured for local mediaUrl')
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const filePath = resolveLocalPath(mediaUrl)
|
|
66
|
-
|
|
67
|
-
if (typeof config.mediaMaxMb === "number" && config.mediaMaxMb > 0) {
|
|
68
|
-
const stats = await stat(filePath);
|
|
69
|
-
const maxBytes = config.mediaMaxMb * 1024 * 1024;
|
|
70
|
-
if (stats.size > maxBytes) {
|
|
71
|
-
throw new Error(`mediaUrl exceeds limit (${config.mediaMaxMb} MB)`)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return uploadLocalMedia({
|
|
76
|
-
uploadUrl,
|
|
77
|
-
filePath,
|
|
78
|
-
uploadFieldName: config.uploadFieldName,
|
|
79
|
-
uploadHeaders: config.uploadHeaders as Record<string, string> | undefined,
|
|
80
|
-
uploadFormFields: config.uploadFormFields as
|
|
81
|
-
| Record<string, string | number | boolean>
|
|
82
|
-
| undefined,
|
|
83
|
-
uploadResponseUrlPath: config.uploadResponseUrlPath,
|
|
84
|
-
requestTimeout: config.requestTimeout ?? 30000,
|
|
85
|
-
allowInsecureTls: config.allowInsecureTls,
|
|
86
|
-
})
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export async function sendMessageWorkclaw(
|
|
90
|
-
params: SendWorkclawMessageParams,
|
|
91
|
-
): Promise<WorkclawSendResult> {
|
|
92
|
-
const { cfg, to, text, mediaUrl, replyToMessageId, accountId, openConversationId, agentId, messageType, msgType, last } =
|
|
93
|
-
params;
|
|
94
|
-
|
|
95
|
-
// 如果没有指定 messageType,根据是否有 replyToMessageId 来判断
|
|
96
|
-
// - 有 replyToMessageId: 回复消息
|
|
97
|
-
// - 没有 replyToMessageId: 主动推送
|
|
98
|
-
const resolvedMessageType = messageType ?? (replyToMessageId ? 'reply' : 'push')
|
|
99
|
-
|
|
100
|
-
// 如果没有指定 accountId,尝试根据 target (to) 和 agentId 查找匹配的账户
|
|
101
|
-
let resolvedAccountId = accountId
|
|
102
|
-
if (!resolvedAccountId) {
|
|
103
|
-
const foundAccountId = findAccountIdByTarget(cfg, to, agentId)
|
|
104
|
-
if (foundAccountId) {
|
|
105
|
-
resolvedAccountId = foundAccountId
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const account = resolveWorkclawAccountWithCache({ cfg, accountId: resolvedAccountId });
|
|
110
|
-
if (!account.configured) {
|
|
111
|
-
throw new Error(`Workclaw account "${account.accountId}" not configured`);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const resolvedMediaUrl = await resolveMediaUrl(mediaUrl, account.config)
|
|
115
|
-
|
|
116
|
-
// 优先使用传入的 openConversationId,其次从 cfg 中直接读取(绕过 account.config 快照问题)
|
|
117
|
-
// 这样可以获取到运行时动态保存的 openConversationId
|
|
118
|
-
type ChannelWithAccounts = { accounts?: Record<string, any>; openConversationId?: string };
|
|
119
|
-
const cfgChannel = (cfg.channels?.['openclaw-workclaw'] as unknown as ChannelWithAccounts) ?? {};
|
|
120
|
-
const cfgAccounts = cfgChannel.accounts ?? {};
|
|
121
|
-
const cfgAccount = cfgAccounts[resolvedAccountId] ?? {};
|
|
122
|
-
const cfgOpenConversationId = cfgAccount?.openConversationId ?? cfgChannel?.openConversationId;
|
|
123
|
-
const effectiveOpenConversationId = openConversationId ?? cfgOpenConversationId;
|
|
124
|
-
getWorkclawLogger().info(`sendMessageWorkclaw - resolvedAccountId: ${resolvedAccountId}, openConversationId param: ${openConversationId}, cfgOpenConversationId: ${cfgOpenConversationId}, effectiveOpenConversationId: ${effectiveOpenConversationId}, messageType: ${resolvedMessageType}, replyToMessageId: ${replyToMessageId}`);
|
|
125
|
-
|
|
126
|
-
// 使用 appKey 作为缓存键,让所有账户共享同一个 token
|
|
127
|
-
const tokenCacheKey = account.config.appKey || account.accountId;
|
|
128
|
-
const result = await sendWorkclawOutboundMessage({
|
|
129
|
-
cacheKey: tokenCacheKey,
|
|
130
|
-
to,
|
|
131
|
-
text,
|
|
132
|
-
msgType,
|
|
133
|
-
mediaUrl: resolvedMediaUrl,
|
|
134
|
-
replyToMessageId,
|
|
135
|
-
openConversationId: effectiveOpenConversationId,
|
|
136
|
-
agentId,
|
|
137
|
-
config: account.config,
|
|
138
|
-
messageType: resolvedMessageType,
|
|
139
|
-
last,
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
// 更新上次出站时间
|
|
143
|
-
if (resolvedAccountId) {
|
|
144
|
-
setLastOutboundAt(resolvedAccountId, Date.now());
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return {
|
|
148
|
-
messageId: result.messageId,
|
|
149
|
-
chatId: to,
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export async function getMessageWorkclaw(_params: {
|
|
154
|
-
cfg: OpenClawConfig;
|
|
155
|
-
messageId: string;
|
|
156
|
-
accountId?: string;
|
|
157
|
-
}): Promise<null> {
|
|
158
|
-
return null
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* 发送主动推送消息(定时任务使用)
|
|
163
|
-
* 使用 pushEndpoint 配置的端口
|
|
164
|
-
*/
|
|
165
|
-
export async function sendPushMessageWorkclaw(
|
|
166
|
-
params: Omit<SendWorkclawMessageParams, "messageType">,
|
|
167
|
-
): Promise<WorkclawSendResult> {
|
|
168
|
-
return sendMessageWorkclaw({
|
|
169
|
-
...params,
|
|
170
|
-
messageType: 'push',
|
|
171
|
-
})
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* 发送回复消息(用户触发)
|
|
176
|
-
* 使用 replyEndpoint 配置的端口
|
|
177
|
-
*/
|
|
178
|
-
export async function sendReplyMessageWorkclaw(
|
|
179
|
-
params: Omit<SendWorkclawMessageParams, "messageType">,
|
|
180
|
-
): Promise<WorkclawSendResult> {
|
|
181
|
-
return sendMessageWorkclaw({
|
|
182
|
-
...params,
|
|
183
|
-
messageType: 'reply',
|
|
184
|
-
})
|
|
185
|
-
}
|