@clawling/clawchat-plugin-openclaw 2026.7.8-1 → 2026.7.8-3

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.
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Synthetic envelope builder for the lighter-awareness consolidated note
3
+ * (friend.added / friend.removed / friend.profile_updated / conversation.*).
4
+ *
5
+ * Like the friend-request turn, the note runs in the owner's direct
6
+ * conversation and must be addressed to the conversation id (cnv_…) recorded
7
+ * at activation. A usr_… id is not a valid chat_id — member-backend rejects it
8
+ * with "invalid conversation id" and msghub silently drops the agent's
9
+ * in-turn replies.
10
+ */
11
+ import { EVENT } from "./protocol-types.js";
12
+ export function buildAwarenessNoteEnvelope(params) {
13
+ const { account, ownerConversationId } = params;
14
+ const now = Date.now();
15
+ return {
16
+ version: "2",
17
+ event: EVENT.MESSAGE_SEND,
18
+ trace_id: `clawchat-plugin-openclaw-awareness-${now}`,
19
+ emitted_at: now,
20
+ chat_id: ownerConversationId ?? account.ownerUserId,
21
+ chat_type: "direct",
22
+ to: { id: account.userId, type: "direct" },
23
+ sender: { id: "clawchat-awareness", type: "direct", nick_name: "ClawChat" },
24
+ payload: {
25
+ message_id: `clawchat-plugin-openclaw-awareness-${now}`,
26
+ message_mode: "normal",
27
+ message: {
28
+ body: {
29
+ fragments: [
30
+ {
31
+ kind: "text",
32
+ text: "ClawChat awareness: your contacts or conversations have been updated.",
33
+ },
34
+ ],
35
+ },
36
+ context: { mentions: [], reply: null },
37
+ streaming: {
38
+ status: "static",
39
+ sequence: 0,
40
+ mutation_policy: "sealed",
41
+ started_at: null,
42
+ completed_at: null,
43
+ },
44
+ },
45
+ },
46
+ };
47
+ }
@@ -15,18 +15,24 @@ import { EVENT } from "./protocol-types.js";
15
15
  *
16
16
  * - `deny` → inform the agent it should decline; no accept instruction.
17
17
  * - `ask` / `allow` → instruct the agent to review the pending request.
18
+ *
19
+ * The requester's user id is embedded so the agent does not go looking up the
20
+ * synthetic sender ("clawchat-friend-request" is not a real user).
18
21
  */
