@bowong/clawshow-gateway 2026.3.13 → 2026.3.16-10

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 (46) hide show
  1. package/README.md +9 -9
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +5 -7
  5. package/dist/index.js.map +1 -1
  6. package/dist/src/config.d.ts +2 -2
  7. package/dist/src/config.d.ts.map +1 -1
  8. package/dist/src/config.js +19 -10
  9. package/dist/src/config.js.map +1 -1
  10. package/dist/src/gateway.d.ts +4 -3
  11. package/dist/src/gateway.d.ts.map +1 -1
  12. package/dist/src/gateway.js +211 -100
  13. package/dist/src/gateway.js.map +1 -1
  14. package/dist/src/index.d.ts +4 -4
  15. package/dist/src/index.d.ts.map +1 -1
  16. package/dist/src/index.js +3 -3
  17. package/dist/src/index.js.map +1 -1
  18. package/dist/src/outbound.d.ts +1 -1
  19. package/dist/src/outbound.d.ts.map +1 -1
  20. package/dist/src/outbound.js +7 -7
  21. package/dist/src/outbound.js.map +1 -1
  22. package/dist/src/plugin.d.ts +2 -2
  23. package/dist/src/plugin.d.ts.map +1 -1
  24. package/dist/src/plugin.js +14 -14
  25. package/dist/src/plugin.js.map +1 -1
  26. package/dist/src/protocol.d.ts +45 -14
  27. package/dist/src/protocol.d.ts.map +1 -1
  28. package/dist/src/protocol.js +138 -3
  29. package/dist/src/protocol.js.map +1 -1
  30. package/dist/src/security.d.ts +2 -2
  31. package/dist/src/security.d.ts.map +1 -1
  32. package/dist/src/security.js +4 -4
  33. package/dist/src/security.js.map +1 -1
  34. package/dist/src/setup.d.ts +1 -1
  35. package/dist/src/setup.d.ts.map +1 -1
  36. package/dist/src/setup.js +10 -8
  37. package/dist/src/setup.js.map +1 -1
  38. package/dist/src/types.d.ts +1 -1
  39. package/dist/src/types.d.ts.map +1 -1
  40. package/openclaw.plugin.json +2 -2
  41. package/package.json +8 -7
  42. package/dist/src/runtime.d.ts +0 -9
  43. package/dist/src/runtime.d.ts.map +0 -1
  44. package/dist/src/runtime.js +0 -9
  45. package/dist/src/runtime.js.map +0 -1
  46. package/index.ts +0 -17
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # @sker-pro/openclaw-channel-web
1
+ # @bowong/clawshow-gateway
2
2
 
3
- OpenClaw Web 频道插件 —— 通过 WebSocket 中继服务器实现浏览器端聊天。
3
+ OpenClaw Clawshow 频道插件 —— 通过 WebSocket 中继服务器实现浏览器端聊天。
4
4
 
5
5
  ## 安装到 OpenClaw
6
6
 
@@ -39,14 +39,14 @@ openclaw plugins install ./packages/openclaw-channel-web
39
39
 
40
40
  ### 配置频道
41
41
 
42
- 这个插件是 channel plugin,配置写在 `channels.web`,不是 `plugins.entries.web.config`。
42
+ 这个插件是 channel plugin,配置写在 `channels.clawshow`,不是 `plugins.entries.clawshow-gateway.config`。
43
43
 
44
44
  编辑 `~/.openclaw/openclaw.json`,添加:
45
45
 
46
46
  ```json5
47
47
  {
48
48
  channels: {
49
- web: {
49
+ clawshow: {
50
50
  enabled: true,
51
51
  relayUrl: "wss://your-relay-server.example.com",
52
52
  authToken: "your-secret-token",
@@ -71,7 +71,7 @@ openclaw plugins install ./packages/openclaw-channel-web
71
71
  WEB_RELAY_AUTH_TOKEN=your-secret-token
72
72
  ```
73
73
 
74
- 对应代码里会优先读取 `channels.web.authToken`,否则回退到 `WEB_RELAY_AUTH_TOKEN`。
74
+ 对应代码里会优先读取 `channels.clawshow.authToken`,否则回退到 `WEB_RELAY_AUTH_TOKEN`。
75
75
 
76
76
  ### 本地 relay 联调示例
