@vauxr/openclaw 2026.5.24-2 → 2026.5.31

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/dist/index.js CHANGED
@@ -28,15 +28,20 @@ const entry = defineChannelPluginEntry({
28
28
  }
29
29
  const client = new VauxrAPIClient(httpBase, config.token ?? "");
30
30
  registerTools(api, client);
31
- // WS bridge to vauxr guard against double-registration.
32
- // OpenClaw invokes registerFull from multiple subsystems in the same
33
- // process; without this flag, both bridges would contend for the
34
- // single active channel slot in vauxr and flap continuously.
31
+ // Construct the WS bridge but DO NOT start it here. `registerFull` is
32
+ // invoked from introspection paths too (e.g. `openclaw doctor`), and
33
+ // starting the bridge here would open a live WebSocket to vauxr during
34
+ // diagnostics. The bridge is started later by `gateway.startAccount`
35
+ // (see channel.ts), which only fires when the gateway is actually
36
+ // bringing the channel up for runtime use. The globalThis stash bridges
37
+ // the two scopes because `startAccount` doesn't have access to `api`.
38
+ //
39
+ // The single-bridge guard remains here so multiple `registerFull`
40
+ // invocations in the same process don't reconstruct the bridge — they'd
41
+ // contend for the single active channel slot in vauxr otherwise.
35
42
  const g = globalThis;
36
- if (!g.__vauxrBridgeStarted) {
37
- g.__vauxrBridgeStarted = true;
38
- const bridge = new VauxrBridge(api, config);
39
- bridge.start();
43
+ if (!g.__vauxrBridge) {
44
+ g.__vauxrBridge = new VauxrBridge(api, config);
40
45
  }
41
46
  // Voice system prompt injection for vauxr sessions. Match both the bare
42
47
  // form (`vauxr:<deviceId>`) used by the old subagent.run path and the
@@ -8,6 +8,7 @@ export class VauxrBridge {
8
8
  reconnectMs = INITIAL_RECONNECT_MS;
9
9
  reconnectTimer = null;
10
10
  unsubscribeEvents = null;
11
+ started = false;
11
12
  // Inflight turns keyed by deviceId. channel.turn.run doesn't surface an SDK
12
13
  // runId to the caller (unlike the old subagent.run path), so dispatch-time
13
14
  // bookkeeping is keyed by deviceId; we then latch onto the SDK runId on the
@@ -33,10 +34,16 @@ export class VauxrBridge {
33
34
  this.wsUrl = base.replace(/^http/, "ws") + "/channel";
34
35
  }
35
36
  start() {
37
+ if (this.started)
38
+ return;
39
+ this.started = true;
36
40
  this.connect();
37
41
  this.subscribeAgentEvents();
38
42
  }
39
43
  stop() {
44
+ if (!this.started)
45
+ return;
46
+ this.started = false;
40
47
  if (this.reconnectTimer) {
41
48
  clearTimeout(this.reconnectTimer);
42
49
  this.reconnectTimer = null;
@@ -49,13 +56,18 @@ export class VauxrBridge {
49
56
  this.ws.close();
50
57
  this.ws = null;
51
58
  }
59
+ // Reset the backoff so a subsequent stop/start cycle (e.g. channel
60
+ // aborts then restarts) begins reconnecting at INITIAL_RECONNECT_MS
61
+ // instead of inheriting whatever escalated delay the previous run
62
+ // had accumulated.
63
+ this.reconnectMs = INITIAL_RECONNECT_MS;
52
64
  }
53
65
  connect() {
54
- this.api.logger.info(`[vauxr-bridge] Connecting to vauxr: ${this.wsUrl}`);
66
+ this.api.logger.debug?.(`[vauxr-bridge] Connecting to vauxr: ${this.wsUrl}`);
55
67
  const ws = new WebSocket(this.wsUrl);
56
68
  this.ws = ws;
57
69
  ws.on("open", () => {
58
- this.api.logger.info("[vauxr-bridge] Connected to vauxr");
70
+ this.api.logger.debug?.("[vauxr-bridge] Connected to vauxr");
59
71
  this.reconnectMs = INITIAL_RECONNECT_MS;
60
72
  // Authenticate with channel token
61
73
  if (this.config.token) {
@@ -72,9 +84,17 @@ export class VauxrBridge {
72
84
  }
73
85
  });
74
86
  ws.on("close", () => {
75
- this.api.logger.info("[vauxr-bridge] Disconnected from vauxr");
87
+ this.api.logger.debug?.("[vauxr-bridge] Disconnected from vauxr");
88
+ // Identity check: a stop() during the close-event async delay can be
89
+ // followed by another start() that opens a fresh ws. If we cleared
90
+ // `this.ws` blindly here we'd wipe the new socket's reference and
91
+ // also fire a duplicate reconnect. Only act if we're still the
92
+ // bridge's current ws.
93
+ if (this.ws !== ws)
94
+ return;
76
95
  this.ws = null;
77
- this.scheduleReconnect();
96
+ if (this.started)
97
+ this.scheduleReconnect();
78
98
  });
79
99
  ws.on("error", (err) => {
80
100
  this.api.logger.warn(`[vauxr-bridge] WS error: ${String(err)}`);
@@ -84,7 +104,7 @@ export class VauxrBridge {
84
104
  scheduleReconnect() {
85
105
  if (this.reconnectTimer)
86
106
  return;
87
- this.api.logger.info(`[vauxr-bridge] Reconnecting in ${this.reconnectMs}ms`);
107
+ this.api.logger.debug?.(`[vauxr-bridge] Reconnecting in ${this.reconnectMs}ms`);
88
108
  this.reconnectTimer = setTimeout(() => {
89
109
  this.reconnectTimer = null;
90
110
  this.connect();
@@ -102,7 +122,7 @@ export class VauxrBridge {
102
122
  this.api.logger.info(`[vauxr-bridge] Device ${frame.deviceId ?? "unknown"}: ${frame.state ?? "unknown"}`);
103
123
  break;
104
124
  case "channel.ready":
105
- this.api.logger.info("[vauxr-bridge] Channel authenticated");
125
+ this.api.logger.debug?.("[vauxr-bridge] Channel authenticated");
106
126
  break;
107
127
  case "error":
108
128
  this.api.logger.warn(`[vauxr-bridge] Error from vauxr: ${frame.code ?? "UNKNOWN"} — ${frame.message ?? "no details"}`);
@@ -144,7 +164,13 @@ export class VauxrBridge {
144
164
  Timestamp: Date.now(),
145
165
  };
146
166
  try {
147
- await this.api.runtime.channel.turn.run({
167
+ // OpenClaw 2026.5.28 renamed `runtime.channel.turn` to
168
+ // `runtime.channel.inbound` (pure rename — same signature, same
169
+ // ChannelInboundEventRunnerParams shape as the prior
170
+ // RunChannelTurnParams). Earlier vauxr-openclaw releases that
171
+ // referenced `.turn.run` will throw `Cannot read properties of
172
+ // undefined (reading 'run')` on gateways 2026.5.28+.
173
+ await this.api.runtime.channel.inbound.run({
148
174
  channel: "vauxr",
149
175
  raw: { deviceId, text },
150
176
  adapter: {
@@ -7,7 +7,7 @@ import { createTopLevelChannelConfigBase } from "openclaw/plugin-sdk/channel-con
7
7
  export const vauxrPlugin = createChatChannelPlugin({
8
8
  base: createChannelPluginBase({
9
9
  id: "vauxr",
10
- meta: { label: "Vauxr" },
10
+ meta: { label: "Vauxr", selectionLabel: "Vauxr (voice devices)", docsPath: "/channels/vauxr", blurb: "Speak through and control Vauxr voice devices on your LAN." },
11
11
  capabilities: {
12
12
  chatTypes: ["direct"],
13
13
  },
@@ -75,9 +75,30 @@ vauxrPlugin.config.isConfigured = (_account, cfg) => {
75
75
  };
76
76
  vauxrPlugin.gateway = {
77
77
  startAccount: async (ctx) => {
78
- await new Promise((resolve) => {
79
- ctx.abortSignal.addEventListener("abort", () => resolve(), { once: true });
80
- });
78
+ // If the channel was aborted before startAccount even ran, there's no
79
+ // bridge to start and nothing to clean up — short-circuit.
80
+ if (ctx.abortSignal.aborted)
81
+ return;
82
+ const g = globalThis;
83
+ g.__vauxrBridge?.start();
84
+ try {
85
+ await new Promise((resolve) => {
86
+ // Guard against the race where the signal aborts between the
87
+ // top-of-function check and the listener attach below. In that
88
+ // window `addEventListener("abort", ...)` would never fire,
89
+ // hanging startAccount forever.
90
+ if (ctx.abortSignal.aborted) {
91
+ resolve();
92
+ return;
93
+ }
94
+ ctx.abortSignal.addEventListener("abort", () => resolve(), { once: true });
95
+ });
96
+ }
97
+ finally {
98
+ // Always stop the bridge on the way out — covers normal abort,
99
+ // already-aborted-after-attach, and any thrown error in between.
100
+ g.__vauxrBridge?.stop();
101
+ }
81
102
  },
82
103
  };
83
104
  function resolveSection(cfg) {
@@ -4,6 +4,7 @@
4
4
  "channels": ["vauxr"],
5
5
  "name": "Vauxr",
6
6
  "description": "Vauxr voice device channel plugin for OpenClaw",
7
+ "contracts": { "tools": ["vauxr_devices", "vauxr_announce", "vauxr_control"] },
7
8
  "channelConfigs": {
8
9
  "vauxr": {
9
10
  "label": "Vauxr",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vauxr/openclaw",
3
- "version": "2026.5.24-2",
3
+ "version": "2026.5.31",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "files": [
@@ -16,12 +16,12 @@
16
16
  "./dist/setup-entry.js"
17
17
  ],
18
18
  "compat": {
19
- "pluginApi": ">=2026.5.12",
20
- "minGatewayVersion": "2026.5.12"
19
+ "pluginApi": ">=2026.5.28",
20
+ "minGatewayVersion": "2026.5.28"
21
21
  },
22
22
  "build": {
23
- "openclawVersion": "2026.5.12",
24
- "pluginSdkVersion": "2026.5.12"
23
+ "openclawVersion": "2026.5.28",
24
+ "pluginSdkVersion": "2026.5.28"
25
25
  }
26
26
  },
27
27
  "dependencies": {
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/ws": "^8.5.0",
33
- "openclaw": "^2026.5.12",
33
+ "openclaw": "^2026.5.28",
34
34
  "typescript": "^5.0.0"
35
35
  },
36
36
  "description": "An OpenClaw plugin that gives your agents the ability to speak through and control Vauxr voice assistant devices.",