@clawling/clawchat-plugin-openclaw 2026.9.13-3 → 2026.9.14-2

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.
@@ -3,6 +3,7 @@ import os from "node:os";
3
3
  import { createClawChatClient } from "./ws-client.js";
4
4
  import { CHANNEL_ID } from "./config.js";
5
5
  import { resolveStateDirFromEnv } from "./state-paths.js";
6
+ import { resolvePluginVersion } from "./plugin-report.js";
6
7
  /**
7
8
  * Derive this instance's `device_id` from stable, per-instance material.
8
9
  *
@@ -35,6 +36,7 @@ export function createOpenclawClawlingClient(account, overrides = {}) {
35
36
  url: account.websocketUrl,
36
37
  token: account.token,
37
38
  deviceId,
39
+ clientVersion: resolvePluginVersion(),
38
40
  ...(overrides.transport ? { transport: overrides.transport } : {}),
39
41
  reconnect: {
40
42
  enabled: true,
@@ -2373,7 +2373,22 @@ export async function startOpenclawClawlingGateway(params) {
2373
2373
  log?.info?.(`[${accountId}] clawchat-plugin-openclaw skip duplicate stored msg=${turn.messageId}`);
2374
2374
  return "skipped";
2375
2375
  }
2376
+ if (claimed !== true) {
2377
+ // Fail closed: a claim that could not be decided (store disabled, SQLite
2378
+ // error, a throw converted to undefined by recordConnection) must not
2379
+ // dispatch. Live delivery and device replay can carry the same message,
2380
+ // and the outbound reply path (outbound.ts / reply-dispatcher.ts) already
2381
+ // refuses to send without a claim — so dispatching here only re-ran the
2382
+ // LLM/tools without delivering, or double-replied. Logged at error level
2383
+ // with reason=claim_unavailable so a store outage is visible and is not
2384
+ // mistaken for dedup. Accepted cost: a one-off transient claim failure
2385
+ // drops that message.
2386
+ log?.error?.(`[${accountId}] clawchat-plugin-openclaw inbound skipped msg=${turn.messageId} chat_id=${turn.peer.id} event=${String(env.event)} reason=claim_unavailable result=${String(claimed)}`);
2387
+ return "skipped";
2388
+ }
2376
2389
  }
2390
+ // No store wired at all (injected test transport, or store construction
2391
+ // threw at startup — which is itself logged) keeps the store-less pass-through.
2377
2392
  return "claimed";
2378
2393
  };
2379
2394
  const dispatchTurnToAgent = async (turn) => {
@@ -2730,7 +2745,10 @@ export async function startOpenclawClawlingGateway(params) {
2730
2745
  // replayed — permanently lost. Claiming first makes it durable; the abort
2731
2746
  // check below then returns without dispatching, but the frame is kept.
2732
2747
  // `claimInboundTurn` is INSERT OR IGNORE, so a genuine duplicate (same
2733
- // message_id) is still deduped here exactly once.
2748
+ // message_id) is still deduped here exactly once. The claim fails CLOSED:
2749
+ // only a successful claim proceeds; an undecided claim (store unavailable)
2750
+ // skips with reason=claim_unavailable, because outbound replies also refuse
2751
+ // to send without a claim, so dispatching would only waste the turn.
2734
2752
  const claimed = claimInboundTurn(turn);
2735
2753
  if (claimed === "skipped")
2736
2754
  return "skipped";
@@ -664,6 +664,7 @@ export class ClawChatClient extends EventEmitter {
664
664
  token: this.opts.token,
665
665
  nonce,
666
666
  ...(this.opts.deviceId ? { device_id: this.opts.deviceId } : {}),
667
+ ...(this.opts.clientVersion ? { client_version: this.opts.clientVersion } : {}),
667
668
  // Agent runtime is single-device: multi_device stays off so the server
668
669
  // never self-fans-out this connection's own messages. notify_signals is
669
670
  // advertised because we now handle the notify.signal frame (§9.4).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawling/clawchat-plugin-openclaw",
3
- "version": "2026.9.13-3",
3
+ "version": "2026.9.14-2",
4
4
  "description": "OpenClaw ClawChat channel plugin",
5
5
  "license": "MIT",
6
6
  "author": "CLAWLING PTE. LTD.",
package/src/client.ts CHANGED
@@ -4,6 +4,7 @@ import type { Envelope, Transport } from "./protocol-types.ts";
4
4
  import { createClawChatClient, type ClawlingChatClient } from "./ws-client.ts";
5
5
  import { CHANNEL_ID, type ResolvedOpenclawClawlingAccount } from "./config.ts";
6
6
  import { resolveStateDirFromEnv } from "./state-paths.ts";
7
+ import { resolvePluginVersion } from "./plugin-report.ts";
7
8
 
8
9
  export type { ChatType } from "./protocol-types.ts";
9
10
 
@@ -69,6 +70,7 @@ export function createOpenclawClawlingClient(
69
70
  url: account.websocketUrl,
70
71
  token: account.token,
71
72
  deviceId,
73
+ clientVersion: resolvePluginVersion(),
72
74
  ...(overrides.transport ? { transport: overrides.transport } : {}),
73
75
  reconnect: {
74
76
  enabled: true,
@@ -102,6 +102,8 @@ export interface ConnectPayload {
102
102
  token: string;
103
103
  nonce: string;
104
104
  device_id?: string;
105
+ /** Telemetry only (msghub logs it on "handshake accepted"). */
106
+ client_version?: string;
105
107
  capabilities?: ConnectCapabilities;