77
77
 
@@ -87,7 +87,7 @@ npm run dev
87
87
  ```json5
88
88
  {
89
89
  channels: {
90
- web: {
90
+ clawshow: {
91
91
  enabled: true,
92
92
  relayUrl: "ws://192.168.0.117:8787",
93
93
  authToken: "your-secret-token",
@@ -117,7 +117,7 @@ node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
117
117
 
118
118
  然后把同一个值配置到:
119
119
 
120
- - `channels.web.authToken` 或 `WEB_RELAY_AUTH_TOKEN`
120
+ - `channels.clawshow.authToken` 或 `WEB_RELAY_AUTH_TOKEN`
121
121
  - `apps/web-relay` 的 `OPENCLAW_AUTH_TOKEN`
122
122
 
123
123
  ### 多账户配置
@@ -125,7 +125,7 @@ node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
125
125
  ```json5
126
126
  {
127
127
  channels: {
128
- web: {
128
+ clawshow: {
129
129
  enabled: true,
130
130
  relayUrl: "wss://relay-1.example.com",
131
131
  authToken: "token-1",
@@ -155,4 +155,4 @@ openclaw plugins list
155
155
  openclaw plugins info web
156
156
  ```
157
157
 
158
- 如果 `web` 频道显示为已配置并且连接成功,说明安装完成。
158
+ 如果 `clawshow` 频道显示为已配置并且连接成功,说明安装完成。
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
1
+ import type { OpenClawPluginApi } from 'openclaw/plugin-sdk';
2
2
  declare const plugin: {
3
3
  id: string;
4
4
  name: string;
@@ -1 +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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAI5D,QAAA,MAAM,MAAM;;;;;kBAKI,iBAAiB;CAGhC,CAAA;AAED,eAAe,MAAM,CAAA"}
package/dist/index.js CHANGED
@@ -1,13 +1,11 @@
1
- import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core";
2
- import { webChannelPlugin } from "./src/plugin.js";
3
- import { setWebRuntime } from "./src/runtime.js";
1
+ import { emptyPluginConfigSchema } from 'openclaw/plugin-sdk';
2
+ import { webChannelPlugin } from './src/plugin.js';
4
3
  const plugin = {
5
- id: "web",
6
- name: "Web",
7
- description: "Web channel plugin — browser chat via WebSocket relay",
4
+ id: 'clawshow-gateway',
5
+ name: 'clawshow-gateway',
6
+ description: 'Web channel plugin — browser chat via WebSocket relay',
8
7
  configSchema: emptyPluginConfigSchema(),
9
8
  register(api) {
10
- setWebRuntime(api.runtime);
11
9
  api.registerChannel({ plugin: webChannelPlugin });
12
10
  },
13
11
  };
package/dist/index.js.map CHANGED
@@ -1 +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,KAAK;IACT,IAAI,EAAE,KAAK;IACX,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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAA;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAElD,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,GAAG,CAAC,eAAe,CAAC,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAA;IACnD,CAAC;CACF,CAAA;AAED,eAAe,MAAM,CAAA"}
@@ -2,8 +2,8 @@
2
2
  * Web channel configuration adapter.
3
3
  * Resolves account configuration from OpenClaw config.
4
4
  */
5
- import type { ChannelConfigAdapter, OpenClawConfig } from "openclaw/plugin-sdk";
6
- import type { ResolvedWebAccount } from "./types.js";
5
+ import type { ChannelConfigAdapter, OpenClawConfig } from 'openclaw/plugin-sdk';
6
+ import type { ResolvedWebAccount } from './types.js';
7
7
  export declare function listWebAccountIds(cfg: OpenClawConfig): string[];
8
8
  export declare function resolveWebAccount(cfg: OpenClawConfig, accountId?: string | null): ResolvedWebAccount;
9
9
  export declare function isWebConfigured(account: ResolvedWebAccount): boolean;
@@ -1 +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"}
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,CAAA;AAC5B,OAAO,KAAK,EAAE,kBAAkB,EAAoB,MAAM,YAAY,CAAA;AAQtE,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,CAgCpB;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAEpE;AAED,eAAO,MAAM,gBAAgB,EAAE,oBAAoB,CAAC,kBAAkB,CAuBrE,CAAA"}
@@ -2,9 +2,9 @@
2
2
  * Web channel configuration adapter.
3
3
  * Resolves account configuration from OpenClaw config.
4
4
  */
5
- const DEFAULT_ACCOUNT_ID = "default";
5
+ const DEFAULT_ACCOUNT_ID = 'default';
6
6
  export function listWebAccountIds(cfg) {
7
- const webConfig = cfg.channels?.web;
7
+ const webConfig = cfg.channels?.clawshow;
8
8
  if (!webConfig)
9
9
  return [];
10
10
  const ids = [];
@@ -24,20 +24,29 @@ export function listWebAccountIds(cfg) {
24
24
  }
25
25
  export function resolveWebAccount(cfg, accountId) {
26
26
  const id = accountId ?? DEFAULT_ACCOUNT_ID;
27
- const webConfig = cfg.channels?.web;
27
+ const webConfig = cfg.channels?.clawshow;
28
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 } : {};
29
+ const base = webConfig
30
+ ? {
31
+ enabled: webConfig.enabled,
32
+ relayUrl: webConfig.relayUrl,
33
+ authToken: webConfig.authToken,
34
+ dmPolicy: webConfig.dmPolicy,
35
+ allowFrom: webConfig.allowFrom,
36
+ name: webConfig.name,
37
+ }
38
+ : {};
30
39
  const merged = {
31
- relayUrl: "",
40
+ relayUrl: '',
32
41
  ...base,
33
42
  ...accountConfig,
34
43
  };
35
- const authToken = merged.authToken ?? process.env.WEB_RELAY_AUTH_TOKEN ?? "";
44
+ const authToken = merged.authToken ?? process.env.WEB_RELAY_AUTH_TOKEN ?? '';
36
45
  return {
37
46
  accountId: id,
38
47
  name: merged.name,
39
48
  enabled: merged.enabled !== false,
40
- relayUrl: merged.relayUrl ?? "",
49
+ relayUrl: merged.relayUrl ?? '',
41
50
  authToken,
42
51
  config: merged,
43
52
  };
@@ -53,10 +62,10 @@ export const webConfigAdapter = {
53
62
  isConfigured: (account, _cfg) => isWebConfigured(account),
54
63
  unconfiguredReason: (account, _cfg) => {
55
64
  if (!account.relayUrl)
56
- return "relayUrl not configured";
65
+ return 'relayUrl not configured';
57
66
  if (!account.authToken)
58
- return "authToken not configured (set WEB_RELAY_AUTH_TOKEN or channels.web.authToken)";
59
- return "not configured";
67
+ return 'authToken not configured (set WEB_RELAY_AUTH_TOKEN or channels.clawshow.authToken)';
68
+ return 'not configured';
60
69
  },
61
70
  describeAccount: (account, _cfg) => ({
62
71
  accountId: account.accountId,
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
@@ -6,10 +6,11 @@
6
6
  * - Listens for browser messages → dispatches AI reply
7
7
  * - Auto-reconnects with exponential backoff
8
8
  * - Responds to AbortSignal for graceful shutdown
9
+ * - Detects zombie connections via pong timeout
9
10
  */
10
- import WebSocket from "ws";
11
- import type { ChannelGatewayAdapter, ChannelGatewayContext } from "openclaw/plugin-sdk";
12
- import type { ResolvedWebAccount } from "./types.js";
11
+ import WebSocket from 'ws';
12
+ import type { ChannelGatewayAdapter, ChannelGatewayContext } from 'openclaw/plugin-sdk';
13
+ import type { ResolvedWebAccount } from './types.js';
13
14
  export declare function getActiveWebSocket(accountId?: string): WebSocket | null;
14
15
  export declare function startWebGateway(ctx: ChannelGatewayContext<ResolvedWebAccount>): Promise<void>;
15
16
  export declare function sendTextToSession(sessionId: string, text: string, accountId?: string): boolean;
@@ -1 +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"}
1
+ {"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../../src/gateway.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,SAAS,MAAM,IAAI,CAAA;AAC1B,OAAO,KAAK,EAEV,qBAAqB,EACrB,qBAAqB,EACtB,MAAM,qBAAqB,CAAA;AAQ5B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAYpD,wBAAgB,kBAAkB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAOvE;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,qBAAqB,CAAC,kBAAkB,CAAC,GAC7C,OAAO,CAAC,IAAI,CAAC,CA0Pf;AAyGD,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAmBT;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,OAAO,EACjB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAiBT;AAED,eAAO,MAAM,iBAAiB,EAAE,qBAAqB,CAAC,kBAAkB,CAIvE,CAAA"}
@@ -6,13 +6,16 @@
6
6
  * - Listens for browser messages → dispatches AI reply
7
7
  * - Auto-reconnects with exponential backoff
8
8
  * - Responds to AbortSignal for graceful shutdown
9
+ * - Detects zombie connections via pong timeout
9
10
  */
10
- import WebSocket from "ws";
11
- import { generateMessageId, HEARTBEAT_INTERVAL_MS, parseRelayMessage, serializeRelayMessage, } from "./protocol.js";
11
+ import WebSocket from 'ws';
12
+ import { generateMessageId, HEARTBEAT_INTERVAL_MS, parseRelayMessage, serializeRelayMessage, } from './protocol.js';
12
13
  const MAX_RECONNECT_ATTEMPTS = 10;
13
14
  const INITIAL_RECONNECT_MS = 2_000;
14
15
  const MAX_RECONNECT_MS = 60_000;
15
16
  const BACKOFF_FACTOR = 2;
17
+ const PONG_TIMEOUT_MS = HEARTBEAT_INTERVAL_MS * 2.5; // 75s — allow 2 missed pongs
18
+ const COOLDOWN_MS = 5 * 60_000; // 5 min cooldown after max reconnect attempts
16
19
  /** Per-account gateway WebSocket references for outbound message sending. */
17
20
  const activeWsMap = new Map();
18
21
  export function getActiveWebSocket(accountId) {
@@ -29,17 +32,21 @@ export async function startWebGateway(ctx) {
29
32
  const { account, abortSignal, log } = ctx;
30
33
  let reconnectAttempts = 0;
31
34
  let reconnectMs = INITIAL_RECONNECT_MS;
35
+ let reconnectTimer = null; // [R3] track timer for abort cleanup
32
36
  const connect = () => new Promise((resolve, reject) => {
33
37
  if (abortSignal.aborted) {
34
38
  resolve();
35
39
  return;
36
40
  }
37
- const baseUrl = account.relayUrl.replace(/\/+$/, "");
38
- const wsUrl = baseUrl.endsWith("/openclaw") ? baseUrl : `${baseUrl}/openclaw`;
41
+ const baseUrl = account.relayUrl.replace(/\/+$/, '');
42
+ const wsUrl = baseUrl.endsWith('/openclaw')
43
+ ? baseUrl
44
+ : `${baseUrl}/openclaw`;
39
45
  log?.info(`[web:${account.accountId}] connecting to relay: ${wsUrl}`);
40
46
  const ws = new WebSocket(wsUrl);
41
47
  let heartbeatTimer = null;
42
48
  let authenticated = false;
49
+ let lastPongAt = Date.now(); // [H1] track last pong for zombie detection
43
50
  const cleanup = () => {
44
51
  activeWsMap.delete(account.accountId);
45
52
  if (heartbeatTimer) {
@@ -53,74 +60,114 @@ export async function startWebGateway(ctx) {
53
60
  lastStopAt: Date.now(),
54
61
  });
55
62
  };
56
- ws.on("open", () => {
63
+ ws.on('open', () => {
57
64
  log?.info(`[web:${account.accountId}] connected, authenticating...`);
58
65
  // Send auth handshake
59
66
  ws.send(serializeRelayMessage({
60
- type: "auth",
61
- role: "openclaw",
67
+ type: 'auth',
68
+ role: 'openclaw',
62
69
  token: account.authToken,
63
70
  }));
64
71
  });
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() }));
72
+ // [E1] Wrap entire message handler in void async IIFE with try/catch
73
+ // to prevent unhandled promise rejections from crashing the process
74
+ ws.on('message', (data) => {
75
+ void (async () => {
76
+ try {
77
+ const raw = typeof data === 'string' ? data : data.toString('utf-8');
78
+ const msg = parseRelayMessage(raw);
79
+ if (!msg)
80
+ return;
81
+ switch (msg.type) {
82
+ case 'auth_result': {
83
+ if (msg.ok) {
84
+ log?.info(`[web:${account.accountId}] authenticated successfully`);
85
+ authenticated = true;
86
+ activeWsMap.set(account.accountId, ws);
87
+ reconnectAttempts = 0;
88
+ reconnectMs = INITIAL_RECONNECT_MS;
89
+ lastPongAt = Date.now(); // [H1] reset pong timer on fresh connection
90
+ ctx.setStatus({
91
+ ...ctx.getStatus(),
92
+ connected: true,
93
+ running: true,
94
+ lastStartAt: Date.now(),
95
+ lastError: null,
96
+ restartPending: false, // [M2] clear restartPending on successful reconnect
97
+ });
98
+ // [H1] Start heartbeat with pong timeout detection
99
+ heartbeatTimer = setInterval(() => {
100
+ if (ws.readyState !== WebSocket.OPEN)
101
+ return;
102
+ // Check if pong timed out — zombie connection detection
103
+ const elapsed = Date.now() - lastPongAt;
104
+ if (elapsed > PONG_TIMEOUT_MS) {
105
+ log?.error(`[web:${account.accountId}] pong timeout (${elapsed}ms elapsed, limit ${PONG_TIMEOUT_MS}ms), forcing reconnect`);
106
+ ws.close(4000, 'Pong timeout');
107
+ return;
108
+ }
109
+ ws.send(serializeRelayMessage({
110
+ type: 'ping',
111
+ timestamp: Date.now(),
112
+ }));
113
+ }, HEARTBEAT_INTERVAL_MS);
89
114
  }
90
- }, HEARTBEAT_INTERVAL_MS);
91
- }
92
- else {
93
- log?.error(`[web:${account.accountId}] auth failed: ${msg.error}`);
94
- ws.close(4001, "Authentication failed");
115
+ else {
116
+ log?.error(`[web:${account.accountId}] auth failed: ${msg.error}`);
117
+ ws.close(4001, 'Authentication failed');
118
+ }
119
+ break;
120
+ }
121
+ case 'message': {
122
+ if (!authenticated)
123
+ break;
124
+ // Incoming message from browser → dispatch to AI
125
+ await handleIncomingMessage(ctx, msg);
126
+ break;
127
+ }
128
+ case 'typing': {
129
+ // Browser typing indicator — can log or pass through
130
+ break;
131
+ }
132
+ case 'pong': {
133
+ // [H1] Update last pong timestamp for zombie detection
134
+ lastPongAt = Date.now();
135
+ break;
136
+ }
137
+ case 'user_status': {
138
+ // Browser user online/offline notification
139
+ log?.info(`[web:${account.accountId}] user_status: ${msg.action} userId=${msg.userId} sessionId=${msg.sessionId}${msg.userName ? ` name=${msg.userName}` : ''}`);
140
+ break;
141
+ }
142
+ default:
143
+ break;
95
144
  }
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
145
  }
109
- case "pong": {
110
- // Heartbeat response connection is alive
111
- break;
146
+ catch (err) {
147
+ // [E1] Catch all async errors to prevent unhandled promise rejection
148
+ log?.error(`[web:${account.accountId}] message handler error: ${err instanceof Error ? err.message : String(err)}`);
112
149
  }
113
- default:
114
- break;
115
- }
150
+ })();
116
151
  });
117
- ws.on("close", (code, reason) => {
118
- log?.warn(`[web:${account.accountId}] disconnected (code=${code}, reason=${reason.toString("utf-8")})`);
152
+ ws.on('close', (code, reason) => {
153
+ log?.warn(`[web:${account.accountId}] disconnected (code=${code}, reason=${reason.toString('utf-8')})`);
119
154
  cleanup();
120
155
  if (abortSignal.aborted) {
121
156
  resolve();
122
157
  return;
123
158
  }
159
+ // [R1] Authentication failure is unrecoverable — do not reconnect
160
+ if (code === 4001) {
161
+ const errMessage = 'Authentication failed — not reconnecting';
162
+ log?.error(`[web:${account.accountId}] ${errMessage}`);
163
+ ctx.setStatus({
164
+ ...ctx.getStatus(),
165
+ lastError: errMessage,
166
+ running: false,
167
+ });
168
+ reject(new Error(errMessage));
169
+ return;
170
+ }
124
171
  // Schedule reconnect
125
172
  if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
126
173
  reconnectAttempts++;
@@ -132,36 +179,58 @@ export async function startWebGateway(ctx) {
132
179
  restartPending: true,
133
180
  reconnectAttempts,
134
181
  });
135
- setTimeout(() => {
182
+ reconnectTimer = setTimeout(() => {
183
+ reconnectTimer = null;
136
184
  reconnectMs = Math.min(reconnectMs * BACKOFF_FACTOR, MAX_RECONNECT_MS);
137
185
  connect().then(resolve, reject);
138
186
  }, delay);
139
187
  }
140
188
  else {
141
- const err = new Error(`Max reconnect attempts (${MAX_RECONNECT_ATTEMPTS}) exceeded`);
142
- log?.error(`[web:${account.accountId}] ${err.message}`);
189
+ // [R2] Max attempts reached cooldown then retry instead of permanent failure
190
+ log?.warn(`[web:${account.accountId}] max reconnect attempts (${MAX_RECONNECT_ATTEMPTS}) reached, cooling down for ${COOLDOWN_MS / 1000}s`);
143
191
  ctx.setStatus({
144
192
  ...ctx.getStatus(),
145
- lastError: err.message,
146
- running: false,
193
+ lastError: `Max reconnect attempts reached, cooling down for ${COOLDOWN_MS / 1000}s`,
194
+ restartPending: true,
147
195
  });
148
- reject(err);
196
+ reconnectTimer = setTimeout(() => {
197
+ reconnectTimer = null;
198
+ if (abortSignal.aborted) {
199
+ resolve();
200
+ return;
201
+ }
202
+ log?.info(`[web:${account.accountId}] cooldown complete, resetting reconnect counter`);
203
+ reconnectAttempts = 0;
204
+ reconnectMs = INITIAL_RECONNECT_MS;
205
+ connect().then(resolve, reject);
206
+ }, COOLDOWN_MS);
149
207
  }
150
208
  });
151
- ws.on("error", (err) => {
209
+ // [M1] Update lastError status in error handler
210
+ ws.on('error', err => {
152
211
  log?.error(`[web:${account.accountId}] WebSocket error: ${err.message}`);
212
+ ctx.setStatus({
213
+ ...ctx.getStatus(),
214
+ lastError: err.message,
215
+ });
153
216
  // Error triggers close event, so reconnect logic runs there
154
217
  });
155
218
  // Listen for abort signal
156
219
  const onAbort = () => {
157
220
  log?.info(`[web:${account.accountId}] shutting down (abort signal)`);
158
221
  cleanup();
159
- if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
160
- ws.close(1000, "Shutting down");
222
+ // [R3] Clear pending reconnect timer on abort
223
+ if (reconnectTimer) {
224
+ clearTimeout(reconnectTimer);
225
+ reconnectTimer = null;
226
+ }
227
+ if (ws.readyState === WebSocket.OPEN ||
228
+ ws.readyState === WebSocket.CONNECTING) {
229
+ ws.close(1000, 'Shutting down');
161
230
  }
162
231
  resolve();
163
232
  };
164
- abortSignal.addEventListener("abort", onAbort, { once: true });
233
+ abortSignal.addEventListener('abort', onAbort, { once: true });
165
234
  });
166
235
  return connect();
167
236
  }
@@ -173,61 +242,103 @@ async function handleIncomingMessage(ctx, msg) {
173
242
  ...ctx.getStatus(),
174
243
  lastInboundAt: Date.now(),
175
244
  });
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) {
245
+ if (!channelRuntime) {
182
246
  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);
247
+ void sendTextToSession(msg.sessionId, 'AI is not available at the moment. Please try again later.', ctx.accountId);
185
248
  return;
186
249
  }
250
+ const core = channelRuntime;
187
251
  try {
188
- await dispatch({
252
+ // 1. Resolve agent routing
253
+ const route = core.routing.resolveAgentRoute({
189
254
  cfg: ctx.cfg,
190
- channel: "web",
191
- chatType: "direct",
192
- from: msg.sessionId,
193
- to: msg.sessionId,
194
- text: msg.text,
255
+ channel: 'clawshow',
195
256
  accountId: ctx.accountId,
196
- messageId: msg.id,
257
+ peer: { kind: 'direct', id: msg.sessionId },
258
+ });
259
+ log?.info(`[web:${ctx.account.accountId}] route: agent=${route.agentId} session=${route.sessionKey}`);
260
+ // 2. Build MsgContext with PascalCase fields (will be finalized by dispatchInboundMessageWithBufferedDispatcher)
261
+ const ctxPayload = {
262
+ Body: msg.text,
263
+ BodyForAgent: msg.text,
264
+ RawBody: msg.text,
265
+ CommandBody: msg.text,
266
+ From: `web:${msg.sessionId}`,
267
+ To: msg.sessionId,
268
+ SessionKey: route.sessionKey,
269
+ AccountId: ctx.accountId,
270
+ ChatType: 'direct',
271
+ Provider: 'clawshow',
272
+ Surface: 'clawshow',
273
+ MessageSid: msg.id,
274
+ Timestamp: msg.timestamp || Date.now(),
275
+ CommandAuthorized: false,
276
+ };
277
+ // 3. Use the high-level dispatch API that handles all lifecycle internally
278
+ sendTypingToSession(msg.sessionId, true, ctx.accountId);
279
+ const result = await core.reply.dispatchReplyWithBufferedBlockDispatcher({
280
+ ctx: ctxPayload,
281
+ cfg: ctx.cfg,
282
+ dispatcherOptions: {
283
+ deliver: async (payload, _info) => {
284
+ if (payload.text) {
285
+ void sendTextToSession(msg.sessionId, payload.text, ctx.accountId);
286
+ }
287
+ },
288
+ onError: (err, info) => {
289
+ log?.error(`[web:${ctx.account.accountId}] reply delivery error (${info.kind}): ${err}`);
290
+ },
291
+ },
197
292
  });
293
+ sendTypingToSession(msg.sessionId, false, ctx.accountId);
294
+ log?.info(`[web:${ctx.account.accountId}] dispatch complete (queuedFinal=${result.queuedFinal}, replies=${result.counts.final})`);
198
295
  }
199
296
  catch (err) {
200
- log?.error(`[web:${ctx.account.accountId}] dispatch error: ${err}`);
201
- sendTextToSession(msg.sessionId, "An error occurred processing your message.", ctx.accountId);
297
+ sendTypingToSession(msg.sessionId, false, ctx.accountId);
298
+ // [M3] Log full stack trace but only send generic error to user
299
+ const fullMsg = err instanceof Error ? `${err.message}\n${err.stack}` : String(err);
300
+ log?.error(`[web:${ctx.account.accountId}] dispatch error: ${fullMsg}`);
301
+ void sendTextToSession(msg.sessionId, `Sorry, an error occurred while processing your message. Please try again.`, ctx.accountId);
202
302
  }
203
303
  }
204
304
  // ── Outbound Helpers ────────────────────────────────────────────────
305
+ // [E2] Try/catch around ws.send to handle race between readyState check and send
205
306
  export function sendTextToSession(sessionId, text, accountId) {
206
307
  const ws = getActiveWebSocket(accountId);
207
308
  if (!ws || ws.readyState !== WebSocket.OPEN)
208
309
  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;
310
+ try {
311
+ const msg = {
312
+ type: 'message',
313
+ id: generateMessageId(),
314
+ sessionId,
315
+ from: 'ai',
316
+ text,
317
+ timestamp: Date.now(),
318
+ };
319
+ ws.send(serializeRelayMessage(msg));
320
+ return true;
321
+ }
322
+ catch {
323
+ return false;
324
+ }
219
325
  }
220
326
  export function sendTypingToSession(sessionId, isTyping, accountId) {
221
327
  const ws = getActiveWebSocket(accountId);
222
328
  if (!ws || ws.readyState !== WebSocket.OPEN)
223
329
  return false;
224
- ws.send(serializeRelayMessage({
225
- type: "typing",
226
- sessionId,
227
- from: "ai",
228
- isTyping,
229
- }));
230
- return true;
330
+ try {
331
+ ws.send(serializeRelayMessage({
332
+ type: 'typing',
333
+ sessionId,
334
+ from: 'ai',
335
+ isTyping,
336
+ }));
337
+ return true;
338
+ }
339
+ catch {
340
+ return false;
341
+ }
231
342
  }
232
343
  export const webGatewayAdapter = {
233
344
  startAccount: async (ctx) => {