@bowong/clawshow-gateway 2026.3.13-alpha.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.
Files changed (48) hide show
  1. package/README.md +158 -0
  2. package/dist/index.d.ts +10 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +15 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/src/config.d.ts +11 -0
  7. package/dist/src/config.d.ts.map +1 -0
  8. package/dist/src/config.js +68 -0
  9. package/dist/src/config.js.map +1 -0
  10. package/dist/src/gateway.d.ts +18 -0
  11. package/dist/src/gateway.d.ts.map +1 -0
  12. package/dist/src/gateway.js +237 -0
  13. package/dist/src/gateway.js.map +1 -0
  14. package/dist/src/index.d.ts +10 -0
  15. package/dist/src/index.d.ts.map +1 -0
  16. package/dist/src/index.js +9 -0
  17. package/dist/src/index.js.map +1 -0
  18. package/dist/src/outbound.d.ts +6 -0
  19. package/dist/src/outbound.d.ts.map +1 -0
  20. package/dist/src/outbound.js +26 -0
  21. package/dist/src/outbound.js.map +1 -0
  22. package/dist/src/plugin.d.ts +9 -0
  23. package/dist/src/plugin.d.ts.map +1 -0
  24. package/dist/src/plugin.js +41 -0
  25. package/dist/src/plugin.js.map +1 -0
  26. package/dist/src/protocol.d.ts +70 -0
  27. package/dist/src/protocol.d.ts.map +1 -0
  28. package/dist/src/protocol.js +28 -0
  29. package/dist/src/protocol.js.map +1 -0
  30. package/dist/src/runtime.d.ts +9 -0
  31. package/dist/src/runtime.d.ts.map +1 -0
  32. package/dist/src/runtime.js +9 -0
  33. package/dist/src/runtime.js.map +1 -0
  34. package/dist/src/security.d.ts +7 -0
  35. package/dist/src/security.d.ts.map +1 -0
  36. package/dist/src/security.js +16 -0
  37. package/dist/src/security.js.map +1 -0
  38. package/dist/src/setup.d.ts +6 -0
  39. package/dist/src/setup.d.ts.map +1 -0
  40. package/dist/src/setup.js +55 -0
  41. package/dist/src/setup.js.map +1 -0
  42. package/dist/src/types.d.ts +20 -0
  43. package/dist/src/types.d.ts.map +1 -0
  44. package/dist/src/types.js +5 -0
  45. package/dist/src/types.js.map +1 -0
  46. package/index.ts +17 -0
  47. package/openclaw.plugin.json +9 -0
  48. package/package.json +42 -0
