@art_style666/hi-light 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # HiLight 安装说明
2
+
3
+ ## 安装方法
4
+
5
+ OpenClaw安装: https://github.com/openclaw/openclaw#install-recommended
6
+
7
+ 插件安装:https://my.feishu.cn/wiki/CO5Vw6cG9iZUpckIvJoc279VnFc
8
+
9
+ ## 安装前准备
10
+
11
+ 先确认电脑里有这两个工具:
12
+
13
+ - `Node.js`(建议 18 或更高)
14
+ - `OpenClaw`
15
+
16
+ 在终端里输入下面两行,能看到版本号就说明已经装好:
17
+
18
+ ```bash
19
+ node -v
20
+ openclaw --version
21
+ ```
22
+
23
+ ## 安装步骤(源码安装)
24
+
25
+ ### 1. 准备源码
26
+
27
+ ```bash
28
+ git@github.com:Gongcong/hi-light-plugin.git
29
+ cd hi-light-plugin
30
+ ```
31
+
32
+ 如果你已经在插件源码目录里了,可以跳过这一步。
33
+
34
+ ### 2. 安装依赖并打包
35
+
36
+ ```bash
37
+ npm install
38
+ npm run build
39
+ ```
40
+
41
+ ### 3. 用本地源码安装到 OpenClaw
42
+
43
+ 把下面命令里的路径改成你电脑上的插件目录绝对路径:
44
+
45
+ ```bash
46
+ openclaw plugins install --link /绝对路径/hi-light-plugin
47
+ ```
48
+
49
+ ### 4. 打开配置文件
50
+
51
+ 编辑文件:`~/.openclaw/openclaw.json`
52
+
53
+ 把下面这段加到 `channels` 里(没有就新建):
54
+
55
+ ```json
56
+ "channels": {
57
+ "hi-light": {
58
+ "enabled": true,
59
+ "wsUrl": "ws://你的服务地址:8080/ws",
60
+ "authToken": "你的API KEY"
61
+ }
62
+ }
63
+ ```
64
+
65
+ API KEY 获取方式:
66
+
67
+ 各大应用商店,下载 HiLight APP,点击设置 -> 帐号管理 -> 获取 API KEY
68
+
69
+
70
+ <img src="https://github.com/user-attachments/assets/6b55651c-ac08-432f-948b-3f82902839c4" alt="API KEY 获取示意图" width="420" />
71
+
72
+
73
+ ### 5. 重启网关让配置生效
74
+
75
+ ```bash
76
+ openclaw gateway restart
77
+ ```
78
+
79
+ ## 安装完成怎么检查
80
+
81
+ 重启后如果没有报错,基本就安装成功了。
82
+ 如果想更稳妥,可以看网关日志里是否出现 `hi-light` 连接成功的信息。
package/dist/index.js ADDED
@@ -0,0 +1,732 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/accounts.ts
12
+ function resolveHiLightAccount(params) {
13
+ const { cfg, accountId } = params;
14
+ const id = accountId ?? DEFAULT_ACCOUNT_ID;
15
+ const channels = cfg.channels;
16
+ const hlCfg = channels?.["hi-light"] ?? {};
17
+ const wsUrl = hlCfg.wsUrl;
18
+ const authToken = hlCfg.authToken;
19
+ const configured = !!wsUrl;
20
+ const enabled = hlCfg.enabled !== false;
21
+ return {
22
+ accountId: id,
23
+ enabled,
24
+ configured,
25
+ config: hlCfg,
26
+ wsUrl,
27
+ authToken
28
+ };
29
+ }
30
+ function listHiLightAccountIds(cfg) {
31
+ const channels = cfg.channels;
32
+ if (!channels?.["hi-light"]) return [];
33
+ return [DEFAULT_ACCOUNT_ID];
34
+ }
35
+ function resolveDefaultHiLightAccountId(_cfg) {
36
+ return DEFAULT_ACCOUNT_ID;
37
+ }
38
+ var DEFAULT_ACCOUNT_ID;
39
+ var init_accounts = __esm({
40
+ "src/accounts.ts"() {
41
+ DEFAULT_ACCOUNT_ID = "default";
42
+ }
43
+ });
44
+
45
+ // src/send.ts
46
+ var send_exports = {};
47
+ __export(send_exports, {
48
+ sendHiLightText: () => sendHiLightText
49
+ });
50
+ async function sendHiLightText(ctx) {
51
+ console.warn(
52
+ `hi-light: outbound send to=${ctx.to} is not fully supported yet. Use inbound message flow instead.`
53
+ );
54
+ return {
55
+ ok: false,
56
+ error: new Error("hi-light outbound send not yet implemented")
57
+ };
58
+ }
59
+ var init_send = __esm({
60
+ "src/send.ts"() {
61
+ }
62
+ });
63
+
64
+ // src/runtime.ts
65
+ function setHiLightRuntime(next) {
66
+ runtime = next;
67
+ }
68
+ function getHiLightRuntime() {
69
+ if (!runtime) {
70
+ throw new Error("HiLight runtime not initialized");
71
+ }
72
+ return runtime;
73
+ }
74
+ var runtime;
75
+ var init_runtime = __esm({
76
+ "src/runtime.ts"() {
77
+ runtime = null;
78
+ }
79
+ });
80
+
81
+ // src/ws-send.ts
82
+ import WebSocket from "ws";
83
+ function sendHiLightEnvelope(params) {
84
+ const { ws, envelope, log, tag } = params;
85
+ const label = tag ? `${tag}` : envelope.action;
86
+ const raw = JSON.stringify(envelope);
87
+ log?.debug?.(`hi-light: ws send start action=${envelope.action} tag=${label} payload=${raw}`);
88
+ if (typeof ws.readyState === "number" && ws.readyState !== WebSocket.OPEN) {
89
+ log?.warn(
90
+ `hi-light: ws send skipped (socket not open) action=${envelope.action} tag=${label} readyState=${ws.readyState} payload=${raw}`
91
+ );
92
+ return false;
93
+ }
94
+ try {
95
+ ws.send(raw, (err) => {
96
+ if (err) {
97
+ log?.error(
98
+ `hi-light: ws send failed action=${envelope.action} tag=${label} error=${err.message} payload=${raw}`
99
+ );
100
+ return;
101
+ }
102
+ log?.debug?.(`hi-light: ws send success action=${envelope.action} tag=${label}`);
103
+ });
104
+ return true;
105
+ } catch (err) {
106
+ const errorText = err instanceof Error ? err.message : String(err);
107
+ log?.error(
108
+ `hi-light: ws send failed action=${envelope.action} tag=${label} error=${errorText} payload=${raw}`
109
+ );
110
+ return false;
111
+ }
112
+ }
113
+ var init_ws_send = __esm({
114
+ "src/ws-send.ts"() {
115
+ }
116
+ });
117
+
118
+ // src/reply-dispatcher.ts
119
+ function createHiLightReplyDispatcher(params) {
120
+ const { ws, config, userId, context, log } = params;
121
+ const core = getHiLightRuntime();
122
+ const stringifyRaw = (value) => {
123
+ try {
124
+ return JSON.stringify(value);
125
+ } catch {
126
+ return "[unserializable]";
127
+ }
128
+ };
129
+ const textChunks = [];
130
+ let hasSentReply = false;
131
+ let sawFinalPayload = false;
132
+ let streamSeq = 0;
133
+ const flushBufferedReply = () => {
134
+ if (hasSentReply) {
135
+ return;
136
+ }
137
+ if (!sawFinalPayload && textChunks.length === 0) {
138
+ return;
139
+ }
140
+ const fullText = textChunks.join("");
141
+ const replyEnvelope = {
142
+ context,
143
+ action: "reply",
144
+ payload: {
145
+ userId,
146
+ text: fullText,
147
+ done: true
148
+ }
149
+ };
150
+ if (sendHiLightEnvelope({ ws, envelope: replyEnvelope, log, tag: "buffered-reply" })) {
151
+ hasSentReply = true;
152
+ textChunks.length = 0;
153
+ log?.debug?.(`hi-light: sent buffered reply (len=${fullText.length})`);
154
+ }
155
+ };
156
+ const {
157
+ dispatcher,
158
+ replyOptions,
159
+ markDispatchIdle: sdkMarkDispatchIdle
160
+ } = core.channel.reply.createReplyDispatcherWithTyping({
161
+ humanDelay: core.channel.reply.resolveHumanDelayConfig(config),
162
+ deliver: async (payload, info) => {
163
+ const text = payload.text ?? "";
164
+ const kind = info?.kind ?? "unknown";
165
+ streamSeq += 1;
166
+ log?.debug?.(
167
+ `hi-light: openclaw stream chunk seq=${streamSeq} kind=${kind} textLen=${text.length} raw=${stringifyRaw({ payload, info })}`
168
+ );
169
+ if (text.length > 0) {
170
+ textChunks.push(text);
171
+ }
172
+ const isFinal = kind === "final";
173
+ if (isFinal) {
174
+ sawFinalPayload = true;
175
+ flushBufferedReply();
176
+ } else {
177
+ log?.debug?.(
178
+ `hi-light: buffering chunk (kind=${info?.kind ?? "unknown"}, len=${text.length})`
179
+ );
180
+ }
181
+ },
182
+ onReplyStart: async () => {
183
+ const typingEnvelope = {
184
+ context,
185
+ action: "typing",
186
+ payload: { userId }
187
+ };
188
+ sendHiLightEnvelope({ ws, envelope: typingEnvelope, log, tag: "typing" });
189
+ }
190
+ });
191
+ const markDispatchIdle = () => {
192
+ flushBufferedReply();
193
+ sdkMarkDispatchIdle();
194
+ };
195
+ return { dispatcher, replyOptions, markDispatchIdle };
196
+ }
197
+ var init_reply_dispatcher = __esm({
198
+ "src/reply-dispatcher.ts"() {
199
+ init_runtime();
200
+ init_ws_send();
201
+ }
202
+ });
203
+
204
+ // src/bot.ts
205
+ async function handleHiLightMessage(params) {
206
+ const { ws, raw, config, accountId, log } = params;
207
+ const core = getHiLightRuntime();
208
+ const stringifyRaw = (value) => {
209
+ try {
210
+ return JSON.stringify(value);
211
+ } catch {
212
+ return "[unserializable]";
213
+ }
214
+ };
215
+ let envelope;
216
+ try {
217
+ envelope = JSON.parse(raw);
218
+ } catch {
219
+ log?.warn(`hi-light: failed to parse message: ${raw.slice(0, 200)}`);
220
+ return;
221
+ }
222
+ if (envelope.action !== "msg") {
223
+ log?.debug?.(`hi-light: ignoring action: ${envelope.action}`);
224
+ return;
225
+ }
226
+ const payload = envelope.payload;
227
+ const userId = typeof payload.userId === "string" || typeof payload.userId === "number" ? String(payload.userId).trim() : "";
228
+ const text = typeof payload.text === "string" ? payload.text : "";
229
+ if (!userId || !text.trim()) {
230
+ log?.warn("hi-light: msg payload missing userId or text");
231
+ return;
232
+ }
233
+ const context = typeof envelope.context === "string" && envelope.context.trim() ? envelope.context.trim() : "default";
234
+ const senderName = typeof payload.userName === "string" && payload.userName.trim() ? payload.userName.trim() : userId;
235
+ log?.info(`hi-light: msg from user=${userId} context=${context}`);
236
+ const route = core.channel.routing.resolveAgentRoute({
237
+ cfg: config,
238
+ channel: "hi-light",
239
+ accountId,
240
+ peer: {
241
+ kind: "direct",
242
+ id: userId
243
+ }
244
+ });
245
+ const ctxPayload = core.channel.reply.finalizeInboundContext({
246
+ Body: text,
247
+ BodyForAgent: text,
248
+ From: userId,
249
+ To: "hi-light",
250
+ Provider: "hi-light",
251
+ AccountId: route.accountId,
252
+ ChatType: "direct",
253
+ SessionKey: route.sessionKey,
254
+ IsGroupchat: false,
255
+ SenderName: senderName
256
+ });
257
+ const storePath = core.channel.session.resolveStorePath(config.session?.store, {
258
+ agentId: route.agentId
259
+ });
260
+ await core.channel.session.recordInboundSession({
261
+ storePath,
262
+ sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
263
+ ctx: ctxPayload,
264
+ onRecordError: (err) => {
265
+ log?.error?.(`hi-light: failed to record inbound session: ${String(err)}`);
266
+ }
267
+ });
268
+ const { dispatcher, replyOptions, markDispatchIdle } = createHiLightReplyDispatcher({
269
+ ws,
270
+ config,
271
+ userId,
272
+ context,
273
+ log
274
+ });
275
+ try {
276
+ log?.debug?.(`hi-light: openclaw inbound ctx raw=${stringifyRaw(ctxPayload)}`);
277
+ await core.channel.reply.dispatchReplyFromConfig({
278
+ ctx: ctxPayload,
279
+ cfg: config,
280
+ dispatcher,
281
+ replyOptions
282
+ });
283
+ } catch (err) {
284
+ log?.error(`hi-light: dispatch error: ${err}`);
285
+ const dispatchErrorRaw = err instanceof Error ? {
286
+ name: err.name,
287
+ message: err.message,
288
+ stack: err.stack
289
+ } : err;
290
+ log?.error(`hi-light: openclaw dispatch error raw=${JSON.stringify(dispatchErrorRaw)}`);
291
+ const errorEnvelope = {
292
+ context,
293
+ action: "error",
294
+ payload: {
295
+ userId,
296
+ code: "DISPATCH_FAILED",
297
+ message: err instanceof Error ? err.message : String(err)
298
+ }
299
+ };
300
+ sendHiLightEnvelope({ ws, envelope: errorEnvelope, log, tag: "dispatch-error" });
301
+ } finally {
302
+ markDispatchIdle?.();
303
+ }
304
+ }
305
+ var init_bot = __esm({
306
+ "src/bot.ts"() {
307
+ init_reply_dispatcher();
308
+ init_runtime();
309
+ init_ws_send();
310
+ }
311
+ });
312
+
313
+ // src/monitor.ts
314
+ var monitor_exports = {};
315
+ __export(monitor_exports, {
316
+ startHiLightMonitor: () => startHiLightMonitor
317
+ });
318
+ import { randomUUID } from "node:crypto";
319
+ import WebSocket2 from "ws";
320
+ function resolveConnectWsUrl(wsUrl) {
321
+ const uuid = randomUUID();
322
+ if (wsUrl.includes(WS_UUID_PLACEHOLDER)) {
323
+ return wsUrl.replace(WS_UUID_PLACEHOLDER, uuid);
324
+ }
325
+ try {
326
+ const parsed = new URL(wsUrl);
327
+ parsed.pathname = `${parsed.pathname.replace(/\/+$/, "")}/${uuid}`;
328
+ return parsed.toString();
329
+ } catch {
330
+ const trimmed = wsUrl.replace(/\/+$/, "");
331
+ return `${trimmed}/${uuid}`;
332
+ }
333
+ }
334
+ async function startHiLightMonitor(params) {
335
+ const { config, abortSignal, accountId, log } = params;
336
+ const account = resolveHiLightAccount({ cfg: config, accountId });
337
+ if (!account.wsUrl) {
338
+ log?.error("hi-light: wsUrl is not configured, cannot start monitor");
339
+ return;
340
+ }
341
+ const wsUrlTemplate = account.wsUrl;
342
+ const authToken = account.authToken;
343
+ const baseReconnectMs = account.config.reconnectIntervalMs ?? 3e3;
344
+ const maxReconnectMs = account.config.maxReconnectIntervalMs ?? 3e4;
345
+ const HEARTBEAT_INTERVAL_MS = 3e4;
346
+ let reconnectAttempts = 0;
347
+ let stopped = false;
348
+ let activeWs = null;
349
+ let heartbeatTimer = null;
350
+ let reconnectTimer = null;
351
+ let missedPongs = 0;
352
+ const MAX_MISSED_PONGS = 2;
353
+ let stopResolved = false;
354
+ let resolveStopped;
355
+ const stoppedPromise = new Promise((resolve) => {
356
+ resolveStopped = resolve;
357
+ });
358
+ function clearHeartbeat() {
359
+ if (heartbeatTimer) {
360
+ clearInterval(heartbeatTimer);
361
+ heartbeatTimer = null;
362
+ }
363
+ }
364
+ function clearReconnect() {
365
+ if (reconnectTimer) {
366
+ clearTimeout(reconnectTimer);
367
+ reconnectTimer = null;
368
+ }
369
+ }
370
+ function resolveStoppedOnce() {
371
+ if (stopResolved) {
372
+ return;
373
+ }
374
+ stopResolved = true;
375
+ resolveStopped();
376
+ }
377
+ function stopAndDispose(reason) {
378
+ if (stopped) {
379
+ return;
380
+ }
381
+ stopped = true;
382
+ clearHeartbeat();
383
+ clearReconnect();
384
+ if (activeWs) {
385
+ log?.info(`hi-light: stopping monitor (${reason}), closing WS connection`);
386
+ try {
387
+ activeWs.terminate();
388
+ } catch {
389
+ }
390
+ activeWs = null;
391
+ }
392
+ resolveStoppedOnce();
393
+ }
394
+ const onAbort = () => {
395
+ stopAndDispose("gateway shutdown");
396
+ };
397
+ if (abortSignal.aborted) {
398
+ stopAndDispose("already aborted");
399
+ await stoppedPromise;
400
+ return;
401
+ }
402
+ abortSignal.addEventListener("abort", onAbort, { once: true });
403
+ function connect() {
404
+ if (stopped || abortSignal.aborted) {
405
+ return;
406
+ }
407
+ const headers = {};
408
+ if (authToken) {
409
+ headers["Authorization"] = `${authToken}`;
410
+ }
411
+ const connectWsUrl = resolveConnectWsUrl(wsUrlTemplate);
412
+ log?.info(`hi-light: connecting to ${connectWsUrl} (attempt ${reconnectAttempts + 1})`);
413
+ const ws = new WebSocket2(connectWsUrl, { headers });
414
+ activeWs = ws;
415
+ ws.on("open", () => {
416
+ if (stopped || abortSignal.aborted) {
417
+ try {
418
+ ws.terminate();
419
+ } catch {
420
+ }
421
+ return;
422
+ }
423
+ reconnectAttempts = 0;
424
+ log?.info(`hi-light: connected to ${connectWsUrl}`);
425
+ sendHiLightEnvelope({
426
+ ws,
427
+ log,
428
+ tag: "connected",
429
+ envelope: {
430
+ context: "",
431
+ action: "connected",
432
+ payload: { pluginId: "hi-light", accountId }
433
+ }
434
+ });
435
+ clearHeartbeat();
436
+ missedPongs = 0;
437
+ heartbeatTimer = setInterval(() => {
438
+ if (missedPongs >= MAX_MISSED_PONGS) {
439
+ log?.warn(
440
+ `hi-light: missed ${missedPongs} pongs, connection seems dead. Reconnecting...`
441
+ );
442
+ clearHeartbeat();
443
+ ws.close(4e3, "pong timeout");
444
+ return;
445
+ }
446
+ missedPongs++;
447
+ sendHiLightEnvelope({
448
+ ws,
449
+ log,
450
+ tag: `heartbeat-${missedPongs}`,
451
+ envelope: {
452
+ context: "",
453
+ action: "ping",
454
+ payload: { ts: Date.now() }
455
+ }
456
+ });
457
+ }, HEARTBEAT_INTERVAL_MS);
458
+ });
459
+ ws.on("message", (data) => {
460
+ if (stopped) {
461
+ return;
462
+ }
463
+ const raw = data.toString();
464
+ log?.debug?.(`hi-light: received raw msg len=${raw.length} raw=${raw}`);
465
+ try {
466
+ const envelope = JSON.parse(raw);
467
+ if (envelope.action === "pong") {
468
+ missedPongs = 0;
469
+ log?.debug?.("hi-light: pong received, connection healthy");
470
+ return;
471
+ }
472
+ } catch {
473
+ }
474
+ handleHiLightMessage({
475
+ ws,
476
+ raw,
477
+ config,
478
+ accountId,
479
+ log
480
+ }).catch((err) => {
481
+ log?.error(`hi-light: error handling message: ${err}`);
482
+ });
483
+ });
484
+ ws.on("close", (code, reason) => {
485
+ clearHeartbeat();
486
+ if (activeWs === ws) {
487
+ activeWs = null;
488
+ }
489
+ if (stopped || abortSignal.aborted) {
490
+ log?.info("hi-light: connection closed (gateway stopped)");
491
+ resolveStoppedOnce();
492
+ return;
493
+ }
494
+ const reasonStr = reason?.toString() || "unknown";
495
+ log?.warn(`hi-light: closed (code=${code}, reason=${reasonStr}), reconnecting...`);
496
+ scheduleReconnect();
497
+ });
498
+ ws.on("error", (err) => {
499
+ log?.error(`hi-light: connection error: ${err.message}`);
500
+ });
501
+ }
502
+ function scheduleReconnect() {
503
+ if (stopped || abortSignal.aborted) {
504
+ return;
505
+ }
506
+ reconnectAttempts++;
507
+ const delay = Math.min(baseReconnectMs * Math.pow(2, reconnectAttempts - 1), maxReconnectMs);
508
+ log?.info(`hi-light: reconnecting in ${delay}ms (attempt ${reconnectAttempts})`);
509
+ clearReconnect();
510
+ reconnectTimer = setTimeout(() => {
511
+ reconnectTimer = null;
512
+ connect();
513
+ }, delay);
514
+ }
515
+ connect();
516
+ try {
517
+ await stoppedPromise;
518
+ } finally {
519
+ abortSignal.removeEventListener("abort", onAbort);
520
+ clearHeartbeat();
521
+ clearReconnect();
522
+ }
523
+ }
524
+ var WS_UUID_PLACEHOLDER;
525
+ var init_monitor = __esm({
526
+ "src/monitor.ts"() {
527
+ init_accounts();
528
+ init_bot();
529
+ init_ws_send();
530
+ WS_UUID_PLACEHOLDER = "{UUIDD}";
531
+ }
532
+ });
533
+
534
+ // index.ts
535
+ import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
536
+
537
+ // src/channel.ts
538
+ init_accounts();
539
+ import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID2 } from "openclaw/plugin-sdk";
540
+ var meta = {
541
+ id: "hi-light",
542
+ label: "HiLight",
543
+ selectionLabel: "HiLight WebSocket Bridge",
544
+ docsPath: "/channels/hi-light",
545
+ docsLabel: "hi-light",
546
+ blurb: "HiLight \u2014 WebSocket bridge channel, connects to an external WS server.",
547
+ order: 80
548
+ };
549
+ var hiLightPlugin = {
550
+ id: "hi-light",
551
+ meta: { ...meta },
552
+ capabilities: {
553
+ chatTypes: ["direct"]
554
+ },
555
+ reload: { configPrefixes: ["channels.hi-light"] },
556
+ configSchema: {
557
+ schema: {
558
+ type: "object",
559
+ additionalProperties: false,
560
+ properties: {
561
+ enabled: { type: "boolean" },
562
+ wsUrl: { type: "string", format: "uri" },
563
+ authToken: { type: "string" },
564
+ reconnectIntervalMs: { type: "integer", minimum: 1e3 },
565
+ maxReconnectIntervalMs: { type: "integer", minimum: 1e3 },
566
+ dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
567
+ allowFrom: {
568
+ type: "array",
569
+ items: { oneOf: [{ type: "string" }, { type: "number" }] }
570
+ }
571
+ }
572
+ },
573
+ uiHints: {
574
+ wsUrl: {
575
+ label: "WebSocket URL",
576
+ help: "Base WebSocket URL. Plugin appends a new UUID path segment on every connection.",
577
+ placeholder: "wss://host/path"
578
+ },
579
+ authToken: {
580
+ label: "Auth Token",
581
+ help: "Token sent as-is in the Authorization header during WS handshake",
582
+ sensitive: true
583
+ },
584
+ reconnectIntervalMs: {
585
+ label: "Reconnect Interval (ms)",
586
+ help: "Base interval for reconnection attempts (exponential backoff)",
587
+ advanced: true
588
+ },
589
+ maxReconnectIntervalMs: {
590
+ label: "Max Reconnect Interval (ms)",
591
+ help: "Maximum interval between reconnection attempts",
592
+ advanced: true
593
+ }
594
+ }
595
+ },
596
+ // ── Config Adapter ──────────────────────────────────────────────────────
597
+ config: {
598
+ listAccountIds: (cfg) => listHiLightAccountIds(cfg),
599
+ resolveAccount: (cfg, accountId) => resolveHiLightAccount({ cfg, accountId }),
600
+ defaultAccountId: (_cfg) => resolveDefaultHiLightAccountId(_cfg),
601
+ isEnabled: (account) => account.enabled,
602
+ isConfigured: (account) => account.configured,
603
+ unconfiguredReason: () => "wsUrl is not set in channels.hi-light config",
604
+ resolveAllowFrom: ({ cfg }) => {
605
+ const hlCfg = resolveHiLightAccount({ cfg }).config;
606
+ return hlCfg.allowFrom;
607
+ },
608
+ describeAccount: (account) => ({
609
+ accountId: account.accountId,
610
+ enabled: account.enabled,
611
+ configured: account.configured,
612
+ wsUrl: account.wsUrl
613
+ }),
614
+ setAccountEnabled: ({ cfg, enabled }) => {
615
+ const channels = cfg.channels;
616
+ const hlCfg = channels?.["hi-light"] ?? {};
617
+ return {
618
+ ...cfg,
619
+ channels: {
620
+ ...channels,
621
+ "hi-light": {
622
+ ...hlCfg,
623
+ enabled
624
+ }
625
+ }
626
+ };
627
+ },
628
+ applyAccountConfig: ({ cfg, input }) => {
629
+ const channels = cfg.channels;
630
+ const hlCfg = channels?.["hi-light"] ?? {};
631
+ return {
632
+ ...cfg,
633
+ channels: {
634
+ ...channels,
635
+ "hi-light": {
636
+ ...hlCfg,
637
+ ...input.url ? { wsUrl: input.url } : {},
638
+ ...input.token ? { authToken: input.token } : {},
639
+ enabled: true
640
+ }
641
+ }
642
+ };
643
+ }
644
+ },
645
+ // ── Security ────────────────────────────────────────────────────────────
646
+ security: {
647
+ resolveDmPolicy: ({ account, cfg }) => {
648
+ const hlCfg = account.config;
649
+ const policy = hlCfg.dmPolicy ?? "open";
650
+ return {
651
+ policy,
652
+ allowFrom: hlCfg.allowFrom ?? null,
653
+ allowFromPath: 'channels["hi-light"].allowFrom',
654
+ policyPath: 'channels["hi-light"].dmPolicy',
655
+ approveHint: "openclaw allow hi-light <userId>"
656
+ };
657
+ }
658
+ },
659
+ // ── Status ──────────────────────────────────────────────────────────────
660
+ status: {
661
+ defaultRuntime: {
662
+ accountId: DEFAULT_ACCOUNT_ID2,
663
+ running: false,
664
+ lastStartAt: null,
665
+ lastStopAt: null,
666
+ lastError: null
667
+ },
668
+ buildChannelSummary: ({ snapshot }) => ({
669
+ configured: snapshot.configured ?? false,
670
+ running: snapshot.running ?? false,
671
+ wsUrl: snapshot.wsUrl ?? null,
672
+ lastStartAt: snapshot.lastStartAt ?? null,
673
+ lastStopAt: snapshot.lastStopAt ?? null,
674
+ lastError: snapshot.lastError ?? null
675
+ }),
676
+ buildAccountSnapshot: ({ account, runtime: runtime2 }) => ({
677
+ accountId: account.accountId,
678
+ enabled: account.enabled,
679
+ configured: account.configured,
680
+ wsUrl: account.wsUrl,
681
+ running: runtime2?.running ?? false,
682
+ lastStartAt: runtime2?.lastStartAt ?? null,
683
+ lastStopAt: runtime2?.lastStopAt ?? null,
684
+ lastError: runtime2?.lastError ?? null
685
+ })
686
+ },
687
+ // ── Outbound ────────────────────────────────────────────────────────────
688
+ outbound: {
689
+ deliveryMode: "direct",
690
+ sendText: async (ctx) => {
691
+ const { sendHiLightText: sendHiLightText2 } = await Promise.resolve().then(() => (init_send(), send_exports));
692
+ return sendHiLightText2(ctx);
693
+ }
694
+ },
695
+ // ── Gateway ─────────────────────────────────────────────────────────────
696
+ gateway: {
697
+ startAccount: async (ctx) => {
698
+ const { startHiLightMonitor: startHiLightMonitor2 } = await Promise.resolve().then(() => (init_monitor(), monitor_exports));
699
+ const account = resolveHiLightAccount({
700
+ cfg: ctx.cfg,
701
+ accountId: ctx.accountId
702
+ });
703
+ ctx.setStatus({ accountId: ctx.accountId });
704
+ ctx.log?.info(`hi-light: starting [${ctx.accountId}] \u2192 ${account.wsUrl ?? "(no url)"}`);
705
+ return startHiLightMonitor2({
706
+ config: ctx.cfg,
707
+ runtime: ctx.runtime,
708
+ abortSignal: ctx.abortSignal,
709
+ accountId: ctx.accountId,
710
+ log: ctx.log
711
+ });
712
+ }
713
+ }
714
+ };
715
+
716
+ // index.ts
717
+ init_runtime();
718
+ var plugin = {
719
+ id: "hi-light",
720
+ name: "HiLight",
721
+ description: "HiLight WebSocket bridge channel plugin \u2014 connects to external WS server",
722
+ configSchema: emptyPluginConfigSchema(),
723
+ register(api) {
724
+ setHiLightRuntime(api.runtime);
725
+ api.registerChannel({ plugin: hiLightPlugin });
726
+ }
727
+ };
728
+ var index_default = plugin;
729
+ export {
730
+ index_default as default,
731
+ hiLightPlugin
732
+ };
@@ -0,0 +1,10 @@
1
+ {
2
+ "id": "hi-light",
3
+ "channels": ["hi-light"],
4
+ "skills": [],
5
+ "configSchema": {
6
+ "type": "object",
7
+ "properties": {},
8
+ "additionalProperties": true
9
+ }
10
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@art_style666/hi-light",
3
+ "version": "1.0.0",
4
+ "description": "HiLight WebSocket bridge channel plugin for OpenClaw",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "exports": {
8
+ ".": "./dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "openclaw.plugin.json",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "build": "esbuild index.ts --bundle --format=esm --platform=node --target=node18 --packages=external --outfile=dist/index.js",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "dependencies": {
20
+ "ws": "^8.18.0",
21
+ "zod": "^3.23.8"
22
+ },
23
+ "devDependencies": {
24
+ "esbuild": "^0.25.0"
25
+ },
26
+ "openclaw": {
27
+ "extensions": [
28
+ "dist/index.js"
29
+ ],
30
+ "channel": {
31
+ "id": "hi-light",
32
+ "label": "HiLight",
33
+ "selectionLabel": "HiLight WebSocket Bridge",
34
+ "docsPath": "/channels/hi-light",
35
+ "docsLabel": "hi-light",
36
+ "blurb": "HiLight — WebSocket bridge channel, connects to external WS server.",
37
+ "order": 80
38
+ }
39
+ }
40
+ }