19
- export function friendRequestPromptFor(state) {
22
+ export function friendRequestPromptFor(state, requesterUserId) {
23
+ const arrived = requesterUserId
24
+ ? `A new friend request has arrived from user ${requesterUserId}.`
25
+ : "A new friend request has arrived.";
20
26
  if (state === "deny") {
21
27
  return [
22
- "A new friend request has arrived.",
28
+ arrived,
23
29
  "Your current friend-add policy is set to deny.",
24
30
  "Do not add this contact.",
25
31
  "You may inform the requester that you cannot add them at this time.",
26
32
  ].join(" ");
27
33
  }
28
34
  return [
29
- "A new friend request has arrived.",
35
+ arrived,
30
36
  "Please review the pending request by calling `clawchat_list_friend_requests`",
31
37
  "and decide whether to accept it.",
32
38
  ].join(" ");
@@ -38,15 +44,19 @@ export function friendRequestPromptFor(state) {
38
44
  * the agent has context about who it is reasoning for.
39
45
  */
40
46
  export function buildFriendRequestEnvelope(params) {
41
- const { account, state, entityId } = params;
42
- const text = friendRequestPromptFor(state);
47
+ const { account, state, entityId, ownerConversationId } = params;
48
+ const text = friendRequestPromptFor(state, entityId);
43
49
  const now = Date.now();
50
+ // Fall back to ownerUserId only when no activation conversation is recorded:
51
+ // the turn still runs (the agent can act via tools, e.g. accept the request),
52
+ // but its in-chat replies will not be deliverable until activation records
53
+ // the owner conversation.
44
54
  return {
45
55
  version: "2",
46
56
  event: EVENT.MESSAGE_SEND,
47
57
  trace_id: `clawchat-plugin-openclaw-friend-request-${entityId}-${now}`,
48
58
  emitted_at: now,
49
- chat_id: account.ownerUserId,
59
+ chat_id: ownerConversationId ?? account.ownerUserId,
50
60
  chat_type: "direct",
51
61
  to: { id: account.userId, type: "direct" },
52
62
  sender: {
@@ -258,6 +258,9 @@ const RESTART_WINDOW_MS = 30 * 60 * 1000;
258
258
  const MAX_RESTARTS_PER_WINDOW = 5;
259
259
  const INTRO_RETRY_DELAY_MS = 30_000;
260
260
  const INTRO_MAX_TRIES = 20;
261
+ // Backoff for transient start() failures (template CDN 429s, network blips):
262
+ // a one-shot bootstrap would otherwise stay dormant until the next restart.
263
+ const START_RETRY_DELAYS_MS = [30_000, 60_000, 120_000, 300_000];
261
264
  export class LivewareSampleSupervisor {
262
265
  deps;
263
266
  serverChild = null;
@@ -272,8 +275,12 @@ export class LivewareSampleSupervisor {
272
275
  constructor(deps) {
273
276
  this.deps = deps;
274
277
  }
275
- /** Entry point. Never throws; every failure is logged + persisted only. */
278
+ /** Entry point. Never throws; every failure is logged + persisted only.
279
+ * Transient failures are retried on the START_RETRY_DELAYS_MS backoff. */
276
280
  async start() {
281
+ await this.startAttempt(0);
282
+ }
283
+ async startAttempt(attempt) {
277
284
  const { deps } = this;
278
285
  try {
279
286
  if (!deps.enabled)
@@ -299,7 +306,13 @@ export class LivewareSampleSupervisor {
299
306
  // A mid-flow failure (e.g. registerApp rejected) may have left children
300
307
  // running without a persisted row — never leak orphan processes.
301
308
  this.killChildren();
302
- deps.log?.warn?.(`clawchat-plugin-openclaw liveware-sample start failed: ${String(err)}`);
309
+ const delayMs = START_RETRY_DELAYS_MS[attempt];
310
+ if (delayMs === undefined || this.stopped) {
311
+ deps.log?.warn?.(`clawchat-plugin-openclaw liveware-sample start failed (attempt ${attempt + 1}, giving up): ${String(err)}`);
312
+ return;
313
+ }
314
+ deps.log?.warn?.(`clawchat-plugin-openclaw liveware-sample start failed (attempt ${attempt + 1}, retrying in ${delayMs / 1000}s): ${String(err)}`);
315
+ this.schedule(() => { void this.startAttempt(attempt + 1); }, delayMs);
303
316
  }
304
317
  }
305
318
  stop() {
@@ -33,6 +33,7 @@ import { openclawLlmContextDebug } from "./llm-context-debug.js";
33
33
  import { GroupSettingsCache } from "./group-settings.js";
34
34
  import { PermissionCache } from "./permissions.js";
35
35
  import { buildFriendRequestEnvelope } from "./friend-request-turn.js";
36
+ import { buildAwarenessNoteEnvelope } from "./awareness-note-turn.js";
36
37
  import { handlePermissionResult } from "./permission-result.js";
37
38
  const { setRuntime: setOpenclawClawlingRuntime, getRuntime: getOpenclawClawlingRuntime } = createPluginRuntimeStore("clawchat-plugin-openclaw runtime not initialized");
38
39
  export { setOpenclawClawlingRuntime, getOpenclawClawlingRuntime };
@@ -2008,8 +2009,15 @@ export async function startOpenclawClawlingGateway(params) {
2008
2009
  // to one observation). Build one synthetic reasoning turn so the agent can
2009
2010
  // act on the pending request; the prompt text varies by policy state.
2010
2011
  const state = permissionCache.stateOf("friend.add");
2012
+ // Address the turn to the activation-recorded owner conversation
2013
+ // (cnv_…) — a usr_… chat_id is rejected by member-backend, so the
2014
+ // agent's in-turn replies would be silently dropped by msghub.
2015
+ const ownerConversationId = resolveOwnerDirectConversationId();
2016
+ if (!ownerConversationId) {
2017
+ log?.info?.(`[${accountId}] clawchat-plugin-openclaw friend.request turn has no activation conversation on record; in-turn replies will not be deliverable`);
2018
+ }
2011
2019
  log?.info?.(`[${accountId}] clawchat-plugin-openclaw friend.request signal received; dispatching reasoning turn entity_id=${entityId} policy=${state}`);
2012
- void handleInboundEnvelope(buildFriendRequestEnvelope({ account, state, entityId }));
2020
+ void handleInboundEnvelope(buildFriendRequestEnvelope({ account, state, entityId, ownerConversationId }));
2013
2021
  }
2014
2022
  else if (type === "friend.added" ||
2015
2023
  type === "friend.removed" ||
@@ -2022,39 +2030,10 @@ export async function startOpenclawClawlingGateway(params) {
2022
2030
  pendingAwarenessNote = true;
2023
2031
  queueMicrotask(() => {
2024
2032
  pendingAwarenessNote = false;
2025
- const now = Date.now();
2026
- void handleInboundEnvelope({
2027
- version: "2",
2028
- event: EVENT.MESSAGE_SEND,
2029
- trace_id: `clawchat-plugin-openclaw-awareness-${now}`,
2030
- emitted_at: now,
2031
- chat_id: account.ownerUserId,
2032
- chat_type: "direct",
2033
- to: { id: account.userId, type: "direct" },
2034
- sender: { id: "clawchat-awareness", type: "direct", nick_name: "ClawChat" },
2035
- payload: {
2036
- message_id: `clawchat-plugin-openclaw-awareness-${now}`,
2037
- message_mode: "normal",
2038
- message: {
2039
- body: {
2040
- fragments: [
2041
- {
2042
- kind: "text",
2043
- text: "ClawChat awareness: your contacts or conversations have been updated.",
2044
- },
2045
- ],
2046
- },
2047
- context: { mentions: [], reply: null },
2048
- streaming: {
2049
- status: "static",
2050
- sequence: 0,
2051
- mutation_policy: "sealed",
2052
- started_at: null,
2053
- completed_at: null,
2054
- },
2055
- },
2056
- },
2057
- });
2033
+ void handleInboundEnvelope(buildAwarenessNoteEnvelope({
2034
+ account,
2035
+ ownerConversationId: resolveOwnerDirectConversationId(),
2036
+ }));
2058
2037
  });
2059
2038
  }
2060
2039
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawling/clawchat-plugin-openclaw",
3
- "version": "2026.7.8-1",
3
+ "version": "2026.7.8-3",
4
4
  "description": "OpenClaw ClawChat channel plugin",
5
5
  "license": "MIT",
6
6
  "author": "CLAWLING PTE. LTD.",
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Synthetic envelope builder for the lighter-awareness consolidated note
3
+ * (friend.added / friend.removed / friend.profile_updated / conversation.*).
4
+ *
5
+ * Like the friend-request turn, the note runs in the owner's direct
6
+ * conversation and must be addressed to the conversation id (cnv_…) recorded
7
+ * at activation. A usr_… id is not a valid chat_id — member-backend rejects it
8
+ * with "invalid conversation id" and msghub silently drops the agent's
9
+ * in-turn replies.
10
+ */
11
+ import { EVENT, type Envelope } from "./protocol-types.ts";
12
+ import type { ResolvedOpenclawClawlingAccount } from "./config.ts";
13
+
14
+ export interface BuildAwarenessNoteEnvelopeParams {
15
+ account: ResolvedOpenclawClawlingAccount;
16
+ /**
17
+ * The owner's direct conversation id (cnv_…) recorded at activation, or
18
+ * null when none is recorded (the note still runs, but replies will not be
19
+ * deliverable until activation records the owner conversation).
20
+ */
21
+ ownerConversationId: string | null;
22
+ }
23
+
24
+ export function buildAwarenessNoteEnvelope(
25
+ params: BuildAwarenessNoteEnvelopeParams,
26
+ ): Envelope {
27
+ const { account, ownerConversationId } = params;
28
+ const now = Date.now();
29
+ return {
30
+ version: "2",
31
+ event: EVENT.MESSAGE_SEND,
32
+ trace_id: `clawchat-plugin-openclaw-awareness-${now}`,
33
+ emitted_at: now,
34
+ chat_id: ownerConversationId ?? account.ownerUserId,
35
+ chat_type: "direct",
36
+ to: { id: account.userId, type: "direct" },
37
+ sender: { id: "clawchat-awareness", type: "direct", nick_name: "ClawChat" },
38
+ payload: {
39
+ message_id: `clawchat-plugin-openclaw-awareness-${now}`,
40
+ message_mode: "normal",
41
+ message: {
42
+ body: {
43
+ fragments: [
44
+ {
45
+ kind: "text",
46
+ text: "ClawChat awareness: your contacts or conversations have been updated.",
47
+ },
48
+ ],
49
+ },
50
+ context: { mentions: [], reply: null },
51
+ streaming: {
52
+ status: "static",
53
+ sequence: 0,
54
+ mutation_policy: "sealed",
55
+ started_at: null,
56
+ completed_at: null,
57
+ },
58
+ },
59
+ },
60
+ } as unknown as Envelope;
61
+ }
@@ -18,18 +18,24 @@ import type { ResolvedOpenclawClawlingAccount } from "./config.ts";
18
18
  *
19
19
  * - `deny` → inform the agent it should decline; no accept instruction.
20
20
  * - `ask` / `allow` → instruct the agent to review the pending request.
21
+ *
22
+ * The requester's user id is embedded so the agent does not go looking up the
23
+ * synthetic sender ("clawchat-friend-request" is not a real user).
21
24
  */
22
- export function friendRequestPromptFor(state: PermState): string {
25
+ export function friendRequestPromptFor(state: PermState, requesterUserId?: string): string {
26
+ const arrived = requesterUserId
27
+ ? `A new friend request has arrived from user ${requesterUserId}.`
28
+ : "A new friend request has arrived.";
23
29
  if (state === "deny") {
24
30
  return [
25
- "A new friend request has arrived.",
31
+ arrived,
26
32
  "Your current friend-add policy is set to deny.",
27
33
  "Do not add this contact.",
28
34
  "You may inform the requester that you cannot add them at this time.",
29
35
  ].join(" ");
30
36
  }
31
37
  return [
32
- "A new friend request has arrived.",
38
+ arrived,
33
39
  "Please review the pending request by calling `clawchat_list_friend_requests`",
34
40
  "and decide whether to accept it.",
35
41
  ].join(" ");
@@ -41,6 +47,13 @@ export interface BuildFriendRequestEnvelopeParams {
41
47
  state: PermState;
42
48
  /** entity_id from the notify.signal payload (the requesting user). */
43
49
  entityId: string;
50
+ /**
51
+ * The owner's direct conversation id (cnv_…) recorded at activation, or null
52
+ * when none is recorded. A usr_… id is not a valid chat_id — member-backend
53
+ * rejects it with "invalid conversation id" and the agent's in-turn replies
54
+ * are silently dropped by msghub — so the envelope must target this id.
55
+ */
56
+ ownerConversationId: string | null;
44
57
  }
45
58
 
46
59
  /**
@@ -52,15 +65,19 @@ export interface BuildFriendRequestEnvelopeParams {
52
65
  export function buildFriendRequestEnvelope(
53
66
  params: BuildFriendRequestEnvelopeParams,
54
67
  ): Envelope {
55
- const { account, state, entityId } = params;
56
- const text = friendRequestPromptFor(state);
68
+ const { account, state, entityId, ownerConversationId } = params;
69
+ const text = friendRequestPromptFor(state, entityId);
57
70
  const now = Date.now();
71
+ // Fall back to ownerUserId only when no activation conversation is recorded:
72
+ // the turn still runs (the agent can act via tools, e.g. accept the request),
73
+ // but its in-chat replies will not be deliverable until activation records
74
+ // the owner conversation.
58
75
  return {
59
76
  version: "2",
60
77
  event: EVENT.MESSAGE_SEND,
61
78
  trace_id: `clawchat-plugin-openclaw-friend-request-${entityId}-${now}`,
62
79
  emitted_at: now,
63
- chat_id: account.ownerUserId,
80
+ chat_id: ownerConversationId ?? account.ownerUserId,
64
81
  chat_type: "direct",
65
82
  to: { id: account.userId, type: "direct" },
66
83
  sender: {
@@ -374,6 +374,9 @@ const RESTART_WINDOW_MS = 30 * 60 * 1000;
374
374
  const MAX_RESTARTS_PER_WINDOW = 5;
375
375
  const INTRO_RETRY_DELAY_MS = 30_000;
376
376
  const INTRO_MAX_TRIES = 20;
377
+ // Backoff for transient start() failures (template CDN 429s, network blips):
378
+ // a one-shot bootstrap would otherwise stay dormant until the next restart.
379
+ const START_RETRY_DELAYS_MS = [30_000, 60_000, 120_000, 300_000];
377
380
 
378
381
  export class LivewareSampleSupervisor {
379
382
  private readonly deps: LivewareSampleSupervisorDeps;
@@ -391,8 +394,13 @@ export class LivewareSampleSupervisor {
391
394
  this.deps = deps;
392
395
  }
393
396
 
394
- /** Entry point. Never throws; every failure is logged + persisted only. */
397
+ /** Entry point. Never throws; every failure is logged + persisted only.
398
+ * Transient failures are retried on the START_RETRY_DELAYS_MS backoff. */
395
399
  async start(): Promise<void> {
400
+ await this.startAttempt(0);
401
+ }
402
+
403
+ private async startAttempt(attempt: number): Promise<void> {
396
404
  const { deps } = this;
397
405
  try {
398
406
  if (!deps.enabled) return;
@@ -412,7 +420,17 @@ export class LivewareSampleSupervisor {
412
420
  // A mid-flow failure (e.g. registerApp rejected) may have left children
413
421
  // running without a persisted row — never leak orphan processes.
414
422
  this.killChildren();
415
- deps.log?.warn?.(`clawchat-plugin-openclaw liveware-sample start failed: ${String(err)}`);
423
+ const delayMs = START_RETRY_DELAYS_MS[attempt];
424
+ if (delayMs === undefined || this.stopped) {
425
+ deps.log?.warn?.(
426
+ `clawchat-plugin-openclaw liveware-sample start failed (attempt ${attempt + 1}, giving up): ${String(err)}`,
427
+ );
428
+ return;
429
+ }
430
+ deps.log?.warn?.(
431
+ `clawchat-plugin-openclaw liveware-sample start failed (attempt ${attempt + 1}, retrying in ${delayMs / 1000}s): ${String(err)}`,
432
+ );
433
+ this.schedule(() => { void this.startAttempt(attempt + 1); }, delayMs);
416
434
  }
417
435
  }
418
436
 
package/src/runtime.ts CHANGED
@@ -85,6 +85,7 @@ import { openclawLlmContextDebug } from "./llm-context-debug.ts";
85
85
  import { GroupSettingsCache } from "./group-settings.ts";
86
86
  import { PermissionCache } from "./permissions.ts";
87
87
  import { buildFriendRequestEnvelope } from "./friend-request-turn.ts";
88
+ import { buildAwarenessNoteEnvelope } from "./awareness-note-turn.ts";
88
89
  import { handlePermissionResult } from "./permission-result.ts";
89
90
 
90
91
  type Log = { debug?: (m: string) => void; info?: (m: string) => void; error?: (m: string) => void };
@@ -2422,10 +2423,21 @@ export async function startOpenclawClawlingGateway(params: StartGatewayParams):
2422
2423
  // to one observation). Build one synthetic reasoning turn so the agent can
2423
2424
  // act on the pending request; the prompt text varies by policy state.
2424
2425
  const state = permissionCache.stateOf("friend.add");
2426
+ // Address the turn to the activation-recorded owner conversation
2427
+ // (cnv_…) — a usr_… chat_id is rejected by member-backend, so the
2428
+ // agent's in-turn replies would be silently dropped by msghub.
2429
+ const ownerConversationId = resolveOwnerDirectConversationId();
2430
+ if (!ownerConversationId) {
2431
+ log?.info?.(
2432
+ `[${accountId}] clawchat-plugin-openclaw friend.request turn has no activation conversation on record; in-turn replies will not be deliverable`,
2433
+ );
2434
+ }
2425
2435
  log?.info?.(
2426
2436
  `[${accountId}] clawchat-plugin-openclaw friend.request signal received; dispatching reasoning turn entity_id=${entityId} policy=${state}`,
2427
2437
  );
2428
- void handleInboundEnvelope(buildFriendRequestEnvelope({ account, state, entityId }));
2438
+ void handleInboundEnvelope(
2439
+ buildFriendRequestEnvelope({ account, state, entityId, ownerConversationId }),
2440
+ );
2429
2441
  } else if (
2430
2442
  type === "friend.added" ||
2431
2443
  type === "friend.removed" ||
@@ -2441,39 +2453,12 @@ export async function startOpenclawClawlingGateway(params: StartGatewayParams):
2441
2453
  pendingAwarenessNote = true;
2442
2454
  queueMicrotask(() => {
2443
2455
  pendingAwarenessNote = false;
2444
- const now = Date.now();
2445
- void handleInboundEnvelope({
2446
- version: "2",
2447
- event: EVENT.MESSAGE_SEND,
2448
- trace_id: `clawchat-plugin-openclaw-awareness-${now}`,
2449
- emitted_at: now,
2450
- chat_id: account.ownerUserId,
2451
- chat_type: "direct",
2452
- to: { id: account.userId, type: "direct" },
2453
- sender: { id: "clawchat-awareness", type: "direct", nick_name: "ClawChat" },
2454
- payload: {
2455
- message_id: `clawchat-plugin-openclaw-awareness-${now}`,
2456
- message_mode: "normal",
2457
- message: {
2458
- body: {
2459
- fragments: [
2460
- {
2461
- kind: "text",
2462
- text: "ClawChat awareness: your contacts or conversations have been updated.",
2463
- },
2464
- ],
2465
- },
2466
- context: { mentions: [], reply: null },
2467
- streaming: {
2468
- status: "static",
2469
- sequence: 0,
2470
- mutation_policy: "sealed",
2471
- started_at: null,
2472
- completed_at: null,
2473
- },
2474
- },
2475
- },
2476
- } as unknown as Envelope);
2456
+ void handleInboundEnvelope(
2457
+ buildAwarenessNoteEnvelope({
2458
+ account,
2459
+ ownerConversationId: resolveOwnerDirectConversationId(),
2460
+ }),
2461
+ );
2477
2462
  });
2478
2463
  }
2479
2464
  }