package/README.md ADDED
@@ -0,0 +1,158 @@
1
+ # @bowong/clawshow-gateway
2
+
3
+ OpenClaw Clawshow 频道插件 —— 通过 WebSocket 中继服务器实现浏览器端聊天。
4
+
5
+ ## 安装到 OpenClaw
6
+
7
+ 推荐使用 OpenClaw 官方插件命令安装,而不是手动复制目录。根据 [OpenClaw Plugins 文档](https://docs.openclaw.ai/plugins),本地插件有两种常用安装方式:
8
+
9
+ ### 方式一:开发联调安装(推荐)
10
+
11
+ 适合你现在这种“插件源码和 OpenClaw 项目在同一台机器上持续开发”的场景。
12
+
13
+ ```bash
14
+ openclaw plugins install -l ./packages/openclaw-channel-web
15
+ ```
16
+
17
+ 说明:
18
+
19
+ - `-l` 表示 link 安装,OpenClaw 会链接到当前源码目录,适合边改边测
20
+ - 改完代码后通常只需要重启 Gateway,不需要重新安装
21
+ - 如果插件目录里的依赖还没装,先在仓库根目录执行你当前项目使用的依赖安装命令
22
+
23
+ ### 方式二:本地目录安装
24
+
25
+ 适合你想把当前插件复制到 OpenClaw 的扩展目录,作为一个相对稳定的本地安装版本。
26
+
27
+ ```bash
28
+ openclaw plugins install ./packages/openclaw-channel-web
29
+ ```
30
+
31
+ 说明:
32
+
33
+ - 这个命令会把插件复制到 `~/.openclaw/extensions/<id>` 并启用它
34
+ - 如果你后续继续改源码,这个已安装副本不会自动同步,需要重新执行安装
35
+
36
+ ### 安装后必须重启 Gateway
37
+
38
+ 插件安装或配置变更后,OpenClaw 需要重启 Gateway 才会重新发现和加载插件。
39
+
40
+ ### 配置频道
41
+
42
+ 这个插件是 channel plugin,配置写在 `channels.clawshow`,不是 `plugins.entries.clawshow-gateway.config`。
43
+
44
+ 编辑 `~/.openclaw/openclaw.json`,添加:
45
+
46
+ ```json5
47
+ {
48
+ channels: {
49
+ clawshow: {
50
+ enabled: true,
51
+ relayUrl: "wss://your-relay-server.example.com",
52
+ authToken: "your-secret-token",
53
+ name: "我的 Web 聊天",
54
+ dmPolicy: "open",
55
+ allowFrom: ["session-id-1"],
56
+ },
57
+ }
58
+ }
59
+ ```
60
+
61
+ 字段说明:
62
+
63
+ - `relayUrl`: WebSocket 中继地址。插件会自动连接到 `<relayUrl>/openclaw`
64
+ - `authToken`: 与 relay 侧 `OPENCLAW_AUTH_TOKEN` 完全一致
65
+ - `dmPolicy`: `"open"` 或 `"allowlist"`
66
+ - `allowFrom`: 仅在 `"allowlist"` 下生效,填允许访问的 `sessionId`
67
+
68
+ `authToken` 也可以不写进配置文件,改为用环境变量:
69
+
70
+ ```bash
71
+ WEB_RELAY_AUTH_TOKEN=your-secret-token
72
+ ```
73
+
74
+ 对应代码里会优先读取 `channels.clawshow.authToken`,否则回退到 `WEB_RELAY_AUTH_TOKEN`。
75
+
76
+ ### 本地 relay 联调示例
77
+
78
+ 如果你的 relay 用 `apps/web-relay` 启动,例如本机局域网地址是 `192.168.0.117`:
79
+
80
+ ```bash
81
+ cd apps/web-relay
82
+ npm run dev
83
+ ```
84
+
85
+ 那么 OpenClaw 里可以这样配:
86
+
87
+ ```json5
88
+ {
89
+ channels: {
90
+ clawshow: {
91
+ enabled: true,
92
+ relayUrl: "ws://192.168.0.117:8787",
93
+ authToken: "your-secret-token",
94
+ name: "局域网 Web 聊天",
95
+ dmPolicy: "open",
96
+ },
97
+ }
98
+ }
99
+ ```
100
+
101
+ 注意 `relayUrl` 填基础地址即可,插件内部会自动补成 `ws://192.168.0.117:8787/openclaw`。
102
+
103
+ 如果你把 relay 部署到 Cloudflare 或公网域名,再切换成 `wss://你的域名`。
104
+
105
+ relay 侧还需要写入同一个密钥:
106
+
107
+ ```bash
108
+ cd apps/web-relay
109
+ npx wrangler secret put OPENCLAW_AUTH_TOKEN
110
+ ```
111
+
112
+ 如果你还没有 token,可以自己生成一个:
113
+
114
+ ```bash
115
+ node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
116
+ ```
117
+
118
+ 然后把同一个值配置到:
119
+
120
+ - `channels.clawshow.authToken` 或 `WEB_RELAY_AUTH_TOKEN`
121
+ - `apps/web-relay` 的 `OPENCLAW_AUTH_TOKEN`
122
+
123
+ ### 多账户配置
124
+
125
+ ```json5
126
+ {
127
+ channels: {
128
+ clawshow: {
129
+ enabled: true,
130
+ relayUrl: "wss://relay-1.example.com",
131
+ authToken: "token-1",
132
+ accounts: {
133
+ secondary: {
134
+ relayUrl: "wss://relay-2.example.com",
135
+ authToken: "token-2",
136
+ }
137
+ }
138
+ }
139
+ }
140
+ }
141
+ ```
142
+
143
+ ## 验证安装
144
+
145
+ 重启 Gateway 后检查:
146
+
147
+ ```bash
148
+ openclaw channels status
149
+ ```
150
+
151
+ 还可以查看插件是否已被发现:
152
+
153
+ ```bash
154
+ openclaw plugins list
155
+ openclaw plugins info web
156
+ ```
157
+
158
+ 如果 `clawshow` 频道显示为已配置并且连接成功,说明安装完成。
@@ -0,0 +1,10 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
2
+ declare const plugin: {
3
+ id: string;
4
+ name: string;
5
+ description: string;
6
+ configSchema: import("openclaw/plugin-sdk").OpenClawPluginConfigSchema;
7
+ register(api: OpenClawPluginApi): void;
8
+ };
9
+ export default plugin;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAKlE,QAAA,MAAM,MAAM;;;;;kBAKI,iBAAiB;CAIhC,CAAC;AAEF,eAAe,MAAM,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core";
2
+ import { webChannelPlugin } from "./src/plugin.js";
3
+ import { setWebRuntime } from "./src/runtime.js";
4
+ const plugin = {
5
+ id: "clawshow-gateway",
6
+ name: "clawshow-gateway",
7
+ description: "Web channel plugin — browser chat via WebSocket relay",
8
+ configSchema: emptyPluginConfigSchema(),
9
+ register(api) {
10
+ setWebRuntime(api.runtime);
11
+ api.registerChannel({ plugin: webChannelPlugin });
12
+ },
13
+ };
14
+ export default plugin;
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,MAAM,MAAM,GAAG;IACb,EAAE,EAAE,kBAAkB;IACtB,IAAI,EAAE,kBAAkB;IACxB,WAAW,EAAE,uDAAuD;IACpE,YAAY,EAAE,uBAAuB,EAAE;IACvC,QAAQ,CAAC,GAAsB;QAC7B,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC3B,GAAG,CAAC,eAAe,CAAC,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAC;IACpD,CAAC;CACF,CAAC;AAEF,eAAe,MAAM,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Web channel configuration adapter.
3
+ * Resolves account configuration from OpenClaw config.
4
+ */
5
+ import type { ChannelConfigAdapter, OpenClawConfig } from "openclaw/plugin-sdk";
6
+ import type { ResolvedWebAccount } from "./types.js";
7
+ export declare function listWebAccountIds(cfg: OpenClawConfig): string[];
8
+ export declare function resolveWebAccount(cfg: OpenClawConfig, accountId?: string | null): ResolvedWebAccount;
9
+ export declare function isWebConfigured(account: ResolvedWebAccount): boolean;
10
+ export declare const webConfigAdapter: ChannelConfigAdapter<ResolvedWebAccount>;
11
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAEV,oBAAoB,EACpB,cAAc,EACf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,kBAAkB,EAAoB,MAAM,YAAY,CAAC;AAQvE,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM,EAAE,CAkB/D;AAED,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,cAAc,EACnB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GACxB,kBAAkB,CAwBpB;AAED,wBAAgB,eAAe,CAC7B,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAET;AAED,eAAO,MAAM,gBAAgB,EAAE,oBAAoB,CAAC,kBAAkB,CAiBrE,CAAC"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Web channel configuration adapter.
3
+ * Resolves account configuration from OpenClaw config.
4
+ */
5
+ const DEFAULT_ACCOUNT_ID = "default";
6
+ export function listWebAccountIds(cfg) {
7
+ const webConfig = cfg.channels?.clawshow;
8
+ if (!webConfig)
9
+ return [];
10
+ const ids = [];
11
+ // Always include default if top-level config exists
12
+ if (webConfig.relayUrl) {
13
+ ids.push(DEFAULT_ACCOUNT_ID);
14
+ }
15
+ // Additional named accounts
16
+ if (webConfig.accounts) {
17
+ for (const id of Object.keys(webConfig.accounts)) {
18
+ if (!ids.includes(id)) {
19
+ ids.push(id);
20
+ }
21
+ }
22
+ }
23
+ return ids;
24
+ }
25
+ export function resolveWebAccount(cfg, accountId) {
26
+ const id = accountId ?? DEFAULT_ACCOUNT_ID;
27
+ const webConfig = cfg.channels?.clawshow;
28
+ const accountConfig = id !== DEFAULT_ACCOUNT_ID ? webConfig?.accounts?.[id] : undefined;
29
+ const base = webConfig ? { enabled: webConfig.enabled, relayUrl: webConfig.relayUrl, authToken: webConfig.authToken, dmPolicy: webConfig.dmPolicy, allowFrom: webConfig.allowFrom, name: webConfig.name } : {};
30
+ const merged = {
31
+ relayUrl: "",
32
+ ...base,
33
+ ...accountConfig,
34
+ };
35
+ const authToken = merged.authToken ?? process.env.WEB_RELAY_AUTH_TOKEN ?? "";
36
+ return {
37
+ accountId: id,
38
+ name: merged.name,
39
+ enabled: merged.enabled !== false,
40
+ relayUrl: merged.relayUrl ?? "",
41
+ authToken,
42
+ config: merged,
43
+ };
44
+ }
45
+ export function isWebConfigured(account) {
46
+ return Boolean(account.relayUrl && account.authToken);
47
+ }
48
+ export const webConfigAdapter = {
49
+ listAccountIds: listWebAccountIds,
50
+ resolveAccount: resolveWebAccount,
51
+ defaultAccountId: (_cfg) => DEFAULT_ACCOUNT_ID,
52
+ isEnabled: (account, _cfg) => account.enabled,
53
+ isConfigured: (account, _cfg) => isWebConfigured(account),
54
+ unconfiguredReason: (account, _cfg) => {
55
+ if (!account.relayUrl)
56
+ return "relayUrl not configured";
57
+ if (!account.authToken)
58
+ return "authToken not configured (set WEB_RELAY_AUTH_TOKEN or channels.clawshow.authToken)";
59
+ return "not configured";
60
+ },
61
+ describeAccount: (account, _cfg) => ({
62
+ accountId: account.accountId,
63
+ name: account.name,
64
+ enabled: account.enabled,
65
+ configured: isWebConfigured(account),
66
+ }),
67
+ };
68
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Web channel gateway — reverse WebSocket connection to relay.
3
+ *
4
+ * Follows the Telegram monitorTelegramProvider pattern:
5
+ * - Connects to relay as "openclaw" role
6
+ * - Listens for browser messages → dispatches AI reply
7
+ * - Auto-reconnects with exponential backoff
8
+ * - Responds to AbortSignal for graceful shutdown
9
+ */
10
+ import WebSocket from "ws";
11
+ import type { ChannelGatewayAdapter, ChannelGatewayContext } from "openclaw/plugin-sdk";
12
+ import type { ResolvedWebAccount } from "./types.js";
13
+ export declare function getActiveWebSocket(accountId?: string): WebSocket | null;
14
+ export declare function startWebGateway(ctx: ChannelGatewayContext<ResolvedWebAccount>): Promise<void>;
15
+ export declare function sendTextToSession(sessionId: string, text: string, accountId?: string): boolean;
16
+ export declare function sendTypingToSession(sessionId: string, isTyping: boolean, accountId?: string): boolean;
17
+ export declare const webGatewayAdapter: ChannelGatewayAdapter<ResolvedWebAccount>;
18
+ //# sourceMappingURL=gateway.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../../src/gateway.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,SAAS,MAAM,IAAI,CAAC;AAC3B,OAAO,KAAK,EAEV,qBAAqB,EACrB,qBAAqB,EACtB,MAAM,qBAAqB,CAAC;AAS7B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAUrD,wBAAgB,kBAAkB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAOvE;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,qBAAqB,CAAC,kBAAkB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAkKnG;AA8CD,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAe9F;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAarG;AAED,eAAO,MAAM,iBAAiB,EAAE,qBAAqB,CAAC,kBAAkB,CAIvE,CAAC"}
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Web channel gateway — reverse WebSocket connection to relay.
3
+ *
4
+ * Follows the Telegram monitorTelegramProvider pattern:
5
+ * - Connects to relay as "openclaw" role
6
+ * - Listens for browser messages → dispatches AI reply
7
+ * - Auto-reconnects with exponential backoff
8
+ * - Responds to AbortSignal for graceful shutdown
9
+ */
10
+ import WebSocket from "ws";
11
+ import { generateMessageId, HEARTBEAT_INTERVAL_MS, parseRelayMessage, serializeRelayMessage, } from "./protocol.js";
12
+ const MAX_RECONNECT_ATTEMPTS = 10;
13
+ const INITIAL_RECONNECT_MS = 2_000;
14
+ const MAX_RECONNECT_MS = 60_000;
15
+ const BACKOFF_FACTOR = 2;
16
+ /** Per-account gateway WebSocket references for outbound message sending. */
17
+ const activeWsMap = new Map();
18
+ export function getActiveWebSocket(accountId) {
19
+ if (accountId)
20
+ return activeWsMap.get(accountId) ?? null;
21
+ // Fallback: return the first connected one (backward compat for single-account)
22
+ for (const ws of activeWsMap.values()) {
23
+ if (ws.readyState === WebSocket.OPEN)
24
+ return ws;
25
+ }
26
+ return null;
27
+ }
28
+ export async function startWebGateway(ctx) {
29
+ const { account, abortSignal, log } = ctx;
30
+ let reconnectAttempts = 0;
31
+ let reconnectMs = INITIAL_RECONNECT_MS;
32
+ const connect = () => new Promise((resolve, reject) => {
33
+ if (abortSignal.aborted) {
34
+ resolve();
35
+ return;
36
+ }
37
+ const baseUrl = account.relayUrl.replace(/\/+$/, "");
38
+ const wsUrl = baseUrl.endsWith("/openclaw") ? baseUrl : `${baseUrl}/openclaw`;
39
+ log?.info(`[web:${account.accountId}] connecting to relay: ${wsUrl}`);
40
+ const ws = new WebSocket(wsUrl);
41
+ let heartbeatTimer = null;
42
+ let authenticated = false;
43
+ const cleanup = () => {
44
+ activeWsMap.delete(account.accountId);
45
+ if (heartbeatTimer) {
46
+ clearInterval(heartbeatTimer);
47
+ heartbeatTimer = null;
48
+ }
49
+ ctx.setStatus({
50
+ ...ctx.getStatus(),
51
+ connected: false,
52
+ running: true,
53
+ lastStopAt: Date.now(),
54
+ });
55
+ };
56
+ ws.on("open", () => {
57
+ log?.info(`[web:${account.accountId}] connected, authenticating...`);
58
+ // Send auth handshake
59
+ ws.send(serializeRelayMessage({
60
+ type: "auth",
61
+ role: "openclaw",
62
+ token: account.authToken,
63
+ }));
64
+ });
65
+ ws.on("message", async (data) => {
66
+ const raw = typeof data === "string" ? data : data.toString("utf-8");
67
+ const msg = parseRelayMessage(raw);
68
+ if (!msg)
69
+ return;
70
+ switch (msg.type) {
71
+ case "auth_result": {
72
+ if (msg.ok) {
73
+ log?.info(`[web:${account.accountId}] authenticated successfully`);
74
+ authenticated = true;
75
+ activeWsMap.set(account.accountId, ws);
76
+ reconnectAttempts = 0;
77
+ reconnectMs = INITIAL_RECONNECT_MS;
78
+ ctx.setStatus({
79
+ ...ctx.getStatus(),
80
+ connected: true,
81
+ running: true,
82
+ lastStartAt: Date.now(),
83
+ lastError: null,
84
+ });
85
+ // Start heartbeat
86
+ heartbeatTimer = setInterval(() => {
87
+ if (ws.readyState === WebSocket.OPEN) {
88
+ ws.send(serializeRelayMessage({ type: "ping", timestamp: Date.now() }));
89
+ }
90
+ }, HEARTBEAT_INTERVAL_MS);
91
+ }
92
+ else {
93
+ log?.error(`[web:${account.accountId}] auth failed: ${msg.error}`);
94
+ ws.close(4001, "Authentication failed");
95
+ }
96
+ break;
97
+ }
98
+ case "message": {
99
+ if (!authenticated)
100
+ break;
101
+ // Incoming message from browser → dispatch to AI
102
+ await handleIncomingMessage(ctx, msg);
103
+ break;
104
+ }
105
+ case "typing": {
106
+ // Browser typing indicator — can log or pass through
107
+ break;
108
+ }
109
+ case "pong": {
110
+ // Heartbeat response — connection is alive
111
+ break;
112
+ }
113
+ default:
114
+ break;
115
+ }
116
+ });
117
+ ws.on("close", (code, reason) => {
118
+ log?.warn(`[web:${account.accountId}] disconnected (code=${code}, reason=${reason.toString("utf-8")})`);
119
+ cleanup();
120
+ if (abortSignal.aborted) {
121
+ resolve();
122
+ return;
123
+ }
124
+ // Schedule reconnect
125
+ if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
126
+ reconnectAttempts++;
127
+ const jitter = Math.random() * 0.2 * reconnectMs;
128
+ const delay = reconnectMs + jitter;
129
+ log?.info(`[web:${account.accountId}] reconnecting in ${Math.round(delay)}ms (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
130
+ ctx.setStatus({
131
+ ...ctx.getStatus(),
132
+ restartPending: true,
133
+ reconnectAttempts,
134
+ });
135
+ setTimeout(() => {
136
+ reconnectMs = Math.min(reconnectMs * BACKOFF_FACTOR, MAX_RECONNECT_MS);
137
+ connect().then(resolve, reject);
138
+ }, delay);
139
+ }
140
+ else {
141
+ const err = new Error(`Max reconnect attempts (${MAX_RECONNECT_ATTEMPTS}) exceeded`);
142
+ log?.error(`[web:${account.accountId}] ${err.message}`);
143
+ ctx.setStatus({
144
+ ...ctx.getStatus(),
145
+ lastError: err.message,
146
+ running: false,
147
+ });
148
+ reject(err);
149
+ }
150
+ });
151
+ ws.on("error", (err) => {
152
+ log?.error(`[web:${account.accountId}] WebSocket error: ${err.message}`);
153
+ // Error triggers close event, so reconnect logic runs there
154
+ });
155
+ // Listen for abort signal
156
+ const onAbort = () => {
157
+ log?.info(`[web:${account.accountId}] shutting down (abort signal)`);
158
+ cleanup();
159
+ if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
160
+ ws.close(1000, "Shutting down");
161
+ }
162
+ resolve();
163
+ };
164
+ abortSignal.addEventListener("abort", onAbort, { once: true });
165
+ });
166
+ return connect();
167
+ }
168
+ // ── Inbound Message Handling ──────────────────────────────────────
169
+ async function handleIncomingMessage(ctx, msg) {
170
+ const { log, channelRuntime } = ctx;
171
+ log?.info(`[web:${ctx.account.accountId}] message from session ${msg.sessionId}: ${msg.text.slice(0, 80)}...`);
172
+ ctx.setStatus({
173
+ ...ctx.getStatus(),
174
+ lastInboundAt: Date.now(),
175
+ });
176
+ // channelRuntime is injected by OpenClaw with a dispatch wrapper that accepts
177
+ // the inbound message shape. Cast required because the SDK type exposes the
178
+ // internal dispatch-from-config signature, not the plugin-facing wrapper.
179
+ const dispatch = channelRuntime?.reply
180
+ ?.dispatchReplyFromConfig;
181
+ if (!dispatch) {
182
+ log?.warn(`[web:${ctx.account.accountId}] channelRuntime not available — cannot dispatch AI reply`);
183
+ // Send a fallback reply
184
+ sendTextToSession(msg.sessionId, "AI is not available at the moment. Please try again later.", ctx.accountId);
185
+ return;
186
+ }
187
+ try {
188
+ await dispatch({
189
+ cfg: ctx.cfg,
190
+ channel: "clawshow",
191
+ chatType: "direct",
192
+ from: msg.sessionId,
193
+ to: msg.sessionId,
194
+ text: msg.text,
195
+ accountId: ctx.accountId,
196
+ messageId: msg.id,
197
+ });
198
+ }
199
+ catch (err) {
200
+ log?.error(`[web:${ctx.account.accountId}] dispatch error: ${err}`);
201
+ sendTextToSession(msg.sessionId, "An error occurred processing your message.", ctx.accountId);
202
+ }
203
+ }
204
+ // ── Outbound Helpers ────────────────────────────────────────────────
205
+ export function sendTextToSession(sessionId, text, accountId) {
206
+ const ws = getActiveWebSocket(accountId);
207
+ if (!ws || ws.readyState !== WebSocket.OPEN)
208
+ return false;
209
+ const msg = {
210
+ type: "message",
211
+ id: generateMessageId(),
212
+ sessionId,
213
+ from: "ai",
214
+ text,
215
+ timestamp: Date.now(),
216
+ };
217
+ ws.send(serializeRelayMessage(msg));
218
+ return true;
219
+ }
220
+ export function sendTypingToSession(sessionId, isTyping, accountId) {
221
+ const ws = getActiveWebSocket(accountId);
222
+ if (!ws || ws.readyState !== WebSocket.OPEN)
223
+ return false;
224
+ ws.send(serializeRelayMessage({
225
+ type: "typing",
226
+ sessionId,
227
+ from: "ai",
228
+ isTyping,
229
+ }));
230
+ return true;
231
+ }
232
+ export const webGatewayAdapter = {
233
+ startAccount: async (ctx) => {
234
+ return startWebGateway(ctx);
235
+ },
236
+ };
237
+ //# sourceMappingURL=gateway.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway.js","sourceRoot":"","sources":["../../src/gateway.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @sker-pro/openclaw-channel-web
3
+ *
4
+ * OpenClaw ChannelPlugin for browser-based chat via WebSocket relay.
5
+ */
6
+ export { webChannelPlugin } from "./plugin.js";
7
+ export type { ResolvedWebAccount, WebAccountConfig } from "./types.js";
8
+ export { sendTextToSession, sendTypingToSession, getActiveWebSocket } from "./gateway.js";
9
+ export { type RelayMessage, type ChatMessage, type AuthMessage, generateMessageId, parseRelayMessage, serializeRelayMessage, HEARTBEAT_INTERVAL_MS, } from "./protocol.js";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC1F,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,eAAe,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @sker-pro/openclaw-channel-web
3
+ *
4
+ * OpenClaw ChannelPlugin for browser-based chat via WebSocket relay.
5
+ */
6
+ export { webChannelPlugin } from "./plugin.js";
7
+ export { sendTextToSession, sendTypingToSession, getActiveWebSocket } from "./gateway.js";
8
+ export { generateMessageId, parseRelayMessage, serializeRelayMessage, HEARTBEAT_INTERVAL_MS, } from "./protocol.js";
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC1F,OAAO,EAIL,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,eAAe,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Web channel outbound adapter — sends messages via the gateway WebSocket.
3
+ */
4
+ import type { ChannelOutboundAdapter } from "openclaw/plugin-sdk";
5
+ export declare const webOutboundAdapter: ChannelOutboundAdapter;
6
+ //# sourceMappingURL=outbound.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbound.d.ts","sourceRoot":"","sources":["../../src/outbound.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,sBAAsB,EAEvB,MAAM,qBAAqB,CAAC;AAI7B,eAAO,MAAM,kBAAkB,EAAE,sBAqBhC,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Web channel outbound adapter — sends messages via the gateway WebSocket.
3
+ */
4
+ import { generateMessageId } from "./protocol.js";
5
+ import { sendTextToSession } from "./gateway.js";
6
+ export const webOutboundAdapter = {
7
+ deliveryMode: "gateway",
8
+ textChunkLimit: 4000,
9
+ sendText: async (ctx) => {
10
+ const ok = sendTextToSession(ctx.to, ctx.text, ctx.accountId ?? undefined);
11
+ return {
12
+ channel: "clawshow",
13
+ messageId: ok ? generateMessageId() : "",
14
+ };
15
+ },
16
+ sendMedia: async (ctx) => {
17
+ // Web channel doesn't support native media — send as text link
18
+ const text = ctx.mediaUrl ? `${ctx.text}\n\n${ctx.mediaUrl}` : ctx.text;
19
+ const ok = sendTextToSession(ctx.to, text, ctx.accountId ?? undefined);
20
+ return {
21
+ channel: "clawshow",
22
+ messageId: ok ? generateMessageId() : "",
23
+ };
24
+ },
25
+ };
26
+ //# sourceMappingURL=outbound.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbound.js","sourceRoot":"","sources":["../../src/outbound.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD,MAAM,CAAC,MAAM,kBAAkB,GAA2B;IACxD,YAAY,EAAE,SAAkB;IAChC,cAAc,EAAE,IAAI;IAEpB,QAAQ,EAAE,KAAK,EAAE,GAA2B,EAAE,EAAE;QAC9C,MAAM,EAAE,GAAG,iBAAiB,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,SAAS,IAAI,SAAS,CAAC,CAAC;QAC3E,OAAO;YACL,OAAO,EAAE,UAAU;YACnB,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE;SACzC,CAAC;IACJ,CAAC;IAED,SAAS,EAAE,KAAK,EAAE,GAA2B,EAAE,EAAE;QAC/C,+DAA+D;QAC/D,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC;QACxE,MAAM,EAAE,GAAG,iBAAiB,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,SAAS,IAAI,SAAS,CAAC,CAAC;QACvE,OAAO;YACL,OAAO,EAAE,UAAU;YACnB,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE;SACzC,CAAC;IACJ,CAAC;CACF,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Web ChannelPlugin — full plugin definition following the ChannelPlugin<ResolvedWebAccount> interface.
3
+ *
4
+ * Pattern follows Telegram/WhatsApp channel plugins.
5
+ */
6
+ import type { ChannelPlugin } from "openclaw/plugin-sdk";
7
+ import type { ResolvedWebAccount } from "./types.js";
8
+ export declare const webChannelPlugin: ChannelPlugin<ResolvedWebAccount>;
9
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAMzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,eAAO,MAAM,gBAAgB,EAAE,aAAa,CAAC,kBAAkB,CA6B9D,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Web ChannelPlugin — full plugin definition following the ChannelPlugin<ResolvedWebAccount> interface.
3
+ *
4
+ * Pattern follows Telegram/WhatsApp channel plugins.
5
+ */
6
+ import { webConfigAdapter } from "./config.js";
7
+ import { webGatewayAdapter } from "./gateway.js";
8
+ import { webOutboundAdapter } from "./outbound.js";
9
+ import { webSecurityAdapter } from "./security.js";
10
+ import { webSetupAdapter } from "./setup.js";
11
+ export const webChannelPlugin = {
12
+ id: "clawshow",
13
+ meta: {
14
+ id: "clawshow",
15
+ label: "Clawshow",
16
+ selectionLabel: "Clawshow (browser chat)",
17
+ docsPath: "/channels/clawshow",
18
+ blurb: "Browser chat via WebSocket relay",
19
+ order: 50,
20
+ },
21
+ capabilities: {
22
+ chatTypes: ["direct"],
23
+ media: false,
24
+ reactions: false,
25
+ },
26
+ defaults: {
27
+ queue: {
28
+ debounceMs: 500,
29
+ },
30
+ },
31
+ reload: { configPrefixes: ["channels.clawshow"] },
32
+ config: webConfigAdapter,
33
+ gateway: webGatewayAdapter,
34
+ outbound: webOutboundAdapter,
35
+ security: webSecurityAdapter,
36
+ setup: webSetupAdapter,
37
+ pairing: {
38
+ idLabel: "sessionId",
39
+ },
40
+ };
41
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAG7C,MAAM,CAAC,MAAM,gBAAgB,GAAsC;IACjE,EAAE,EAAE,UAAU;IACd,IAAI,EAAE;QACJ,EAAE,EAAE,UAAU;QACd,KAAK,EAAE,UAAU;QACjB,cAAc,EAAE,yBAAyB;QACzC,QAAQ,EAAE,oBAAoB;QAC9B,KAAK,EAAE,kCAAkC;QACzC,KAAK,EAAE,EAAE;KACV;IACD,YAAY,EAAE;QACZ,SAAS,EAAE,CAAC,QAAQ,CAAC;QACrB,KAAK,EAAE,KAAK;QACZ,SAAS,EAAE,KAAK;KACjB;IACD,QAAQ,EAAE;QACR,KAAK,EAAE;YACL,UAAU,EAAE,GAAG;SAChB;KACF;IACD,MAAM,EAAE,EAAE,cAAc,EAAE,CAAC,mBAAmB,CAAC,EAAE;IACjD,MAAM,EAAE,gBAAgB;IACxB,OAAO,EAAE,iBAAiB;IAC1B,QAAQ,EAAE,kBAAkB;IAC5B,QAAQ,EAAE,kBAAkB;IAC5B,KAAK,EAAE,eAAe;IACtB,OAAO,EAAE;QACP,OAAO,EAAE,WAAW;KACrB;CACF,CAAC"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Shared WebSocket message protocol for Web Channel.
3
+ * Used by: web-relay (Cloudflare Worker), openclaw-channel-web (plugin), web-chat (frontend).
4
+ */
5
+ export type WebSocketRole = "browser" | "openclaw";
6
+ export type AuthMessage = {
7
+ type: "auth";
8
+ role: WebSocketRole;
9
+ token: string;
10
+ sessionId?: string;
11
+ };
12
+ export type AuthResultMessage = {
13
+ type: "auth_result";
14
+ ok: boolean;
15
+ error?: string;
16
+ sessionId?: string;
17
+ };
18
+ export type ChatMessage = {
19
+ type: "message";
20
+ id: string;
21
+ sessionId: string;
22
+ from: "user" | "ai";
23
+ text: string;
24
+ timestamp: number;
25
+ };
26
+ export type AckMessage = {
27
+ type: "ack";
28
+ messageId: string;
29
+ sessionId: string;
30
+ };
31
+ export type TypingMessage = {
32
+ type: "typing";
33
+ sessionId: string;
34
+ from: "user" | "ai";
35
+ isTyping: boolean;
36
+ };
37
+ export type HistoryRequestMessage = {
38
+ type: "history_request";
39
+ sessionId: string;
40
+ before?: number;
41
+ limit?: number;
42
+ };
43
+ export type HistoryResponseMessage = {
44
+ type: "history_response";
45
+ sessionId: string;
46
+ messages: ChatMessage[];
47
+ hasMore: boolean;
48
+ };
49
+ export type PingMessage = {
50
+ type: "ping";
51
+ timestamp: number;
52
+ };
53
+ export type PongMessage = {
54
+ type: "pong";
55
+ timestamp: number;
56
+ };
57
+ export type ErrorMessage = {
58
+ type: "error";
59
+ code: string;
60
+ message: string;
61
+ sessionId?: string;
62
+ };
63
+ export type RelayMessage = AuthMessage | AuthResultMessage | ChatMessage | AckMessage | TypingMessage | HistoryRequestMessage | HistoryResponseMessage | PingMessage | PongMessage | ErrorMessage;
64
+ export declare function parseRelayMessage(raw: string): RelayMessage | null;
65
+ export declare function serializeRelayMessage(msg: RelayMessage): string;
66
+ export declare function generateMessageId(): string;
67
+ export declare const HEARTBEAT_INTERVAL_MS = 30000;
68
+ export declare const MAX_HISTORY_PER_SESSION = 200;
69
+ export declare const DEFAULT_HISTORY_LIMIT = 50;
70
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/protocol.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,UAAU,CAAC;AAInD,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,aAAa,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,aAAa,CAAC;IACpB,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,SAAS,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,KAAK,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,QAAQ,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,iBAAiB,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,kBAAkB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAIF,MAAM,MAAM,YAAY,GACpB,WAAW,GACX,iBAAiB,GACjB,WAAW,GACX,UAAU,GACV,aAAa,GACb,qBAAqB,GACrB,sBAAsB,GACtB,WAAW,GACX,WAAW,GACX,YAAY,CAAC;AAIjB,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAUlE;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAE/D;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAID,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAC5C,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,qBAAqB,KAAK,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Shared WebSocket message protocol for Web Channel.
3
+ * Used by: web-relay (Cloudflare Worker), openclaw-channel-web (plugin), web-chat (frontend).
4
+ */
5
+ // ── Helpers ─────────────────────────────────────────────────────────
6
+ export function parseRelayMessage(raw) {
7
+ try {
8
+ const parsed = JSON.parse(raw);
9
+ if (typeof parsed === "object" && parsed !== null && "type" in parsed) {
10
+ return parsed;
11
+ }
12
+ return null;
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ }
18
+ export function serializeRelayMessage(msg) {
19
+ return JSON.stringify(msg);
20
+ }
21
+ export function generateMessageId() {
22
+ return `msg-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
23
+ }
24
+ // ── Constants ───────────────────────────────────────────────────────
25
+ export const HEARTBEAT_INTERVAL_MS = 30_000;
26
+ export const MAX_HISTORY_PER_SESSION = 200;
27
+ export const DEFAULT_HISTORY_LIMIT = 50;
28
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/protocol.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAyFH,uEAAuE;AAEvE,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAiB,CAAC;QAC/C,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,IAAI,MAAM,EAAE,CAAC;YACtE,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,GAAiB;IACrD,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;AACxE,CAAC;AAED,uEAAuE;AAEvE,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAC5C,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,CAAC;AAC3C,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Web channel runtime singleton.
3
+ * Uses the same PluginRuntime type as other channel extensions (WhatsApp, etc.)
4
+ * for full access to openclaw infrastructure (config, logging, subagent, channel).
5
+ */
6
+ import type { PluginRuntime } from "openclaw/plugin-sdk/core";
7
+ declare const setWebRuntime: (next: PluginRuntime) => void, getWebRuntime: () => PluginRuntime;
8
+ export { getWebRuntime, setWebRuntime };
9
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,QAAA,MAAoB,aAAa,iCAAc,aAAa,qBACoB,CAAC;AACjF,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Web channel runtime singleton.
3
+ * Uses the same PluginRuntime type as other channel extensions (WhatsApp, etc.)
4
+ * for full access to openclaw infrastructure (config, logging, subagent, channel).
5
+ */
6
+ import { createPluginRuntimeStore } from "openclaw/plugin-sdk/compat";
7
+ const { setRuntime: setWebRuntime, getRuntime: getWebRuntime } = createPluginRuntimeStore("Web channel runtime not initialized");
8
+ export { getWebRuntime, setWebRuntime };
9
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAGtE,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,aAAa,EAAE,GAC5D,wBAAwB,CAAgB,qCAAqC,CAAC,CAAC;AACjF,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Web channel DM security adapter.
3
+ */
4
+ import type { ChannelSecurityAdapter } from "openclaw/plugin-sdk";
5
+ import type { ResolvedWebAccount } from "./types.js";
6
+ export declare const webSecurityAdapter: ChannelSecurityAdapter<ResolvedWebAccount>;
7
+ //# sourceMappingURL=security.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/security.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,sBAAsB,EAGvB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,eAAO,MAAM,kBAAkB,EAAE,sBAAsB,CAAC,kBAAkB,CAWzE,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Web channel DM security adapter.
3
+ */
4
+ export const webSecurityAdapter = {
5
+ resolveDmPolicy: (ctx) => {
6
+ const policy = ctx.account.config.dmPolicy ?? "open";
7
+ return {
8
+ policy,
9
+ allowFrom: ctx.account.config.allowFrom ?? null,
10
+ policyPath: "channels.clawshow.dmPolicy",
11
+ allowFromPath: "channels.clawshow.allowFrom",
12
+ approveHint: "Add the session ID to channels.clawshow.allowFrom",
13
+ };
14
+ },
15
+ };
16
+ //# sourceMappingURL=security.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.js","sourceRoot":"","sources":["../../src/security.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH,MAAM,CAAC,MAAM,kBAAkB,GAA+C;IAC5E,eAAe,EAAE,CAAC,GAA+C,EAA2B,EAAE;QAC5F,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC;QACrD,OAAO;YACL,MAAM;YACN,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,IAAI;YAC/C,UAAU,EAAE,4BAA4B;YACxC,aAAa,EAAE,6BAA6B;YAC5C,WAAW,EAAE,mDAAmD;SACjE,CAAC;IACJ,CAAC;CACF,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Web channel setup adapter — applies account configuration.
3
+ */
4
+ import type { ChannelSetupAdapter } from "openclaw/plugin-sdk";
5
+ export declare const webSetupAdapter: ChannelSetupAdapter;
6
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/setup.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,mBAAmB,EAGpB,MAAM,qBAAqB,CAAC;AAE7B,eAAO,MAAM,eAAe,EAAE,mBA8D7B,CAAC"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Web channel setup adapter — applies account configuration.
3
+ */
4
+ export const webSetupAdapter = {
5
+ applyAccountConfig: (params) => {
6
+ const { cfg, accountId, input } = params;
7
+ if (accountId === "default") {
8
+ return {
9
+ ...cfg,
10
+ channels: {
11
+ ...cfg.channels,
12
+ clawshow: {
13
+ ...cfg.channels?.clawshow,
14
+ enabled: true,
15
+ ...(input.url ? { relayUrl: input.url } : {}),
16
+ ...(input.token ? { authToken: input.token } : {}),
17
+ ...(input.name ? { name: input.name } : {}),
18
+ },
19
+ },
20
+ };
21
+ }
22
+ const webConfig = cfg.channels?.clawshow ?? {};
23
+ const accounts = webConfig.accounts ?? {};
24
+ return {
25
+ ...cfg,
26
+ channels: {
27
+ ...cfg.channels,
28
+ clawshow: {
29
+ ...webConfig,
30
+ enabled: true,
31
+ accounts: {
32
+ ...accounts,
33
+ [accountId]: {
34
+ ...accounts[accountId],
35
+ enabled: true,
36
+ ...(input.url ? { relayUrl: input.url } : {}),
37
+ ...(input.token ? { authToken: input.token } : {}),
38
+ ...(input.name ? { name: input.name } : {}),
39
+ },
40
+ },
41
+ },
42
+ },
43
+ };
44
+ },
45
+ validateInput: (params) => {
46
+ if (!params.input.url && !params.input.useEnv) {
47
+ return "Web channel requires relayUrl (--url) or --use-env.";
48
+ }
49
+ if (!params.input.token && !params.input.useEnv && !process.env.WEB_RELAY_AUTH_TOKEN) {
50
+ return "Web channel requires authToken (--token), --use-env, or WEB_RELAY_AUTH_TOKEN env var.";
51
+ }
52
+ return null;
53
+ },
54
+ };
55
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/setup.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,MAAM,CAAC,MAAM,eAAe,GAAwB;IAClD,kBAAkB,EAAE,CAAC,MAIpB,EAAkB,EAAE;QACnB,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC;QAEzC,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,OAAO;gBACL,GAAG,GAAG;gBACN,QAAQ,EAAE;oBACR,GAAG,GAAG,CAAC,QAAQ;oBACf,QAAQ,EAAE;wBACR,GAAI,GAAG,CAAC,QAAQ,EAAE,QAAgD;wBAClE,OAAO,EAAE,IAAI;wBACb,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;wBAC7C,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;wBAClD,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;qBAC5C;iBACF;aACF,CAAC;QACJ,CAAC;QAED,MAAM,SAAS,GAAI,GAAG,CAAC,QAAQ,EAAE,QAAoC,IAAI,EAAE,CAAC;QAC5E,MAAM,QAAQ,GAAI,SAAS,CAAC,QAAoC,IAAI,EAAE,CAAC;QAEvE,OAAO;YACL,GAAG,GAAG;YACN,QAAQ,EAAE;gBACR,GAAG,GAAG,CAAC,QAAQ;gBACf,QAAQ,EAAE;oBACR,GAAG,SAAS;oBACZ,OAAO,EAAE,IAAI;oBACb,QAAQ,EAAE;wBACR,GAAG,QAAQ;wBACX,CAAC,SAAS,CAAC,EAAE;4BACX,GAAI,QAAQ,CAAC,SAAS,CAAyC;4BAC/D,OAAO,EAAE,IAAI;4BACb,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;4BAC7C,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;4BAClD,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;yBAC5C;qBACF;iBACF;aACF;SACF,CAAC;IACJ,CAAC;IAED,aAAa,EAAE,CAAC,MAIf,EAAiB,EAAE;QAClB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YAC9C,OAAO,qDAAqD,CAAC;QAC/D,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,CAAC;YACrF,OAAO,uFAAuF,CAAC;QACjG,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Web channel account configuration and resolved types.
3
+ */
4
+ export interface WebAccountConfig {
5
+ enabled?: boolean;
6
+ relayUrl: string;
7
+ authToken?: string;
8
+ dmPolicy?: "open" | "allowlist";
9
+ allowFrom?: string[];
10
+ name?: string;
11
+ }
12
+ export interface ResolvedWebAccount {
13
+ accountId: string;
14
+ name?: string;
15
+ enabled: boolean;
16
+ relayUrl: string;
17
+ authToken: string;
18
+ config: WebAccountConfig;
19
+ }
20
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,gBAAgB,CAAC;CAC1B"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Web channel account configuration and resolved types.
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
package/index.ts ADDED
@@ -0,0 +1,17 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
2
+ import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core";
3
+ import { webChannelPlugin } from "./src/plugin.js";
4
+ import { setWebRuntime } from "./src/runtime.js";
5
+
6
+ const plugin = {
7
+ id: "clawshow-gateway",
8
+ name: "clawshow-gateway",
9
+ description: "Web channel plugin — browser chat via WebSocket relay",
10
+ configSchema: emptyPluginConfigSchema(),
11
+ register(api: OpenClawPluginApi) {
12
+ setWebRuntime(api.runtime);
13
+ api.registerChannel({ plugin: webChannelPlugin });
14
+ },
15
+ };
16
+
17
+ export default plugin;
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "clawshow-gateway",
3
+ "channels": ["clawshow"],
4
+ "configSchema": {
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "properties": {}
8
+ }
9
+ }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@bowong/clawshow-gateway",
3
+ "version": "2026.3.13-alpha.0",
4
+ "description": "OpenClaw Web channel plugin — browser-based chat via WebSocket relay",
5
+ "type": "module",
6
+ "main": "dist/src/index.js",
7
+ "types": "dist/src/index.d.ts",
8
+ "publishConfig": {
9
+ "access": "public",
10
+ "registry": "https://registry.npmjs.org/"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "import": "./dist/src/index.js",
15
+ "types": "./dist/src/index.d.ts"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "openclaw.plugin.json",
21
+ "package.json",
22
+ "README.md",
23
+ "index.ts"
24
+ ],
25
+ "openclaw": {
26
+ "extensions": [
27
+ "./index.ts"
28
+ ]
29
+ },
30
+ "dependencies": {
31
+ "ws": "^8.18.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/ws": "^8.5.13",
35
+ "openclaw": "^2026.3.12"
36
+ },
37
+ "scripts": {
38
+ "build": "node -e \"require('fs').rmSync('dist',{ recursive: true, force: true })\" && bun x tsc -p tsconfig.build.json",
39
+ "typecheck": "bun x tsc --noEmit",
40
+ "clean": "node -e \"require('fs').rmSync('dist',{ recursive: true, force: true })\""
41
+ }
42
+ }