106
108
  }
107
109
 
package/src/runtime.ts CHANGED
@@ -2859,7 +2859,24 @@ export async function startOpenclawClawlingGateway(params: StartGatewayParams):
2859
2859
  );
2860
2860
  return "skipped";
2861
2861
  }
2862
+ if (claimed !== true) {
2863
+ // Fail closed: a claim that could not be decided (store disabled, SQLite
2864
+ // error, a throw converted to undefined by recordConnection) must not
2865
+ // dispatch. Live delivery and device replay can carry the same message,
2866
+ // and the outbound reply path (outbound.ts / reply-dispatcher.ts) already
2867
+ // refuses to send without a claim — so dispatching here only re-ran the
2868
+ // LLM/tools without delivering, or double-replied. Logged at error level
2869
+ // with reason=claim_unavailable so a store outage is visible and is not
2870
+ // mistaken for dedup. Accepted cost: a one-off transient claim failure
2871
+ // drops that message.
2872
+ log?.error?.(
2873
+ `[${accountId}] clawchat-plugin-openclaw inbound skipped msg=${turn.messageId} chat_id=${turn.peer.id} event=${String(env.event)} reason=claim_unavailable result=${String(claimed)}`,
2874
+ );
2875
+ return "skipped";
2876
+ }
2862
2877
  }
2878
+ // No store wired at all (injected test transport, or store construction
2879
+ // threw at startup — which is itself logged) keeps the store-less pass-through.
2863
2880
  return "claimed";
2864
2881
  };
2865
2882
 
@@ -3265,7 +3282,10 @@ export async function startOpenclawClawlingGateway(params: StartGatewayParams):
3265
3282
  // replayed — permanently lost. Claiming first makes it durable; the abort
3266
3283
  // check below then returns without dispatching, but the frame is kept.
3267
3284
  // `claimInboundTurn` is INSERT OR IGNORE, so a genuine duplicate (same
3268
- // message_id) is still deduped here exactly once.
3285
+ // message_id) is still deduped here exactly once. The claim fails CLOSED:
3286
+ // only a successful claim proceeds; an undecided claim (store unavailable)
3287
+ // skips with reason=claim_unavailable, because outbound replies also refuse
3288
+ // to send without a claim, so dispatching would only waste the turn.
3269
3289
  const claimed = claimInboundTurn(turn);
3270
3290
  if (claimed === "skipped") return "skipped";
3271
3291
  // Findings 2 & 3 — hold ALL group traffic until the in-flight settings pull
package/src/ws-client.ts CHANGED
@@ -118,6 +118,8 @@ export interface ClawChatClientOptions {
118
118
  url: string;
119
119
  token: string;
120
120
  deviceId?: string;
121
+ /** Sent as connect `client_version` (telemetry only); omitted when empty. */
122
+ clientVersion?: string;
121
123
  transport?: Transport;
122
124
  traceIdFactory?: () => string;
123
125
  reconnect?: {
@@ -137,6 +139,7 @@ export interface NormalizedClawChatClientOptions {
137
139
  url: string;
138
140
  token: string;
139
141
  deviceId?: string;
142
+ clientVersion?: string;
140
143
  transport: Transport;
141
144
  traceIdFactory: () => string;
142
145
  reconnect: Required<NonNullable<ClawChatClientOptions["reconnect"]>>;
@@ -750,6 +753,7 @@ export class ClawChatClient extends EventEmitter {
750
753
  token: this.opts.token,
751
754
  nonce,
752
755
  ...(this.opts.deviceId ? { device_id: this.opts.deviceId } : {}),
756
+ ...(this.opts.clientVersion ? { client_version: this.opts.clientVersion } : {}),
753
757
  // Agent runtime is single-device: multi_device stays off so the server
754
758
  // never self-fans-out this connection's own messages. notify_signals is
755
759
  // advertised because we now handle the notify.signal frame (§9.4).