@sumeai/sumeclaw 1.1.20 → 1.2.1

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.
@@ -0,0 +1,746 @@
1
+ // src/runtime.ts
2
+ var runtimeRef = null;
3
+ function setSumeclawRuntime(runtime) {
4
+ runtimeRef = runtime;
5
+ }
6
+ function tryGetSumeclawRuntime() {
7
+ return runtimeRef;
8
+ }
9
+
10
+ // src/gateway.ts
11
+ import WebSocket from "ws";
12
+ import { dispatchInboundDirectDmWithRuntime } from "openclaw/plugin-sdk/direct-dm";
13
+ var DEFAULT_PLATFORM_URL = "wss://api.gixin.cc";
14
+ var connections = /* @__PURE__ */ new Map();
15
+ var connectingAccounts = /* @__PURE__ */ new Set();
16
+ var pendingReplyContexts = /* @__PURE__ */ new Map();
17
+ function replyContextKey(accountId, userId) {
18
+ return `${accountId}:${userId}`;
19
+ }
20
+ function getWebSocket(accountId) {
21
+ return connections.get(accountId);
22
+ }
23
+ function rememberPendingReplyContext(params) {
24
+ const key = replyContextKey(params.accountId, params.userId);
25
+ pendingReplyContexts.set(replyContextKey(params.accountId, params.userId), {
26
+ requestId: params.requestId,
27
+ sessionKey: params.sessionKey,
28
+ stream: params.stream,
29
+ accumulatedText: ""
30
+ });
31
+ }
32
+ function getPendingReplyContext(accountId, userId) {
33
+ return pendingReplyContexts.get(replyContextKey(accountId, userId));
34
+ }
35
+ function consumePendingReplyContext(accountId, userId) {
36
+ const key = replyContextKey(accountId, userId);
37
+ const ctx = pendingReplyContexts.get(key);
38
+ pendingReplyContexts.delete(key);
39
+ return ctx;
40
+ }
41
+ function appendPendingReplyText(accountId, userId, text) {
42
+ const ctx = getPendingReplyContext(accountId, userId);
43
+ if (!ctx) return "";
44
+ if (!ctx.accumulatedText) {
45
+ ctx.accumulatedText = text;
46
+ } else if (text.startsWith(ctx.accumulatedText)) {
47
+ ctx.accumulatedText = text;
48
+ } else if (!ctx.accumulatedText.endsWith(text)) {
49
+ ctx.accumulatedText += text;
50
+ }
51
+ return ctx.accumulatedText;
52
+ }
53
+ function connectGateway(opts) {
54
+ const { accountId, botToken, api } = opts;
55
+ const platformUrl = opts.platformUrl || DEFAULT_PLATFORM_URL;
56
+ const defaultAgentId = opts.defaultAgentId || "main";
57
+ const existing = connections.get(accountId);
58
+ if (existing && (existing.readyState === WebSocket.OPEN || existing.readyState === WebSocket.CONNECTING)) {
59
+ console.log(`[sumeclaw] \u267B\uFE0F reuse existing connection: ${accountId}`);
60
+ return;
61
+ }
62
+ if (connectingAccounts.has(accountId)) {
63
+ console.log(`[sumeclaw] \u267B\uFE0F connection already starting: ${accountId}`);
64
+ return;
65
+ }
66
+ let ws = null;
67
+ let heartbeatTimer = null;
68
+ let reconnectTimer = null;
69
+ const HEARTBEAT_INTERVAL = 3e4;
70
+ const RECONNECT_DELAY = 5e3;
71
+ function start() {
72
+ const url = `${platformUrl.replace(/\/$/, "")}/api/channel/${botToken}`;
73
+ console.log(`[sumeclaw] \u{1F310} connecting -> ${url} (${accountId})`);
74
+ connectingAccounts.add(accountId);
75
+ ws = new WebSocket(url);
76
+ connections.set(accountId, ws);
77
+ ws.on("open", () => {
78
+ console.log(`[sumeclaw] \u2705 connected: ${accountId}`);
79
+ connectingAccounts.delete(accountId);
80
+ connections.set(accountId, ws);
81
+ ws?.send(
82
+ JSON.stringify({
83
+ type: "register_info",
84
+ token: botToken,
85
+ agentId: defaultAgentId,
86
+ version: "1.2.0"
87
+ })
88
+ );
89
+ heartbeatTimer = setInterval(() => {
90
+ if (ws?.readyState === WebSocket.OPEN) {
91
+ ws.send(JSON.stringify({ type: "heartbeat" }));
92
+ }
93
+ }, HEARTBEAT_INTERVAL);
94
+ });
95
+ ws.on("message", (data) => {
96
+ try {
97
+ const msg = JSON.parse(data.toString());
98
+ console.log("[sumeclaw] \u{1F4E9} message:", msg);
99
+ handleMessage(msg).catch((err) => {
100
+ console.error("[sumeclaw] \u274C handle message error", err);
101
+ });
102
+ } catch (err) {
103
+ console.error("[sumeclaw] \u274C parse message error", err);
104
+ }
105
+ });
106
+ ws.on("close", (code, reasonBuffer) => {
107
+ const reason = reasonBuffer.toString();
108
+ console.warn(`[sumeclaw] \u26A0\uFE0F disconnected: ${accountId}`, { code, reason });
109
+ cleanup();
110
+ if (code === 4e3 || reason.includes("replaced by a newer OpenClaw plugin connection")) {
111
+ console.warn(`[sumeclaw] \u{1F6D1} connection replaced, stop reconnecting: ${accountId}`);
112
+ return;
113
+ }
114
+ scheduleReconnect();
115
+ });
116
+ ws.on("error", (err) => {
117
+ console.error(`[sumeclaw] \u274C ws error: ${accountId}`, err);
118
+ connectingAccounts.delete(accountId);
119
+ ws?.close();
120
+ });
121
+ }
122
+ async function handleMessage(msg) {
123
+ switch (msg.type) {
124
+ case "heartbeat_ack":
125
+ break;
126
+ case "hooks_agent":
127
+ await handleHooksAgent(msg.payload ?? msg);
128
+ break;
129
+ case "message":
130
+ console.log("[sumeclaw] \u{1F4AC} \u6536\u5230\u6D88\u606F:", msg);
131
+ await dispatchToOpenClaw({
132
+ agentId: msg.agentId || "main",
133
+ message: msg.text || msg.message || "",
134
+ messageId: msg.id || msg.messageId,
135
+ sessionKey: msg.sessionKey || `agent:${msg.agentId || "main"}:user:${msg.from || msg.userId || "unknown"}`,
136
+ userId: msg.from || msg.userId || msg.senderId || "unknown",
137
+ raw: msg,
138
+ requestId: msg.requestId,
139
+ stream: Boolean(msg.stream)
140
+ });
141
+ break;
142
+ case "registered":
143
+ console.log(`[sumeclaw] \u{1F510} \u6CE8\u518C\u6210\u529F: ${accountId}`);
144
+ break;
145
+ default:
146
+ console.log("[sumeclaw] \u2753 \u672A\u5904\u7406\u6D88\u606F:", msg);
147
+ }
148
+ }
149
+ async function handleHooksAgent(payload) {
150
+ const meta = payload?.meta ?? {};
151
+ const userId = String(meta.user_id || payload.userId || payload.from || "unknown");
152
+ const agentId = String(payload.agentId || meta.agent_id || "main");
153
+ const text = String(payload.message || payload.text || "");
154
+ const sessionKey = payload.sessionKey || `agent:${agentId}:user:${userId}`;
155
+ console.log("[sumeclaw] \u{1F9E0} hooks_agent -> OpenClaw", {
156
+ accountId,
157
+ agentId,
158
+ sessionKey,
159
+ userId,
160
+ text: text.slice(0, 100)
161
+ });
162
+ rememberPendingReplyContext({
163
+ accountId,
164
+ userId,
165
+ requestId: payload.requestId,
166
+ sessionKey,
167
+ stream: Boolean(payload.stream)
168
+ });
169
+ await dispatchToOpenClaw({
170
+ agentId,
171
+ message: text,
172
+ messageId: payload.messageId || `${sessionKey}:${Date.now()}`,
173
+ sessionKey,
174
+ userId,
175
+ raw: payload,
176
+ requestId: payload.requestId,
177
+ stream: Boolean(payload.stream)
178
+ });
179
+ }
180
+ function sendAgentReply(input) {
181
+ const activeWs = connections.get(accountId) ?? ws;
182
+ if (!activeWs || activeWs.readyState !== WebSocket.OPEN) {
183
+ console.error("[sumeclaw] \u274C cannot send agent_reply, websocket is not open", {
184
+ accountId,
185
+ requestId: input.requestId,
186
+ readyState: activeWs?.readyState
187
+ });
188
+ return false;
189
+ }
190
+ activeWs.send(
191
+ JSON.stringify({
192
+ type: "agent_reply",
193
+ requestId: input.requestId,
194
+ userId: input.userId,
195
+ sessionKey: input.sessionKey,
196
+ text: input.text,
197
+ messageId: input.messageId ?? `sumeclaw-${Date.now()}`,
198
+ ok: input.ok ?? true
199
+ })
200
+ );
201
+ console.log("[sumeclaw] \u{1F4E4} agent_reply sent", {
202
+ accountId,
203
+ requestId: input.requestId,
204
+ userId: input.userId,
205
+ textLength: input.text.length
206
+ });
207
+ return true;
208
+ }
209
+ function sendAgentDelta(input) {
210
+ const activeWs = connections.get(accountId) ?? ws;
211
+ if (!activeWs || activeWs.readyState !== WebSocket.OPEN) {
212
+ console.error("[sumeclaw] \u274C cannot send agent_delta, websocket is not open", {
213
+ accountId,
214
+ requestId: input.requestId,
215
+ readyState: activeWs?.readyState
216
+ });
217
+ return false;
218
+ }
219
+ activeWs.send(
220
+ JSON.stringify({
221
+ type: "agent_delta",
222
+ requestId: input.requestId,
223
+ userId: input.userId,
224
+ sessionKey: input.sessionKey,
225
+ text: input.text,
226
+ messageId: input.messageId ?? `sumeclaw-${Date.now()}`
227
+ })
228
+ );
229
+ console.log("[sumeclaw] \u{1F4E4} agent_delta sent", {
230
+ accountId,
231
+ requestId: input.requestId,
232
+ userId: input.userId,
233
+ textLength: input.text.length
234
+ });
235
+ return true;
236
+ }
237
+ function sendAgentDone(input) {
238
+ const activeWs = connections.get(accountId) ?? ws;
239
+ if (!activeWs || activeWs.readyState !== WebSocket.OPEN) {
240
+ console.error("[sumeclaw] \u274C cannot send agent_done, websocket is not open", {
241
+ accountId,
242
+ requestId: input.requestId,
243
+ readyState: activeWs?.readyState
244
+ });
245
+ return false;
246
+ }
247
+ activeWs.send(
248
+ JSON.stringify({
249
+ type: "agent_done",
250
+ requestId: input.requestId,
251
+ userId: input.userId,
252
+ sessionKey: input.sessionKey,
253
+ text: input.text,
254
+ messageId: input.messageId ?? `sumeclaw-${Date.now()}`,
255
+ ok: input.ok ?? true
256
+ })
257
+ );
258
+ console.log("[sumeclaw] \u{1F4E4} agent_done sent", {
259
+ accountId,
260
+ requestId: input.requestId,
261
+ userId: input.userId,
262
+ textLength: input.text.length
263
+ });
264
+ return true;
265
+ }
266
+ function finishStreamReply(input) {
267
+ const latest = consumePendingReplyContext(accountId, input.userId);
268
+ if (!latest) {
269
+ console.warn("[sumeclaw] \u26A0\uFE0F no pending stream context to finish", {
270
+ accountId,
271
+ requestId: input.requestId,
272
+ userId: input.userId
273
+ });
274
+ return false;
275
+ }
276
+ return sendAgentDone({
277
+ requestId: latest.requestId ?? input.requestId,
278
+ userId: input.userId,
279
+ sessionKey: latest.sessionKey ?? input.sessionKey,
280
+ text: latest.accumulatedText,
281
+ ok: input.ok
282
+ });
283
+ }
284
+ function finishBufferedReply(input) {
285
+ const latest = consumePendingReplyContext(accountId, input.userId);
286
+ if (!latest) {
287
+ console.warn("[sumeclaw] \u26A0\uFE0F no pending reply context to finish", {
288
+ accountId,
289
+ requestId: input.requestId,
290
+ userId: input.userId
291
+ });
292
+ return false;
293
+ }
294
+ return sendAgentReply({
295
+ requestId: latest.requestId ?? input.requestId,
296
+ userId: input.userId,
297
+ sessionKey: latest.sessionKey ?? input.sessionKey,
298
+ text: latest.accumulatedText,
299
+ ok: input.ok
300
+ });
301
+ }
302
+ async function dispatchToOpenClaw(input) {
303
+ const runtime = tryGetSumeclawRuntime() ?? api?.runtime;
304
+ const turn = runtime?.channel?.turn;
305
+ if (!turn?.run) {
306
+ const directReplyDispatcher = runtime?.channel?.reply?.dispatchReplyWithBufferedBlockDispatcher;
307
+ if (directReplyDispatcher) {
308
+ await dispatchViaDirectDmRuntime(runtime, input);
309
+ return;
310
+ }
311
+ console.error(
312
+ "[sumeclaw] \u274C \u5F53\u524D OpenClaw runtime \u4E0D\u63D0\u4F9B\u53EF\u7528\u7684 channel \u5165\u7AD9\u5206\u53D1\u80FD\u529B"
313
+ );
314
+ console.error("[sumeclaw] runtime debug:", {
315
+ hasStoredRuntime: Boolean(tryGetSumeclawRuntime()),
316
+ apiKeys: api ? Object.keys(api) : [],
317
+ runtimeKeys: runtime ? Object.keys(runtime) : [],
318
+ channelKeys: runtime?.channel ? Object.keys(runtime.channel) : []
319
+ });
320
+ sendAgentReply({
321
+ requestId: input.requestId,
322
+ userId: input.userId,
323
+ sessionKey: input.sessionKey,
324
+ text: "OpenClaw \u63D2\u4EF6\u672A\u83B7\u5F97\u8FD0\u884C\u65F6\u5206\u53D1\u80FD\u529B\uFF0C\u8BF7\u5347\u7EA7\u63D2\u4EF6\u5165\u53E3\u540E\u91CD\u542F OpenClaw Gateway\u3002",
325
+ ok: false
326
+ });
327
+ return;
328
+ }
329
+ const now = Date.now();
330
+ const messageId = String(input.messageId || `${input.sessionKey}:${now}`);
331
+ const senderLabel = `\u53CB\u867E\u7528\u6237 ${input.userId}`;
332
+ try {
333
+ const result = await turn.run({
334
+ channel: "sumeclaw",
335
+ accountId,
336
+ raw: input.raw,
337
+ adapter: {
338
+ ingest() {
339
+ return {
340
+ id: messageId,
341
+ timestamp: now,
342
+ rawText: input.message,
343
+ textForAgent: input.message,
344
+ textForCommands: input.message,
345
+ raw: input.raw
346
+ };
347
+ },
348
+ classify(turnInput) {
349
+ return {
350
+ kind: "message",
351
+ canStartAgentTurn: Boolean(turnInput.rawText)
352
+ };
353
+ },
354
+ resolveTurn(turnInput) {
355
+ return {
356
+ sender: {
357
+ id: input.userId,
358
+ name: senderLabel,
359
+ displayLabel: senderLabel,
360
+ isBot: false,
361
+ isSelf: false
362
+ },
363
+ conversation: {
364
+ kind: "direct",
365
+ id: input.userId,
366
+ label: senderLabel,
367
+ routePeer: input.userId
368
+ },
369
+ route: {
370
+ agentId: input.agentId,
371
+ accountId,
372
+ routeSessionKey: input.sessionKey,
373
+ dispatchSessionKey: input.sessionKey,
374
+ persistedSessionKey: input.sessionKey,
375
+ mainSessionKey: input.sessionKey,
376
+ createIfMissing: true
377
+ },
378
+ reply: {
379
+ to: input.userId,
380
+ originatingTo: input.userId,
381
+ replyTarget: input.userId,
382
+ sourceReplyDeliveryMode: "direct"
383
+ },
384
+ access: {
385
+ dm: {
386
+ policy: "open",
387
+ allowed: true,
388
+ allowFrom: []
389
+ },
390
+ group: {
391
+ isGroup: false,
392
+ routeAllowed: true,
393
+ senderAllowed: true,
394
+ requireMention: false
395
+ },
396
+ commands: {
397
+ authorized: true
398
+ },
399
+ mentions: {
400
+ canDetectMention: false,
401
+ wasMentioned: true
402
+ }
403
+ },
404
+ message: {
405
+ body: turnInput.rawText,
406
+ rawBody: turnInput.rawText,
407
+ bodyForAgent: turnInput.textForAgent,
408
+ commandBody: turnInput.textForCommands,
409
+ envelopeFrom: senderLabel,
410
+ senderLabel,
411
+ preview: turnInput.rawText.slice(0, 120)
412
+ },
413
+ delivery: {
414
+ deliver: async (payload) => {
415
+ const text = String(payload?.text || payload?.body || "");
416
+ if (!text) return { visibleReplySent: false };
417
+ const replyId = `sumeclaw-${Date.now()}`;
418
+ if (input.stream) {
419
+ appendPendingReplyText(accountId, input.userId, text);
420
+ sendAgentDelta({
421
+ requestId: input.requestId,
422
+ userId: input.userId,
423
+ sessionKey: input.sessionKey,
424
+ text,
425
+ messageId: replyId
426
+ });
427
+ } else {
428
+ appendPendingReplyText(accountId, input.userId, text);
429
+ }
430
+ return {
431
+ messageIds: [replyId],
432
+ visibleReplySent: true
433
+ };
434
+ },
435
+ onError(err) {
436
+ console.error("[sumeclaw] \u274C deliver error", err);
437
+ }
438
+ }
439
+ };
440
+ }
441
+ }
442
+ });
443
+ console.log("[sumeclaw] \u2705 OpenClaw turn dispatched", result?.admission ?? "");
444
+ if (input.stream) {
445
+ finishStreamReply(input);
446
+ } else {
447
+ finishBufferedReply(input);
448
+ }
449
+ } catch (err) {
450
+ console.error("[sumeclaw] \u274C OpenClaw turn dispatch failed", err);
451
+ sendAgentReply({
452
+ requestId: input.requestId,
453
+ userId: input.userId,
454
+ sessionKey: input.sessionKey,
455
+ text: "OpenClaw \u672C\u5730\u5904\u7406\u6D88\u606F\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002",
456
+ ok: false
457
+ });
458
+ }
459
+ }
460
+ async function dispatchViaDirectDmRuntime(runtime, input) {
461
+ const now = Date.now();
462
+ const messageId = String(input.messageId || `${input.sessionKey}:${now}`);
463
+ const senderLabel = `\u53CB\u867E\u7528\u6237 ${input.userId}`;
464
+ const baseCfg = api?.getConfig?.() ?? api?.config ?? runtime?.config ?? {};
465
+ const cfg = input.stream ? {
466
+ ...baseCfg,
467
+ agents: {
468
+ ...baseCfg.agents ?? {},
469
+ defaults: {
470
+ ...baseCfg.agents?.defaults ?? {},
471
+ blockStreamingDefault: "on",
472
+ blockStreamingBreak: "text_end",
473
+ blockStreamingChunk: {
474
+ minChars: 12,
475
+ maxChars: 80,
476
+ breakPreference: "sentence",
477
+ ...baseCfg.agents?.defaults?.blockStreamingChunk ?? {}
478
+ }
479
+ }
480
+ },
481
+ channels: {
482
+ ...baseCfg.channels ?? {},
483
+ sumeclaw: {
484
+ ...baseCfg.channels?.sumeclaw ?? {},
485
+ blockStreaming: true
486
+ }
487
+ }
488
+ } : baseCfg;
489
+ try {
490
+ await dispatchInboundDirectDmWithRuntime({
491
+ cfg,
492
+ runtime,
493
+ channel: "sumeclaw",
494
+ channelLabel: "\u53CB\u867E\u540D\u7247",
495
+ accountId,
496
+ peer: {
497
+ kind: "direct",
498
+ id: input.userId
499
+ },
500
+ senderId: input.userId,
501
+ senderAddress: input.userId,
502
+ recipientAddress: "sumeclaw",
503
+ conversationLabel: senderLabel,
504
+ rawBody: input.message,
505
+ messageId,
506
+ timestamp: now,
507
+ bodyForAgent: input.message,
508
+ commandBody: input.message,
509
+ commandAuthorized: true,
510
+ provider: "sumeclaw",
511
+ surface: "sumeclaw",
512
+ originatingChannel: "sumeclaw",
513
+ originatingTo: input.userId,
514
+ extraContext: {
515
+ AccountId: accountId,
516
+ RouteAgentId: input.agentId,
517
+ RequestId: input.requestId
518
+ },
519
+ deliver: async (payload) => {
520
+ const text = String(payload?.text || payload?.body || "");
521
+ if (!text) return { visibleReplySent: false };
522
+ const replyId = `sumeclaw-${Date.now()}`;
523
+ if (input.stream) {
524
+ appendPendingReplyText(accountId, input.userId, text);
525
+ sendAgentDelta({
526
+ requestId: input.requestId,
527
+ userId: input.userId,
528
+ sessionKey: input.sessionKey,
529
+ text,
530
+ messageId: replyId
531
+ });
532
+ } else {
533
+ appendPendingReplyText(accountId, input.userId, text);
534
+ }
535
+ return {
536
+ messageIds: [replyId],
537
+ visibleReplySent: true
538
+ };
539
+ },
540
+ onDispatchError: (err) => {
541
+ console.error("[sumeclaw] \u274C Direct DM dispatch error", err);
542
+ },
543
+ onRecordError: (err) => {
544
+ console.error("[sumeclaw] \u274C Direct DM record error", err);
545
+ }
546
+ });
547
+ console.log("[sumeclaw] \u2705 OpenClaw Direct DM dispatched");
548
+ if (input.stream) {
549
+ finishStreamReply(input);
550
+ } else {
551
+ finishBufferedReply(input);
552
+ }
553
+ } catch (err) {
554
+ console.error("[sumeclaw] \u274C Direct DM dispatch failed", err);
555
+ sendAgentReply({
556
+ requestId: input.requestId,
557
+ userId: input.userId,
558
+ sessionKey: input.sessionKey,
559
+ text: "OpenClaw \u672C\u5730\u5904\u7406\u6D88\u606F\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002",
560
+ ok: false
561
+ });
562
+ }
563
+ }
564
+ function cleanup() {
565
+ connectingAccounts.delete(accountId);
566
+ if (heartbeatTimer) {
567
+ clearInterval(heartbeatTimer);
568
+ heartbeatTimer = null;
569
+ }
570
+ if (connections.get(accountId) === ws) {
571
+ connections.delete(accountId);
572
+ }
573
+ }
574
+ function scheduleReconnect() {
575
+ if (reconnectTimer) return;
576
+ console.log(`[sumeclaw] \u{1F501} reconnecting in ${RECONNECT_DELAY / 1e3}s`);
577
+ reconnectTimer = setTimeout(() => {
578
+ reconnectTimer = null;
579
+ start();
580
+ }, RECONNECT_DELAY);
581
+ }
582
+ start();
583
+ }
584
+
585
+ // src/channel.ts
586
+ import {
587
+ createChatChannelPlugin,
588
+ createChannelPluginBase
589
+ } from "openclaw/plugin-sdk/channel-core";
590
+ var channelKey = "sumeclaw";
591
+ var channelConfigAdapter = {
592
+ listAccountIds(cfg) {
593
+ const accounts = cfg.channels?.[channelKey]?.accounts ?? {};
594
+ return Object.keys(accounts);
595
+ },
596
+ resolveAccount(cfg, accountId) {
597
+ const accounts = cfg.channels?.[channelKey]?.accounts ?? {};
598
+ return accounts[accountId ?? "default"] ?? {};
599
+ }
600
+ };
601
+ var setupWizard = {
602
+ channel: channelKey,
603
+ /** 根据当前配置显示连接状态 */
604
+ status: {
605
+ configuredLabel: "\u5DF2\u8FDE\u63A5\u53CB\u867E\u540D\u7247",
606
+ unconfiguredLabel: "\u5C1A\u672A\u914D\u7F6E",
607
+ resolveConfigured: ({ cfg }) => {
608
+ const accounts = cfg.channels?.[channelKey]?.accounts ?? {};
609
+ return Object.values(accounts).some((acct) => acct?.botToken);
610
+ }
611
+ },
612
+ /** 交互式凭据收集 — 引导用户输入 botToken */
613
+ credentials: [
614
+ {
615
+ inputKey: "botToken",
616
+ credentialLabel: "\u53CB\u867E\u8FDE\u63A5\u7801",
617
+ preferredEnvVar: "SUMECLAW_BOT_TOKEN",
618
+ envPrompt: "\u662F\u5426\u4F7F\u7528\u73AF\u5883\u53D8\u91CF SUMECLAW_BOT_TOKEN\uFF1F",
619
+ keepPrompt: "\u4FDD\u7559\u5F53\u524D botToken\uFF1F",
620
+ inputPrompt: "\u8BF7\u8F93\u5165\u4ECE\u53CB\u867E\u5C0F\u7A0B\u5E8F\u83B7\u53D6\u7684 botToken\uFF1A",
621
+ inspect: ({ cfg, accountId }) => {
622
+ const token = cfg.channels?.[channelKey]?.accounts?.[accountId ?? "default"]?.botToken;
623
+ return {
624
+ accountConfigured: Boolean(token),
625
+ hasConfiguredValue: Boolean(token)
626
+ };
627
+ }
628
+ }
629
+ ]
630
+ };
631
+ var sumeclawPlugin = createChatChannelPlugin({
632
+ base: createChannelPluginBase({
633
+ id: channelKey,
634
+ setupWizard,
635
+ setup: {
636
+ /**
637
+ * 从用户配置中解析账户信息
638
+ */
639
+ resolveAccount(cfg, accountId) {
640
+ const accounts = cfg.channels?.[channelKey]?.accounts ?? {};
641
+ return accounts[accountId ?? "default"] ?? {};
642
+ },
643
+ /**
644
+ * 检查账户配置状态
645
+ */
646
+ inspectAccount(cfg, accountId) {
647
+ const account = cfg.channels?.[channelKey]?.accounts?.[accountId ?? "default"];
648
+ return {
649
+ enabled: !!account?.botToken,
650
+ configured: !!account?.botToken
651
+ };
652
+ }
653
+ }
654
+ }),
655
+ /**
656
+ * 渠道配置适配器 (ChannelConfigAdapter)
657
+ */
658
+ config: channelConfigAdapter,
659
+ /**
660
+ * DM 安全策略
661
+ *
662
+ * 友虾名片场景下,所有通过名片访问的用户都允许对话。
663
+ * 名片主可在小程序端管理黑名单(后续扩展)。
664
+ */
665
+ security: {
666
+ dm: {
667
+ channelKey: "sumeclaw",
668
+ resolvePolicy: () => "open",
669
+ defaultPolicy: "open"
670
+ }
671
+ },
672
+ /**
673
+ * 出站消息处理
674
+ *
675
+ * 当 OpenClaw Agent 生成回复后,通过 WebSocket 推回友虾名片平台。
676
+ */
677
+ outbound: {
678
+ attachedResults: {
679
+ sendText: async ({ text, to, accountId }) => {
680
+ const resolvedAccountId = accountId ?? "default";
681
+ const ws = getWebSocket(resolvedAccountId);
682
+ if (!ws || ws.readyState !== 1) {
683
+ throw new Error("WebSocket \u672A\u8FDE\u63A5\u5230\u53CB\u867E\u540D\u7247\u5E73\u53F0");
684
+ }
685
+ const pending = getPendingReplyContext(resolvedAccountId, to);
686
+ const messageId = `sumeclaw-${Date.now()}`;
687
+ if (pending?.stream) {
688
+ appendPendingReplyText(resolvedAccountId, to, text);
689
+ ws.send(
690
+ JSON.stringify({
691
+ type: "agent_delta",
692
+ requestId: pending.requestId,
693
+ userId: to,
694
+ sessionKey: pending.sessionKey,
695
+ text,
696
+ messageId
697
+ })
698
+ );
699
+ console.log("[sumeclaw] \u{1F4E4} outbound agent_delta sent", {
700
+ accountId: resolvedAccountId,
701
+ requestId: pending.requestId,
702
+ userId: to,
703
+ textLength: text.length
704
+ });
705
+ return { messageId };
706
+ }
707
+ if (pending && !pending.stream) {
708
+ appendPendingReplyText(resolvedAccountId, to, text);
709
+ console.log("[sumeclaw] \u{1F4E5} outbound reply buffered", {
710
+ accountId: resolvedAccountId,
711
+ requestId: pending.requestId,
712
+ userId: to,
713
+ textLength: text.length
714
+ });
715
+ return { messageId };
716
+ }
717
+ const finalPending = consumePendingReplyContext(resolvedAccountId, to);
718
+ ws.send(
719
+ JSON.stringify({
720
+ type: "agent_reply",
721
+ requestId: finalPending?.requestId,
722
+ userId: to,
723
+ sessionKey: finalPending?.sessionKey,
724
+ text,
725
+ messageId
726
+ })
727
+ );
728
+ console.log("[sumeclaw] \u{1F4E4} outbound agent_reply sent", {
729
+ accountId: resolvedAccountId,
730
+ requestId: finalPending?.requestId,
731
+ userId: to,
732
+ textLength: text.length
733
+ });
734
+ return { messageId };
735
+ }
736
+ }
737
+ }
738
+ });
739
+ sumeclawPlugin.config ??= channelConfigAdapter;
740
+
741
+ export {
742
+ setSumeclawRuntime,
743
+ connectGateway,
744
+ sumeclawPlugin
745
+ };
746
+ //# sourceMappingURL=chunk-RIUKFLMY.js.map