@partme.ai/openclaw-web-stomp 0.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/LICENSE +21 -0
- package/README.md +155 -0
- package/README_CN.md +137 -0
- package/RELEASING.md +77 -0
- package/dist/chunk-ORWEBPDJ.js +379 -0
- package/dist/chunk-ORWEBPDJ.js.map +1 -0
- package/dist/chunk-PKJKPVTH.js +4333 -0
- package/dist/chunk-PKJKPVTH.js.map +1 -0
- package/dist/index.d.ts +110 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/setup-entry.d.ts +6 -0
- package/dist/setup-entry.js +16 -0
- package/dist/setup-entry.js.map +1 -0
- package/dist/stomp-server-FJO6VDEE.js +15 -0
- package/dist/stomp-server-FJO6VDEE.js.map +1 -0
- package/openclaw.plugin.json +130 -0
- package/package.json +89 -0
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getAckStats,
|
|
3
|
+
getConnectionInfoList,
|
|
4
|
+
getStompServerStats,
|
|
5
|
+
getSubscriptionStats,
|
|
6
|
+
publishToDestination,
|
|
7
|
+
startStompServer,
|
|
8
|
+
stopStompServer
|
|
9
|
+
} from "./chunk-PKJKPVTH.js";
|
|
10
|
+
|
|
11
|
+
// src/stomp-config.ts
|
|
12
|
+
var CHANNEL_KEY = "stomp-ws";
|
|
13
|
+
var DEFAULT_STOMP_WS_CONFIG = {
|
|
14
|
+
wsPort: 15674,
|
|
15
|
+
path: "/ws",
|
|
16
|
+
host: "0.0.0.0",
|
|
17
|
+
heartbeatIncoming: 1e4,
|
|
18
|
+
heartbeatOutgoing: 1e4,
|
|
19
|
+
maxConnections: 500,
|
|
20
|
+
maxFrameSize: 1024 * 1024,
|
|
21
|
+
subscribeTopics: [],
|
|
22
|
+
topicBindings: [],
|
|
23
|
+
defaultAckMode: "auto",
|
|
24
|
+
prefetchCount: 10,
|
|
25
|
+
auth: {
|
|
26
|
+
required: false,
|
|
27
|
+
allowAnonymous: true,
|
|
28
|
+
users: []
|
|
29
|
+
},
|
|
30
|
+
tls: {
|
|
31
|
+
enabled: false
|
|
32
|
+
},
|
|
33
|
+
limits: {
|
|
34
|
+
maxPayloadBytes: 1024 * 1024,
|
|
35
|
+
maxSubscriptionsPerClient: 200
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
function asInt(n, fallback) {
|
|
39
|
+
if (typeof n === "number" && Number.isFinite(n)) return Math.floor(n);
|
|
40
|
+
if (typeof n === "string" && n.trim() && Number.isFinite(Number(n))) return Math.floor(Number(n));
|
|
41
|
+
return fallback;
|
|
42
|
+
}
|
|
43
|
+
function normalizePath(p) {
|
|
44
|
+
const t = p.trim();
|
|
45
|
+
if (!t) return "/ws";
|
|
46
|
+
return t.startsWith("/") ? t : `/${t}`;
|
|
47
|
+
}
|
|
48
|
+
function normalizeBindings(input) {
|
|
49
|
+
if (!Array.isArray(input)) return [];
|
|
50
|
+
const out = [];
|
|
51
|
+
for (const row of input) {
|
|
52
|
+
if (!row || typeof row !== "object") continue;
|
|
53
|
+
const r = row;
|
|
54
|
+
if (!r.topicPattern || !r.agentId) continue;
|
|
55
|
+
out.push({
|
|
56
|
+
topicPattern: r.topicPattern,
|
|
57
|
+
agentId: r.agentId,
|
|
58
|
+
accountId: r.accountId,
|
|
59
|
+
replyTopic: r.replyTopic
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
function normalizeUsers(input) {
|
|
65
|
+
if (!Array.isArray(input)) return [];
|
|
66
|
+
const out = [];
|
|
67
|
+
for (const row of input) {
|
|
68
|
+
if (!row || typeof row !== "object") continue;
|
|
69
|
+
const u = row;
|
|
70
|
+
if (!u.username) continue;
|
|
71
|
+
out.push({
|
|
72
|
+
username: u.username,
|
|
73
|
+
password: typeof u.password === "string" ? u.password : void 0,
|
|
74
|
+
publishAllow: Array.isArray(u.publishAllow) ? u.publishAllow.filter((x) => typeof x === "string") : void 0,
|
|
75
|
+
subscribeAllow: Array.isArray(u.subscribeAllow) ? u.subscribeAllow.filter((x) => typeof x === "string") : void 0,
|
|
76
|
+
aclRules: Array.isArray(u.aclRules) ? u.aclRules : void 0
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
function resolveStompWsConfig(globalConfig) {
|
|
82
|
+
const channels = globalConfig.channels ?? {};
|
|
83
|
+
const raw = channels[CHANNEL_KEY] ?? {};
|
|
84
|
+
const legacy = channels.stomp ?? {};
|
|
85
|
+
const merged = { ...legacy, ...raw };
|
|
86
|
+
const hb = merged.heartbeat ?? {};
|
|
87
|
+
const auth = merged.auth ?? {};
|
|
88
|
+
const tls = merged.tls ?? {};
|
|
89
|
+
const limits = merged.limits ?? {};
|
|
90
|
+
return {
|
|
91
|
+
wsPort: asInt(merged.wsPort ?? merged.port, DEFAULT_STOMP_WS_CONFIG.wsPort),
|
|
92
|
+
path: normalizePath(typeof merged.path === "string" ? merged.path : DEFAULT_STOMP_WS_CONFIG.path),
|
|
93
|
+
host: typeof merged.host === "string" && merged.host.trim() ? merged.host.trim() : DEFAULT_STOMP_WS_CONFIG.host,
|
|
94
|
+
heartbeatIncoming: asInt(
|
|
95
|
+
merged.heartbeatIncoming ?? hb.clientMs,
|
|
96
|
+
DEFAULT_STOMP_WS_CONFIG.heartbeatIncoming
|
|
97
|
+
),
|
|
98
|
+
heartbeatOutgoing: asInt(
|
|
99
|
+
merged.heartbeatOutgoing ?? hb.serverMs,
|
|
100
|
+
DEFAULT_STOMP_WS_CONFIG.heartbeatOutgoing
|
|
101
|
+
),
|
|
102
|
+
maxConnections: asInt(merged.maxConnections, DEFAULT_STOMP_WS_CONFIG.maxConnections),
|
|
103
|
+
maxFrameSize: asInt(merged.maxFrameSize, DEFAULT_STOMP_WS_CONFIG.maxFrameSize),
|
|
104
|
+
subscribeTopics: Array.isArray(merged.subscribeTopics) ? merged.subscribeTopics.filter((t) => typeof t === "string" && t.length > 0) : DEFAULT_STOMP_WS_CONFIG.subscribeTopics,
|
|
105
|
+
topicBindings: normalizeBindings(merged.topicBindings),
|
|
106
|
+
defaultAckMode: merged.defaultAckMode === "client" || merged.defaultAckMode === "client-individual" ? merged.defaultAckMode : DEFAULT_STOMP_WS_CONFIG.defaultAckMode,
|
|
107
|
+
prefetchCount: asInt(merged.prefetchCount, DEFAULT_STOMP_WS_CONFIG.prefetchCount),
|
|
108
|
+
auth: {
|
|
109
|
+
required: auth.required !== false,
|
|
110
|
+
allowAnonymous: auth.allowAnonymous === true,
|
|
111
|
+
defaultUser: typeof auth.defaultUser === "string" ? auth.defaultUser : void 0,
|
|
112
|
+
defaultPass: typeof auth.defaultPass === "string" ? auth.defaultPass : void 0,
|
|
113
|
+
users: normalizeUsers(auth.users)
|
|
114
|
+
},
|
|
115
|
+
tls: {
|
|
116
|
+
enabled: tls.enabled === true,
|
|
117
|
+
certFile: typeof tls.certFile === "string" ? tls.certFile : void 0,
|
|
118
|
+
keyFile: typeof tls.keyFile === "string" ? tls.keyFile : void 0,
|
|
119
|
+
caFile: typeof tls.caFile === "string" ? tls.caFile : void 0
|
|
120
|
+
},
|
|
121
|
+
limits: {
|
|
122
|
+
maxPayloadBytes: asInt(limits.maxPayloadBytes, DEFAULT_STOMP_WS_CONFIG.limits.maxPayloadBytes),
|
|
123
|
+
maxSubscriptionsPerClient: asInt(
|
|
124
|
+
limits.maxSubscriptionsPerClient,
|
|
125
|
+
DEFAULT_STOMP_WS_CONFIG.limits.maxSubscriptionsPerClient
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function buildStompWsConfigSnapshot(config) {
|
|
131
|
+
return {
|
|
132
|
+
wsPort: config.wsPort,
|
|
133
|
+
path: config.path,
|
|
134
|
+
host: config.host,
|
|
135
|
+
heartbeatIncoming: config.heartbeatIncoming,
|
|
136
|
+
heartbeatOutgoing: config.heartbeatOutgoing,
|
|
137
|
+
maxConnections: config.maxConnections,
|
|
138
|
+
maxFrameSize: config.maxFrameSize,
|
|
139
|
+
subscribeTopics: config.subscribeTopics,
|
|
140
|
+
topicBindings: config.topicBindings.map((b) => ({
|
|
141
|
+
topicPattern: b.topicPattern,
|
|
142
|
+
agentId: b.agentId,
|
|
143
|
+
accountId: b.accountId,
|
|
144
|
+
replyTopic: b.replyTopic
|
|
145
|
+
})),
|
|
146
|
+
defaultAckMode: config.defaultAckMode,
|
|
147
|
+
prefetchCount: config.prefetchCount,
|
|
148
|
+
auth: {
|
|
149
|
+
required: config.auth.required,
|
|
150
|
+
allowAnonymous: config.auth.allowAnonymous,
|
|
151
|
+
userCount: config.auth.users.length,
|
|
152
|
+
hasDefaultCredentials: Boolean(config.auth.defaultUser && config.auth.defaultPass)
|
|
153
|
+
},
|
|
154
|
+
tls: { enabled: config.tls.enabled },
|
|
155
|
+
limits: config.limits
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function validateStompWsConfig(config) {
|
|
159
|
+
const issues = [];
|
|
160
|
+
if (config.auth.required && !config.auth.allowAnonymous && config.auth.users.length === 0 && (!config.auth.defaultUser || !config.auth.defaultPass)) {
|
|
161
|
+
issues.push("auth.required is true but no auth.users and no auth.defaultUser/defaultPass (set allowAnonymous or add users)");
|
|
162
|
+
}
|
|
163
|
+
if (config.tls.enabled && (!config.tls.certFile || !config.tls.keyFile)) {
|
|
164
|
+
issues.push("tls.enabled requires tls.certFile and tls.keyFile (not wired in this build \u2014 use reverse proxy TLS)");
|
|
165
|
+
}
|
|
166
|
+
return issues;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/stomp-state.ts
|
|
170
|
+
var active = null;
|
|
171
|
+
function setStompWsChannelConfig(config) {
|
|
172
|
+
active = config;
|
|
173
|
+
}
|
|
174
|
+
function getStompWsChannelConfig() {
|
|
175
|
+
return active;
|
|
176
|
+
}
|
|
177
|
+
function clearStompWsChannelConfig() {
|
|
178
|
+
active = null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/runtime.ts
|
|
182
|
+
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
|
|
183
|
+
var runtimeStore = createPluginRuntimeStore("openclaw-web-stomp runtime not initialized");
|
|
184
|
+
function setStompWsRuntime(runtime) {
|
|
185
|
+
runtimeStore.setRuntime(runtime);
|
|
186
|
+
}
|
|
187
|
+
function tryGetStompWsRuntime() {
|
|
188
|
+
return runtimeStore.tryGetRuntime();
|
|
189
|
+
}
|
|
190
|
+
function getStompWsRuntime() {
|
|
191
|
+
return runtimeStore.getRuntime();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/destination-router.ts
|
|
195
|
+
function buildSessionDestination(sessionKey) {
|
|
196
|
+
return `/topic/session.${sessionKey}`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/outbound.ts
|
|
200
|
+
function publishOutboundSessionText(sessionKey, text) {
|
|
201
|
+
const destination = buildSessionDestination(sessionKey);
|
|
202
|
+
publishToDestination(destination, text);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/dm-scope.ts
|
|
206
|
+
function resolveDmScopeFromRuntimeConfig(cfg) {
|
|
207
|
+
const rawScope = cfg.session?.dmScope;
|
|
208
|
+
const allowed = /* @__PURE__ */ new Set(["main", "per-peer", "per-channel-peer", "per-account-channel-peer"]);
|
|
209
|
+
if (typeof rawScope === "string" && allowed.has(rawScope)) {
|
|
210
|
+
return rawScope;
|
|
211
|
+
}
|
|
212
|
+
return "main";
|
|
213
|
+
}
|
|
214
|
+
function buildSessionKeyFromDmScope(params) {
|
|
215
|
+
const dmScope = resolveDmScopeFromRuntimeConfig(params.cfg);
|
|
216
|
+
const agent = normalizeToken(params.agentId) || "main";
|
|
217
|
+
const channel = normalizeToken(params.channel) || "unknown";
|
|
218
|
+
const accountId = normalizeToken(params.accountId) || "default";
|
|
219
|
+
const peerId = normalizeToken(params.peerId);
|
|
220
|
+
if (!peerId || dmScope === "main") {
|
|
221
|
+
return `agent:${agent}:main`;
|
|
222
|
+
}
|
|
223
|
+
if (dmScope === "per-account-channel-peer") {
|
|
224
|
+
return `agent:${agent}:${channel}:${accountId}:direct:${peerId}`;
|
|
225
|
+
}
|
|
226
|
+
if (dmScope === "per-channel-peer") {
|
|
227
|
+
return `agent:${agent}:${channel}:direct:${peerId}`;
|
|
228
|
+
}
|
|
229
|
+
return `agent:${agent}:direct:${peerId}`;
|
|
230
|
+
}
|
|
231
|
+
function normalizeToken(value) {
|
|
232
|
+
return value.trim().toLowerCase();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// src/inbound.ts
|
|
236
|
+
var CHANNEL_ID = "stomp-ws";
|
|
237
|
+
async function dispatchInboundToRuntime(message) {
|
|
238
|
+
const runtime = tryGetStompWsRuntime();
|
|
239
|
+
if (!runtime) {
|
|
240
|
+
console.warn("[openclaw-web-stomp] Runtime not initialized, drop inbound");
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const cfg = runtime.config;
|
|
244
|
+
const route = await runtime.channel.routing.resolveAgentRoute({
|
|
245
|
+
cfg,
|
|
246
|
+
channel: CHANNEL_ID,
|
|
247
|
+
accountId: message.accountId,
|
|
248
|
+
peer: { kind: "dm", id: message.peerId }
|
|
249
|
+
});
|
|
250
|
+
const resolvedAgentId = typeof route.agentId === "string" && route.agentId.trim().length > 0 ? route.agentId : message.agentId;
|
|
251
|
+
const sessionKey = buildSessionKeyFromDmScope({
|
|
252
|
+
cfg,
|
|
253
|
+
agentId: resolvedAgentId,
|
|
254
|
+
channel: CHANNEL_ID,
|
|
255
|
+
accountId: message.accountId,
|
|
256
|
+
peerId: message.peerId
|
|
257
|
+
});
|
|
258
|
+
const replyDestination = message.replyDestination ?? buildSessionDestination(sessionKey);
|
|
259
|
+
const ctx = await runtime.channel.reply.finalizeInboundContext({
|
|
260
|
+
channel: CHANNEL_ID,
|
|
261
|
+
accountId: message.accountId,
|
|
262
|
+
from: message.peerId,
|
|
263
|
+
text: message.text,
|
|
264
|
+
chatType: "direct",
|
|
265
|
+
extra: {
|
|
266
|
+
stompDestination: message.destination,
|
|
267
|
+
stompReplyDestination: replyDestination,
|
|
268
|
+
sessionKey
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
const dispatcher = runtime.channel.reply.createReplyDispatcherWithTyping({
|
|
272
|
+
deliver: async (payload) => {
|
|
273
|
+
const { publishToDestination: publishToDestination2 } = await import("./stomp-server-FJO6VDEE.js");
|
|
274
|
+
publishToDestination2(replyDestination, payload.text);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
await runtime.channel.reply.dispatchReplyFromConfig({
|
|
278
|
+
ctx,
|
|
279
|
+
cfg,
|
|
280
|
+
dispatcher,
|
|
281
|
+
replyOptions: route
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
function dispatchInboundFireAndForget(message) {
|
|
285
|
+
dispatchInboundToRuntime(message).catch((err) => {
|
|
286
|
+
console.error("[openclaw-web-stomp] Runtime dispatch failed:", err);
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/channel.ts
|
|
291
|
+
var DEFAULT_ACCOUNT_ID = "default";
|
|
292
|
+
var stompWsChannel = {
|
|
293
|
+
id: "stomp-ws",
|
|
294
|
+
name: "STOMP over WebSocket",
|
|
295
|
+
meta: {
|
|
296
|
+
id: "stomp-ws",
|
|
297
|
+
label: "STOMP over WebSocket",
|
|
298
|
+
selectionLabel: "STOMP over WebSocket (Browser)",
|
|
299
|
+
docsPath: "/channels/stomp-ws",
|
|
300
|
+
blurb: "STOMP over WebSocket with topic bindings and session.dmScope isolation.",
|
|
301
|
+
aliases: ["stomp-ws", "web-stomp", "stomp", "ws-stomp"],
|
|
302
|
+
order: 90
|
|
303
|
+
},
|
|
304
|
+
capabilities: { chatTypes: ["direct"] },
|
|
305
|
+
reload: { configPrefixes: ["channels.stomp-ws", "channels.stomp"] },
|
|
306
|
+
config: {
|
|
307
|
+
listAccountIds: () => [DEFAULT_ACCOUNT_ID],
|
|
308
|
+
resolveAccount: (cfg) => {
|
|
309
|
+
const config = resolveStompWsConfig(cfg);
|
|
310
|
+
return {
|
|
311
|
+
accountId: DEFAULT_ACCOUNT_ID,
|
|
312
|
+
name: "STOMP over WebSocket",
|
|
313
|
+
enabled: true,
|
|
314
|
+
configured: Boolean(config.wsPort && config.path)
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
status: {
|
|
319
|
+
buildAccountSnapshot: (cfg) => {
|
|
320
|
+
const config = resolveStompWsConfig(cfg);
|
|
321
|
+
const active2 = getStompWsChannelConfig() ?? config;
|
|
322
|
+
return {
|
|
323
|
+
accountId: DEFAULT_ACCOUNT_ID,
|
|
324
|
+
name: "STOMP over WebSocket",
|
|
325
|
+
enabled: true,
|
|
326
|
+
configured: true,
|
|
327
|
+
webhookPath: "/stomp-ws/status",
|
|
328
|
+
port: active2.wsPort,
|
|
329
|
+
extra: {
|
|
330
|
+
stats: getStompServerStats(),
|
|
331
|
+
connections: getConnectionInfoList(),
|
|
332
|
+
subscriptions: getSubscriptionStats(),
|
|
333
|
+
ack: getAckStats()
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
gateway: {
|
|
339
|
+
startAccount: async ({
|
|
340
|
+
runtime,
|
|
341
|
+
abortSignal
|
|
342
|
+
}) => {
|
|
343
|
+
const config = resolveStompWsConfig(runtime.config);
|
|
344
|
+
setStompWsChannelConfig(config);
|
|
345
|
+
for (const issue of validateStompWsConfig(config)) {
|
|
346
|
+
console.warn(`[openclaw-web-stomp] config warning: ${issue}`);
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
await startStompServer(config, (message) => {
|
|
350
|
+
dispatchInboundFireAndForget(message);
|
|
351
|
+
});
|
|
352
|
+
await new Promise((resolve) => {
|
|
353
|
+
const onAbort = () => resolve();
|
|
354
|
+
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
355
|
+
});
|
|
356
|
+
} finally {
|
|
357
|
+
await stopStompServer().catch(() => void 0);
|
|
358
|
+
clearStompWsChannelConfig();
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
outbound: {
|
|
363
|
+
deliveryMode: "direct",
|
|
364
|
+
sendText: async (sessionKey, text) => {
|
|
365
|
+
publishOutboundSessionText(sessionKey, text);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
export {
|
|
371
|
+
resolveStompWsConfig,
|
|
372
|
+
buildStompWsConfigSnapshot,
|
|
373
|
+
getStompWsChannelConfig,
|
|
374
|
+
setStompWsRuntime,
|
|
375
|
+
tryGetStompWsRuntime,
|
|
376
|
+
getStompWsRuntime,
|
|
377
|
+
stompWsChannel
|
|
378
|
+
};
|
|
379
|
+
//# sourceMappingURL=chunk-ORWEBPDJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/stomp-config.ts","../src/stomp-state.ts","../src/runtime.ts","../src/destination-router.ts","../src/outbound.ts","../src/dm-scope.ts","../src/inbound.ts","../src/channel.ts"],"sourcesContent":["/**\n * 解析 `channels[\"stomp-ws\"]` 为运行时配置(默认值与 stomp-tcp / web-mqtt 对齐)。\n */\n\nimport type { StompWsConfig, StompWsUser, TopicBinding } from \"./types.js\";\n\nconst CHANNEL_KEY = \"stomp-ws\";\n\nexport const DEFAULT_STOMP_WS_CONFIG: StompWsConfig = {\n wsPort: 15674,\n path: \"/ws\",\n host: \"0.0.0.0\",\n heartbeatIncoming: 10_000,\n heartbeatOutgoing: 10_000,\n maxConnections: 500,\n maxFrameSize: 1024 * 1024,\n subscribeTopics: [],\n topicBindings: [],\n defaultAckMode: \"auto\",\n prefetchCount: 10,\n auth: {\n required: false,\n allowAnonymous: true,\n users: [],\n },\n tls: {\n enabled: false,\n },\n limits: {\n maxPayloadBytes: 1024 * 1024,\n maxSubscriptionsPerClient: 200,\n },\n};\n\nfunction asInt(n: unknown, fallback: number): number {\n if (typeof n === \"number\" && Number.isFinite(n)) return Math.floor(n);\n if (typeof n === \"string\" && n.trim() && Number.isFinite(Number(n))) return Math.floor(Number(n));\n return fallback;\n}\n\nfunction normalizePath(p: string): string {\n const t = p.trim();\n if (!t) return \"/ws\";\n return t.startsWith(\"/\") ? t : `/${t}`;\n}\n\nfunction normalizeBindings(input: unknown): TopicBinding[] {\n if (!Array.isArray(input)) return [];\n const out: TopicBinding[] = [];\n for (const row of input) {\n if (!row || typeof row !== \"object\") continue;\n const r = row as Partial<TopicBinding>;\n if (!r.topicPattern || !r.agentId) continue;\n out.push({\n topicPattern: r.topicPattern,\n agentId: r.agentId,\n accountId: r.accountId,\n replyTopic: r.replyTopic,\n });\n }\n return out;\n}\n\nfunction normalizeUsers(input: unknown): StompWsUser[] {\n if (!Array.isArray(input)) return [];\n const out: StompWsUser[] = [];\n for (const row of input) {\n if (!row || typeof row !== \"object\") continue;\n const u = row as Partial<StompWsUser>;\n if (!u.username) continue;\n out.push({\n username: u.username,\n password: typeof u.password === \"string\" ? u.password : undefined,\n publishAllow: Array.isArray(u.publishAllow) ? u.publishAllow.filter((x): x is string => typeof x === \"string\") : undefined,\n subscribeAllow: Array.isArray(u.subscribeAllow)\n ? u.subscribeAllow.filter((x): x is string => typeof x === \"string\")\n : undefined,\n aclRules: Array.isArray(u.aclRules) ? (u.aclRules as StompWsUser[\"aclRules\"]) : undefined,\n });\n }\n return out;\n}\n\n/**\n * 从全局 OpenClaw 配置解析 stomp-ws 段。\n */\nexport function resolveStompWsConfig(globalConfig: Record<string, unknown>): StompWsConfig {\n const channels = (globalConfig.channels as Record<string, unknown> | undefined) ?? {};\n const raw = (channels[CHANNEL_KEY] ?? {}) as Record<string, unknown>;\n const legacy = (channels.stomp as Record<string, unknown> | undefined) ?? {};\n const merged = { ...legacy, ...raw };\n\n const hb = (merged.heartbeat as Record<string, unknown> | undefined) ?? {};\n const auth = (merged.auth as Record<string, unknown> | undefined) ?? {};\n const tls = (merged.tls as Record<string, unknown> | undefined) ?? {};\n const limits = (merged.limits as Record<string, unknown> | undefined) ?? {};\n\n return {\n wsPort: asInt(merged.wsPort ?? merged.port, DEFAULT_STOMP_WS_CONFIG.wsPort),\n path: normalizePath(typeof merged.path === \"string\" ? merged.path : DEFAULT_STOMP_WS_CONFIG.path),\n host: typeof merged.host === \"string\" && merged.host.trim() ? merged.host.trim() : DEFAULT_STOMP_WS_CONFIG.host,\n heartbeatIncoming: asInt(\n merged.heartbeatIncoming ?? hb.clientMs,\n DEFAULT_STOMP_WS_CONFIG.heartbeatIncoming,\n ),\n heartbeatOutgoing: asInt(\n merged.heartbeatOutgoing ?? hb.serverMs,\n DEFAULT_STOMP_WS_CONFIG.heartbeatOutgoing,\n ),\n maxConnections: asInt(merged.maxConnections, DEFAULT_STOMP_WS_CONFIG.maxConnections),\n maxFrameSize: asInt(merged.maxFrameSize, DEFAULT_STOMP_WS_CONFIG.maxFrameSize),\n subscribeTopics: Array.isArray(merged.subscribeTopics)\n ? merged.subscribeTopics.filter((t): t is string => typeof t === \"string\" && t.length > 0)\n : DEFAULT_STOMP_WS_CONFIG.subscribeTopics,\n topicBindings: normalizeBindings(merged.topicBindings),\n defaultAckMode:\n merged.defaultAckMode === \"client\" || merged.defaultAckMode === \"client-individual\"\n ? merged.defaultAckMode\n : DEFAULT_STOMP_WS_CONFIG.defaultAckMode,\n prefetchCount: asInt(merged.prefetchCount, DEFAULT_STOMP_WS_CONFIG.prefetchCount),\n auth: {\n required: auth.required !== false,\n allowAnonymous: auth.allowAnonymous === true,\n defaultUser: typeof auth.defaultUser === \"string\" ? auth.defaultUser : undefined,\n defaultPass: typeof auth.defaultPass === \"string\" ? auth.defaultPass : undefined,\n users: normalizeUsers(auth.users),\n },\n tls: {\n enabled: tls.enabled === true,\n certFile: typeof tls.certFile === \"string\" ? tls.certFile : undefined,\n keyFile: typeof tls.keyFile === \"string\" ? tls.keyFile : undefined,\n caFile: typeof tls.caFile === \"string\" ? tls.caFile : undefined,\n },\n limits: {\n maxPayloadBytes: asInt(limits.maxPayloadBytes, DEFAULT_STOMP_WS_CONFIG.limits.maxPayloadBytes),\n maxSubscriptionsPerClient: asInt(\n limits.maxSubscriptionsPerClient,\n DEFAULT_STOMP_WS_CONFIG.limits.maxSubscriptionsPerClient,\n ),\n },\n };\n}\n\n/**\n * 脱敏配置快照(供 status 接口)。\n */\nexport function buildStompWsConfigSnapshot(config: StompWsConfig): Record<string, unknown> {\n return {\n wsPort: config.wsPort,\n path: config.path,\n host: config.host,\n heartbeatIncoming: config.heartbeatIncoming,\n heartbeatOutgoing: config.heartbeatOutgoing,\n maxConnections: config.maxConnections,\n maxFrameSize: config.maxFrameSize,\n subscribeTopics: config.subscribeTopics,\n topicBindings: config.topicBindings.map((b) => ({\n topicPattern: b.topicPattern,\n agentId: b.agentId,\n accountId: b.accountId,\n replyTopic: b.replyTopic,\n })),\n defaultAckMode: config.defaultAckMode,\n prefetchCount: config.prefetchCount,\n auth: {\n required: config.auth.required,\n allowAnonymous: config.auth.allowAnonymous,\n userCount: config.auth.users.length,\n hasDefaultCredentials: Boolean(config.auth.defaultUser && config.auth.defaultPass),\n },\n tls: { enabled: config.tls.enabled },\n limits: config.limits,\n };\n}\n\n/**\n * 配置校验提示(非致命)。\n */\nexport function validateStompWsConfig(config: StompWsConfig): string[] {\n const issues: string[] = [];\n if (\n config.auth.required &&\n !config.auth.allowAnonymous &&\n config.auth.users.length === 0 &&\n (!config.auth.defaultUser || !config.auth.defaultPass)\n ) {\n issues.push(\"auth.required is true but no auth.users and no auth.defaultUser/defaultPass (set allowAnonymous or add users)\");\n }\n if (config.tls.enabled && (!config.tls.certFile || !config.tls.keyFile)) {\n issues.push(\"tls.enabled requires tls.certFile and tls.keyFile (not wired in this build — use reverse proxy TLS)\");\n }\n return issues;\n}\n","/**\n * 当前 stomp-ws 渠道活跃配置(供 outbound 与 status 读取)。\n */\n\nimport type { StompWsConfig } from \"./types.js\";\nimport { DEFAULT_STOMP_WS_CONFIG } from \"./stomp-config.js\";\n\nlet active: StompWsConfig | null = null;\n\n/**\n * 设置当前网关账号启动后的配置快照。\n */\nexport function setStompWsChannelConfig(config: StompWsConfig): void {\n active = config;\n}\n\n/**\n * 读取活跃配置;未启动时返回 null。\n */\nexport function getStompWsChannelConfig(): StompWsConfig | null {\n return active;\n}\n\n/**\n * 解析用默认:未启动网关时 outbound 仍可降级。\n */\nexport function getStompWsConfigOrDefault(): StompWsConfig {\n return active ?? DEFAULT_STOMP_WS_CONFIG;\n}\n\n/**\n * 网关停止后清除活跃配置。\n */\nexport function clearStompWsChannelConfig(): void {\n active = null;\n}\n","/**\n * Gateway PluginRuntime 存取(与 openclaw-web-mqtt 一致)。\n */\n\nimport { createPluginRuntimeStore } from \"openclaw/plugin-sdk/runtime-store\";\nimport type { PluginRuntime } from \"openclaw/plugin-sdk/runtime-store\";\n\nconst runtimeStore = createPluginRuntimeStore<PluginRuntime>(\"openclaw-web-stomp runtime not initialized\");\n\n/**\n * 注入 OpenClaw 插件运行时。\n */\nexport function setStompWsRuntime(runtime: PluginRuntime): void {\n runtimeStore.setRuntime(runtime);\n}\n\n/**\n * 获取运行时;未初始化时返回 null。\n */\nexport function tryGetStompWsRuntime(): PluginRuntime | null {\n return runtimeStore.tryGetRuntime();\n}\n\n/**\n * 必须获取运行时。\n */\nexport function getStompWsRuntime(): PluginRuntime {\n return runtimeStore.getRuntime();\n}\n","/**\n * STOMP Destination 路由模块\n * 将 STOMP Destination 映射到 OpenClaw Agent\n *\n * Destination 规范:\n * - /queue/agent -- 发送消息给默认 Agent\n * - /queue/agent.<agentId> -- 发送消息给指定 Agent\n * - /topic/session.<key> -- 订阅某会话的事件流\n * - /topic/agent.<agentId>.events -- 订阅 Agent 级别事件\n */\n\n/**\n * Destination 解析结果\n */\nexport interface DestinationRoute {\n /** 路由类型 */\n type: \"queue\" | \"topic\";\n /** 目标类别 */\n target: \"agent\" | \"session\";\n /** Agent ID(如果有) */\n agentId?: string;\n /** Session Key(如果有) */\n sessionKey?: string;\n /** 事件类型(如 \"events\") */\n eventType?: string;\n}\n\n/**\n * 解析 STOMP Destination\n * 从 Destination 路径中提取路由信息\n *\n * @param destination - STOMP Destination 字符串\n * @returns 路由信息,null 表示无法解析\n */\nexport function parseDestination(destination: string): DestinationRoute | null {\n // /queue/agent -- 发送给默认 Agent\n if (destination === \"/queue/agent\") {\n return {\n type: \"queue\",\n target: \"agent\",\n };\n }\n\n // /queue/agent.<agentId> -- 发送给指定 Agent\n const queueAgentMatch = destination.match(/^\\/queue\\/agent\\.(.+)$/);\n if (queueAgentMatch) {\n return {\n type: \"queue\",\n target: \"agent\",\n agentId: queueAgentMatch[1],\n };\n }\n\n // /topic/session.<key> -- 订阅会话事件流\n const topicSessionMatch = destination.match(/^\\/topic\\/session\\.(.+)$/);\n if (topicSessionMatch) {\n return {\n type: \"topic\",\n target: \"session\",\n sessionKey: topicSessionMatch[1],\n };\n }\n\n // /topic/agent.<agentId>.events -- 订阅 Agent 事件\n const topicAgentMatch = destination.match(\n /^\\/topic\\/agent\\.([^.]+)\\.(\\w+)$/\n );\n if (topicAgentMatch) {\n return {\n type: \"topic\",\n target: \"agent\",\n agentId: topicAgentMatch[1],\n eventType: topicAgentMatch[2],\n };\n }\n\n return null;\n}\n\n/**\n * 构建会话事件 Destination\n *\n * @param sessionKey - OpenClaw 会话键\n * @returns Topic Destination\n */\nexport function buildSessionDestination(sessionKey: string): string {\n return `/topic/session.${sessionKey}`;\n}\n\n/**\n * 构建 Agent 事件 Destination\n *\n * @param agentId - Agent ID\n * @param eventType - 事件类型\n * @returns Topic Destination\n */\nexport function buildAgentEventDestination(\n agentId: string,\n eventType = \"events\"\n): string {\n return `/topic/agent.${agentId}.${eventType}`;\n}\n\n/**\n * 验证 Destination 是否可订阅(/topic/ 前缀)\n *\n * @param destination - 目标 Destination\n */\nexport function isSubscribable(destination: string): boolean {\n return destination.startsWith(\"/topic/\");\n}\n\n/**\n * 验证 Destination 是否可发送(/queue/ 前缀)\n *\n * @param destination - 目标 Destination\n */\nexport function isSendable(destination: string): boolean {\n return destination.startsWith(\"/queue/\");\n}\n","/**\n * Agent 出站:按 OpenClaw sessionKey 推送到 STOMP `/topic/session.<key>`。\n */\n\nimport { buildSessionDestination } from \"./destination-router.js\";\nimport { publishToDestination } from \"./stomp-server.js\";\n\n/**\n * 将回复文本发布到会话订阅 destination。\n */\nexport function publishOutboundSessionText(sessionKey: string, text: string): void {\n const destination = buildSessionDestination(sessionKey);\n publishToDestination(destination, text);\n}\n","export type DmScope = \"main\" | \"per-peer\" | \"per-channel-peer\" | \"per-account-channel-peer\";\n\n/**\n * 从 OpenClaw 运行时配置读取 dmScope。\n * 仅接受 OpenClaw 标准四档,非法或缺失时回退 main。\n */\nexport function resolveDmScopeFromRuntimeConfig(cfg: Record<string, unknown>): DmScope {\n const rawScope = (cfg.session as { dmScope?: unknown } | undefined)?.dmScope;\n const allowed = new Set([\"main\", \"per-peer\", \"per-channel-peer\", \"per-account-channel-peer\"]);\n if (typeof rawScope === \"string\" && allowed.has(rawScope)) {\n return rawScope as DmScope;\n }\n return \"main\";\n}\n\n/**\n * 根据 dmScope 生成统一的会话键。\n */\nexport function buildSessionKeyFromDmScope(params: {\n cfg: Record<string, unknown>;\n agentId: string;\n channel: string;\n accountId: string;\n peerId: string;\n}): string {\n const dmScope = resolveDmScopeFromRuntimeConfig(params.cfg);\n const agent = normalizeToken(params.agentId) || \"main\";\n const channel = normalizeToken(params.channel) || \"unknown\";\n const accountId = normalizeToken(params.accountId) || \"default\";\n const peerId = normalizeToken(params.peerId);\n\n if (!peerId || dmScope === \"main\") {\n return `agent:${agent}:main`;\n }\n if (dmScope === \"per-account-channel-peer\") {\n return `agent:${agent}:${channel}:${accountId}:direct:${peerId}`;\n }\n if (dmScope === \"per-channel-peer\") {\n return `agent:${agent}:${channel}:direct:${peerId}`;\n }\n return `agent:${agent}:direct:${peerId}`;\n}\n\nfunction normalizeToken(value: string): string {\n return value.trim().toLowerCase();\n}\n","/**\n * 将 STOMP 入站消息派发到 OpenClaw(session.dmScope + dispatchReplyFromConfig)。\n */\n\nimport { buildSessionKeyFromDmScope } from \"./dm-scope.js\";\nimport { buildSessionDestination } from \"./destination-router.js\";\nimport { setStompWsRuntime, tryGetStompWsRuntime } from \"./runtime.js\";\nimport type { InboundMessage } from \"./types.js\";\n\nconst CHANNEL_ID = \"stomp-ws\";\n\n/**\n * 异步派发单条入站到 Agent 管线。\n */\nexport async function dispatchInboundToRuntime(message: InboundMessage): Promise<void> {\n const runtime = tryGetStompWsRuntime();\n if (!runtime) {\n console.warn(\"[openclaw-web-stomp] Runtime not initialized, drop inbound\");\n return;\n }\n const cfg = runtime.config as Record<string, unknown>;\n const route = await runtime.channel.routing.resolveAgentRoute({\n cfg,\n channel: CHANNEL_ID,\n accountId: message.accountId,\n peer: { kind: \"dm\", id: message.peerId },\n });\n const resolvedAgentId =\n typeof route.agentId === \"string\" && route.agentId.trim().length > 0 ? route.agentId : message.agentId;\n\n const sessionKey = buildSessionKeyFromDmScope({\n cfg,\n agentId: resolvedAgentId,\n channel: CHANNEL_ID,\n accountId: message.accountId,\n peerId: message.peerId,\n });\n\n const replyDestination = message.replyDestination ?? buildSessionDestination(sessionKey);\n\n const ctx = await runtime.channel.reply.finalizeInboundContext({\n channel: CHANNEL_ID,\n accountId: message.accountId,\n from: message.peerId,\n text: message.text,\n chatType: \"direct\",\n extra: {\n stompDestination: message.destination,\n stompReplyDestination: replyDestination,\n sessionKey,\n },\n });\n\n const dispatcher = runtime.channel.reply.createReplyDispatcherWithTyping({\n deliver: async (payload: { text: string }) => {\n const { publishToDestination } = await import(\"./stomp-server.js\");\n publishToDestination(replyDestination, payload.text);\n },\n });\n\n await runtime.channel.reply.dispatchReplyFromConfig({\n ctx,\n cfg,\n dispatcher,\n replyOptions: route,\n });\n}\n\n/**\n * 同步包装:捕获异步错误并打日志。\n */\nexport function dispatchInboundFireAndForget(message: InboundMessage): void {\n dispatchInboundToRuntime(message).catch((err) => {\n console.error(\"[openclaw-web-stomp] Runtime dispatch failed:\", err);\n });\n}\n\n/**\n * 测试用:注入 mock 运行时。\n */\nexport function __setRuntimeForTest(runtime: NonNullable<ReturnType<typeof tryGetStompWsRuntime>>): void {\n setStompWsRuntime(runtime);\n}\n","/**\n * stomp-ws 渠道插件:Gateway 生命周期、账户快照、出站。\n */\n\nimport { resolveStompWsConfig, validateStompWsConfig, buildStompWsConfigSnapshot } from \"./stomp-config.js\";\nimport { getStompWsChannelConfig, setStompWsChannelConfig, clearStompWsChannelConfig } from \"./stomp-state.js\";\nimport { publishOutboundSessionText } from \"./outbound.js\";\nimport {\n startStompServer,\n stopStompServer,\n getConnectionInfoList,\n getStompServerStats,\n} from \"./stomp-server.js\";\nimport { dispatchInboundFireAndForget } from \"./inbound.js\";\nimport { getSubscriptionStats } from \"./subscription-mgr.js\";\nimport { getAckStats } from \"./ack-handler.js\";\n\nexport const DEFAULT_ACCOUNT_ID = \"default\";\n\n/**\n * OpenClaw ChannelPlugin:STOMP over WebSocket(stomp-ws)。\n */\nexport const stompWsChannel = {\n id: \"stomp-ws\",\n name: \"STOMP over WebSocket\",\n meta: {\n id: \"stomp-ws\",\n label: \"STOMP over WebSocket\",\n selectionLabel: \"STOMP over WebSocket (Browser)\",\n docsPath: \"/channels/stomp-ws\",\n blurb: \"STOMP over WebSocket with topic bindings and session.dmScope isolation.\",\n aliases: [\"stomp-ws\", \"web-stomp\", \"stomp\", \"ws-stomp\"],\n order: 90,\n },\n capabilities: { chatTypes: [\"direct\"] as const },\n reload: { configPrefixes: [\"channels.stomp-ws\", \"channels.stomp\"] },\n config: {\n listAccountIds: () => [DEFAULT_ACCOUNT_ID],\n resolveAccount: (cfg: Record<string, unknown>) => {\n const config = resolveStompWsConfig(cfg);\n return {\n accountId: DEFAULT_ACCOUNT_ID,\n name: \"STOMP over WebSocket\",\n enabled: true,\n configured: Boolean(config.wsPort && config.path),\n };\n },\n },\n status: {\n buildAccountSnapshot: (cfg: Record<string, unknown>) => {\n const config = resolveStompWsConfig(cfg);\n const active = getStompWsChannelConfig() ?? config;\n return {\n accountId: DEFAULT_ACCOUNT_ID,\n name: \"STOMP over WebSocket\",\n enabled: true,\n configured: true,\n webhookPath: \"/stomp-ws/status\",\n port: active.wsPort,\n extra: {\n stats: getStompServerStats(),\n connections: getConnectionInfoList(),\n subscriptions: getSubscriptionStats(),\n ack: getAckStats(),\n },\n };\n },\n },\n gateway: {\n startAccount: async ({\n runtime,\n abortSignal,\n }: {\n runtime: { config: Record<string, unknown> };\n abortSignal: AbortSignal;\n }) => {\n const config = resolveStompWsConfig(runtime.config);\n setStompWsChannelConfig(config);\n for (const issue of validateStompWsConfig(config)) {\n console.warn(`[openclaw-web-stomp] config warning: ${issue}`);\n }\n\n try {\n await startStompServer(config, (message) => {\n dispatchInboundFireAndForget(message);\n });\n\n await new Promise<void>((resolve) => {\n const onAbort = (): void => resolve();\n abortSignal.addEventListener(\"abort\", onAbort, { once: true });\n });\n } finally {\n await stopStompServer().catch(() => undefined);\n clearStompWsChannelConfig();\n }\n },\n },\n outbound: {\n deliveryMode: \"direct\" as const,\n sendText: async (sessionKey: string, text: string): Promise<void> => {\n publishOutboundSessionText(sessionKey, text);\n },\n },\n};\n\nexport type { StompWsConfig } from \"./types.js\";\nexport { buildStompWsConfigSnapshot } from \"./stomp-config.js\";\n"],"mappings":"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","names":["publishToDestination","active"]}
|