@clawling/clawchat-plugin-openclaw 2026.6.30-3 → 2026.7.1
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/src/api-client.js +13 -0
- package/dist/src/config.js +3 -0
- package/dist/src/friend-request-turn.js +73 -0
- package/dist/src/gate-outcome.js +26 -0
- package/dist/src/inbound.js +11 -0
- package/dist/src/permission-result.js +107 -0
- package/dist/src/permissions.js +6 -0
- package/dist/src/reply-dispatcher.js +9 -0
- package/dist/src/runtime.js +124 -16
- package/dist/src/tools.js +43 -10
- package/dist/src/ws-client.js +19 -6
- package/openclaw.plugin.json +2 -0
- package/package.json +1 -1
- package/src/api-client.ts +23 -0
- package/src/api-types.ts +1 -1
- package/src/config.ts +7 -0
- package/src/friend-request-turn.ts +87 -0
- package/src/gate-outcome.ts +44 -0
- package/src/inbound.ts +28 -0
- package/src/permission-result.ts +128 -0
- package/src/permissions.ts +13 -0
- package/src/reply-dispatcher.ts +10 -0
- package/src/runtime.ts +135 -16
- package/src/tools.ts +44 -10
- package/src/ws-client.ts +21 -6
package/src/runtime.ts
CHANGED
|
@@ -78,6 +78,9 @@ import {
|
|
|
78
78
|
import { createGroupMessageCoalescer } from "./group-message-coalescer.ts";
|
|
79
79
|
import { openclawLlmContextDebug } from "./llm-context-debug.ts";
|
|
80
80
|
import { GroupSettingsCache } from "./group-settings.ts";
|
|
81
|
+
import { PermissionCache } from "./permissions.ts";
|
|
82
|
+
import { buildFriendRequestEnvelope } from "./friend-request-turn.ts";
|
|
83
|
+
import { handlePermissionResult } from "./permission-result.ts";
|
|
81
84
|
|
|
82
85
|
type Log = { debug?: (m: string) => void; info?: (m: string) => void; error?: (m: string) => void };
|
|
83
86
|
type ChannelContextBuilder = (params: Record<string, unknown>) => unknown;
|
|
@@ -1752,6 +1755,7 @@ export async function startOpenclawClawlingGateway(params: StartGatewayParams):
|
|
|
1752
1755
|
context: wsLogContext,
|
|
1753
1756
|
});
|
|
1754
1757
|
const groupSettingsCache = new GroupSettingsCache();
|
|
1758
|
+
const permissionCache = new PermissionCache();
|
|
1755
1759
|
// Findings 2 & 3 — gate group dispatch on the IN-FLIGHT settings pull, not just
|
|
1756
1760
|
// the first one. The pull is fire-and-forget, so replayed/live group messages
|
|
1757
1761
|
// could otherwise enter the governance gate against a stale/empty cache
|
|
@@ -1829,6 +1833,28 @@ export async function startOpenclawClawlingGateway(params: StartGatewayParams):
|
|
|
1829
1833
|
}
|
|
1830
1834
|
})();
|
|
1831
1835
|
};
|
|
1836
|
+
// Best-effort pull of the agent's permission policy from the backend.
|
|
1837
|
+
// Called at startup (bypassRefresh: true, mirrors pullGroupSettings startup)
|
|
1838
|
+
// and on every successful (re)connect after hello-ok. On failure, logs and
|
|
1839
|
+
// keeps the stale cache — never throws, never blocks message handling.
|
|
1840
|
+
const pullPermissions = (opts?: { bypassRefresh?: boolean }): void => {
|
|
1841
|
+
void (async () => {
|
|
1842
|
+
try {
|
|
1843
|
+
const policy = opts?.bypassRefresh
|
|
1844
|
+
? await (conversationApiClient ??= buildConversationApiClient()).getMyPermissions()
|
|
1845
|
+
: await getConversationApiClient().getMyPermissions();
|
|
1846
|
+
permissionCache.set(policy);
|
|
1847
|
+
log?.info?.(
|
|
1848
|
+
`[${accountId}] clawchat-plugin-openclaw permissions pull ok ops=${Object.keys(policy.byOperation).length}`,
|
|
1849
|
+
);
|
|
1850
|
+
} catch (err) {
|
|
1851
|
+
log?.error?.(
|
|
1852
|
+
`[${accountId}] clawchat-plugin-openclaw permissions pull failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1853
|
+
);
|
|
1854
|
+
// Keep stale cache; never throw.
|
|
1855
|
+
}
|
|
1856
|
+
})();
|
|
1857
|
+
};
|
|
1832
1858
|
const logAuthFailure = (reason: string) => {
|
|
1833
1859
|
if (authFailureLogged) return;
|
|
1834
1860
|
authFailureLogged = true;
|
|
@@ -1850,10 +1876,11 @@ export async function startOpenclawClawlingGateway(params: StartGatewayParams):
|
|
|
1850
1876
|
};
|
|
1851
1877
|
let dispatchActivationBootstrap: () => Promise<void> = async () => {};
|
|
1852
1878
|
// Set by dispatchTurnToAgent when it dispatches the synthetic
|
|
1853
|
-
// `clawchat-bootstrap` turn: true only if the
|
|
1854
|
-
// outbound
|
|
1855
|
-
// whether to mark the bootstrap sent or leave it pending for
|
|
1856
|
-
|
|
1879
|
+
// `clawchat-bootstrap` turn: true only if the greeting was actually delivered
|
|
1880
|
+
// (a real successful outbound send — block or final). The bootstrap dispatcher
|
|
1881
|
+
// reads it to decide whether to mark the bootstrap sent or leave it pending for
|
|
1882
|
+
// a later retry.
|
|
1883
|
+
let bootstrapGreetingDelivered = false;
|
|
1857
1884
|
// Per-connection retry for a pending activation bootstrap (see the module-level
|
|
1858
1885
|
// constants). Re-attempts on the live connection while `wsReady`, bounded by
|
|
1859
1886
|
// ACTIVATION_BOOTSTRAP_MAX_RETRIES; the budget resets on each fresh connect.
|
|
@@ -1961,6 +1988,8 @@ export async function startOpenclawClawlingGateway(params: StartGatewayParams):
|
|
|
1961
1988
|
// Re-pull per-group agent settings on every successful (re)connect so the
|
|
1962
1989
|
// cache stays current after a network interruption or token refresh.
|
|
1963
1990
|
pullGroupSettings();
|
|
1991
|
+
// Re-pull permission policy alongside group settings on every (re)connect.
|
|
1992
|
+
pullPermissions();
|
|
1964
1993
|
// §A.1 — arm the proactive refresh timer from the live token's `exp`
|
|
1965
1994
|
// every time a connection becomes ready (re-armed after every refresh via
|
|
1966
1995
|
// the gateway re-enter).
|
|
@@ -2192,6 +2221,11 @@ export async function startOpenclawClawlingGateway(params: StartGatewayParams):
|
|
|
2192
2221
|
});
|
|
2193
2222
|
};
|
|
2194
2223
|
|
|
2224
|
+
// Debounce flag for lighter-awareness consolidated notes.
|
|
2225
|
+
// A single queueMicrotask coalesces all awareness signals that arrive in
|
|
2226
|
+
// the same synchronous turn into exactly one handleInboundEnvelope call.
|
|
2227
|
+
let pendingAwarenessNote = false;
|
|
2228
|
+
|
|
2195
2229
|
client.on("notify:signal", (env: Envelope) => {
|
|
2196
2230
|
// §9.4 reliable system notification. The plugin holds no friend/roster
|
|
2197
2231
|
// cache (friends are fetched on demand via REST tools), so there is nothing
|
|
@@ -2203,11 +2237,17 @@ export async function startOpenclawClawlingGateway(params: StartGatewayParams):
|
|
|
2203
2237
|
? (env.payload as Record<string, unknown>)
|
|
2204
2238
|
: undefined;
|
|
2205
2239
|
const type = typeof payload?.type === "string" ? payload.type : "";
|
|
2240
|
+
const entityId = typeof payload?.entity_id === "string" ? payload.entity_id : "";
|
|
2206
2241
|
if (type === "agent.config.changed") {
|
|
2207
2242
|
log?.info?.(
|
|
2208
2243
|
`[${accountId}] clawchat-plugin-openclaw agent.config.changed signal received; re-pulling group settings`,
|
|
2209
2244
|
);
|
|
2210
2245
|
pullGroupSettings();
|
|
2246
|
+
} else if (type === "agent.permission.changed") {
|
|
2247
|
+
log?.info?.(
|
|
2248
|
+
`[${accountId}] clawchat-plugin-openclaw agent.permission.changed signal received; re-pulling permissions`,
|
|
2249
|
+
);
|
|
2250
|
+
pullPermissions();
|
|
2211
2251
|
} else if (type === "clawchat.skill.update.check") {
|
|
2212
2252
|
// Content-free trigger: check the official skill source in code and, if
|
|
2213
2253
|
// there is an update, ask the owner in chat for consent. The atomic
|
|
@@ -2222,6 +2262,66 @@ export async function startOpenclawClawlingGateway(params: StartGatewayParams):
|
|
|
2222
2262
|
}`,
|
|
2223
2263
|
);
|
|
2224
2264
|
});
|
|
2265
|
+
} else if (type === "friend.request") {
|
|
2266
|
+
// Dedup is already applied above by notifySignalObserver.observe (the live
|
|
2267
|
+
// frame and its reliable-inbox replay share the same event_id and collapse
|
|
2268
|
+
// to one observation). Build one synthetic reasoning turn so the agent can
|
|
2269
|
+
// act on the pending request; the prompt text varies by policy state.
|
|
2270
|
+
const state = permissionCache.stateOf("friend.add");
|
|
2271
|
+
log?.info?.(
|
|
2272
|
+
`[${accountId}] clawchat-plugin-openclaw friend.request signal received; dispatching reasoning turn entity_id=${entityId} policy=${state}`,
|
|
2273
|
+
);
|
|
2274
|
+
void handleInboundEnvelope(buildFriendRequestEnvelope({ account, state, entityId }));
|
|
2275
|
+
} else if (
|
|
2276
|
+
type === "friend.added" ||
|
|
2277
|
+
type === "friend.removed" ||
|
|
2278
|
+
type === "friend.profile_updated" ||
|
|
2279
|
+
type.startsWith("conversation.")
|
|
2280
|
+
) {
|
|
2281
|
+
// Lighter-awareness: log + optional single consolidated note. Never
|
|
2282
|
+
// injects a full reasoning turn per event (unlike friend.request).
|
|
2283
|
+
log?.info?.(
|
|
2284
|
+
`[${accountId}] clawchat-plugin-openclaw ${type} signal received; lighter-awareness handling`,
|
|
2285
|
+
);
|
|
2286
|
+
if (account.awarenessNote && !pendingAwarenessNote) {
|
|
2287
|
+
pendingAwarenessNote = true;
|
|
2288
|
+
queueMicrotask(() => {
|
|
2289
|
+
pendingAwarenessNote = false;
|
|
2290
|
+
const now = Date.now();
|
|
2291
|
+
void handleInboundEnvelope({
|
|
2292
|
+
version: "2",
|
|
2293
|
+
event: EVENT.MESSAGE_SEND,
|
|
2294
|
+
trace_id: `clawchat-plugin-openclaw-awareness-${now}`,
|
|
2295
|
+
emitted_at: now,
|
|
2296
|
+
chat_id: account.ownerUserId,
|
|
2297
|
+
chat_type: "direct",
|
|
2298
|
+
to: { id: account.userId, type: "direct" },
|
|
2299
|
+
sender: { id: "clawchat-awareness", type: "direct", nick_name: "ClawChat" },
|
|
2300
|
+
payload: {
|
|
2301
|
+
message_id: `clawchat-plugin-openclaw-awareness-${now}`,
|
|
2302
|
+
message_mode: "normal",
|
|
2303
|
+
message: {
|
|
2304
|
+
body: {
|
|
2305
|
+
fragments: [
|
|
2306
|
+
{
|
|
2307
|
+
kind: "text",
|
|
2308
|
+
text: "ClawChat awareness: your contacts or conversations have been updated.",
|
|
2309
|
+
},
|
|
2310
|
+
],
|
|
2311
|
+
},
|
|
2312
|
+
context: { mentions: [], reply: null },
|
|
2313
|
+
streaming: {
|
|
2314
|
+
status: "static",
|
|
2315
|
+
sequence: 0,
|
|
2316
|
+
mutation_policy: "sealed",
|
|
2317
|
+
started_at: null,
|
|
2318
|
+
completed_at: null,
|
|
2319
|
+
},
|
|
2320
|
+
},
|
|
2321
|
+
},
|
|
2322
|
+
} as unknown as Envelope);
|
|
2323
|
+
});
|
|
2324
|
+
}
|
|
2225
2325
|
}
|
|
2226
2326
|
}
|
|
2227
2327
|
});
|
|
@@ -2603,7 +2703,7 @@ export async function startOpenclawClawlingGateway(params: StartGatewayParams):
|
|
|
2603
2703
|
chatType: turn.peer.kind,
|
|
2604
2704
|
agentId: route.agentId,
|
|
2605
2705
|
});
|
|
2606
|
-
const { dispatcher, replyOptions, markDispatchIdle } =
|
|
2706
|
+
const { dispatcher, replyOptions, markDispatchIdle, getOutboundDeliveredCount } =
|
|
2607
2707
|
createOpenclawClawlingReplyDispatcher({
|
|
2608
2708
|
cfg,
|
|
2609
2709
|
runtime,
|
|
@@ -2649,10 +2749,14 @@ export async function startOpenclawClawlingGateway(params: StartGatewayParams):
|
|
|
2649
2749
|
(dispatchResult as { queuedFinal?: boolean } | undefined)?.queuedFinal,
|
|
2650
2750
|
);
|
|
2651
2751
|
if (turn.senderId === "clawchat-bootstrap") {
|
|
2652
|
-
// Did the
|
|
2653
|
-
//
|
|
2654
|
-
//
|
|
2655
|
-
|
|
2752
|
+
// Did the greeting actually reach the user? The reliable signal is a
|
|
2753
|
+
// real, successful outbound send (the agent may deliver the greeting
|
|
2754
|
+
// as a `block`, not a `final`, and a cancelled send still shows up in
|
|
2755
|
+
// host `counts.block`). Treat a queued/counted final as delivered too,
|
|
2756
|
+
// to cover deferred/human-delay finals not yet flushed at this point.
|
|
2757
|
+
const deliveredOutbound = getOutboundDeliveredCount?.() ?? 0;
|
|
2758
|
+
bootstrapGreetingDelivered =
|
|
2759
|
+
deliveredOutbound > 0 || queuedFinal || (counts.final ?? 0) > 0;
|
|
2656
2760
|
}
|
|
2657
2761
|
log?.info?.(
|
|
2658
2762
|
`[${accountId}] clawchat-plugin-openclaw dispatch complete msg=${turn.messageId} queuedFinal=${queuedFinal} counts=${JSON.stringify(counts)}`,
|
|
@@ -2919,6 +3023,18 @@ export async function startOpenclawClawlingGateway(params: StartGatewayParams):
|
|
|
2919
3023
|
account,
|
|
2920
3024
|
log,
|
|
2921
3025
|
consumeOwnerConsent: consumeOwnerSkillConsent,
|
|
3026
|
+
consumePermissionResult: async (metadata) => {
|
|
3027
|
+
// A permission_result receipt closes the loop on an earlier request.
|
|
3028
|
+
// Dedup by request_id; on a fresh receipt feed the synthetic context
|
|
3029
|
+
// note back to the agent so it can react to the outcome.
|
|
3030
|
+
const note = handlePermissionResult(metadata, account);
|
|
3031
|
+
if (note) {
|
|
3032
|
+
log?.info?.(
|
|
3033
|
+
`[${accountId}] clawchat-plugin-openclaw permission_result receipt op=${metadata.operation} outcome=${metadata.outcome} request_id=${metadata.request_id}; dispatching context note`,
|
|
3034
|
+
);
|
|
3035
|
+
void handleInboundEnvelope(note);
|
|
3036
|
+
}
|
|
3037
|
+
},
|
|
2922
3038
|
ingest: async (turn) => {
|
|
2923
3039
|
ingestResult = await ingestTurn(turn);
|
|
2924
3040
|
},
|
|
@@ -2961,7 +3077,7 @@ export async function startOpenclawClawlingGateway(params: StartGatewayParams):
|
|
|
2961
3077
|
const claimedBootstrap = bootstrap;
|
|
2962
3078
|
claimedInFlight = true;
|
|
2963
3079
|
incrementActivationBootstrapInFlight(accountId);
|
|
2964
|
-
|
|
3080
|
+
bootstrapGreetingDelivered = false;
|
|
2965
3081
|
const result = await handleInboundEnvelope(
|
|
2966
3082
|
buildActivationBootstrapEnvelope({ account, conversationId: claimedBootstrap.conversationId }),
|
|
2967
3083
|
);
|
|
@@ -2970,15 +3086,15 @@ export async function startOpenclawClawlingGateway(params: StartGatewayParams):
|
|
|
2970
3086
|
armBootstrapRetry();
|
|
2971
3087
|
return;
|
|
2972
3088
|
}
|
|
2973
|
-
// Only mark the bootstrap done once the
|
|
2974
|
-
// (a
|
|
2975
|
-
//
|
|
2976
|
-
// agent went off to write a file/note instead of replying — leave the
|
|
3089
|
+
// Only mark the bootstrap done once the greeting was actually delivered
|
|
3090
|
+
// (a real successful outbound send). If the turn was handed off but nothing
|
|
3091
|
+
// reached the user — e.g. the greeting send was cancelled by a reconnect, or
|
|
3092
|
+
// the agent went off to write a file/note instead of replying — leave the
|
|
2977
3093
|
// bootstrap pending (release the claim) and retry on the live connection,
|
|
2978
3094
|
// rather than silently consuming it forever or waiting for the next reconnect.
|
|
2979
|
-
if (!
|
|
3095
|
+
if (!bootstrapGreetingDelivered) {
|
|
2980
3096
|
log?.info?.(
|
|
2981
|
-
`[${accountId}] clawchat-plugin-openclaw activation bootstrap dispatched but
|
|
3097
|
+
`[${accountId}] clawchat-plugin-openclaw activation bootstrap dispatched but greeting was not delivered; leaving pending for retry`,
|
|
2982
3098
|
);
|
|
2983
3099
|
releaseBootstrap();
|
|
2984
3100
|
armBootstrapRetry();
|
|
@@ -3015,6 +3131,9 @@ export async function startOpenclawClawlingGateway(params: StartGatewayParams):
|
|
|
3015
3131
|
// rotating `account.token` out from under the already-constructed WS client.
|
|
3016
3132
|
// Settling this releases the Finding-2 group-dispatch gate.
|
|
3017
3133
|
pullGroupSettings({ bypassRefresh: true });
|
|
3134
|
+
// Initial pull of permission policy at startup (best-effort; errors logged,
|
|
3135
|
+
// stale cache kept). Uses bypassRefresh for the same reason as pullGroupSettings.
|
|
3136
|
+
pullPermissions({ bypassRefresh: true });
|
|
3018
3137
|
// Safety: never strand a held group message if the runtime is torn down while a
|
|
3019
3138
|
// pull is in flight. `waitWithTimeout` already releases group waiters on abort,
|
|
3020
3139
|
// but force the gate open too so any later `wait()` resolves immediately.
|
package/src/tools.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
ClawlingApiError,
|
|
10
10
|
type Profile,
|
|
11
11
|
} from "./api-types.ts";
|
|
12
|
+
import { mapGateOutcome } from "./gate-outcome.ts";
|
|
12
13
|
import { resolveOpenclawClawlingAccount } from "./config.ts";
|
|
13
14
|
import {
|
|
14
15
|
clawChatDbPathForStateDir,
|
|
@@ -389,6 +390,39 @@ export function registerOpenclawClawlingTools(
|
|
|
389
390
|
})();
|
|
390
391
|
}
|
|
391
392
|
|
|
393
|
+
/**
|
|
394
|
+
* Like withClient, but maps gate codes (21001/21003) to structured outcomes
|
|
395
|
+
* via mapGateOutcome so the agent sees a clear pending/forbidden status
|
|
396
|
+
* instead of a generic error. Used for friend and moment mutation tools.
|
|
397
|
+
*/
|
|
398
|
+
function withGatedClient<T>(
|
|
399
|
+
fn: (client: ReturnType<typeof createOpenclawClawlingApiClient>) => Promise<T>,
|
|
400
|
+
): Promise<OpenClawAgentToolResult<unknown>> {
|
|
401
|
+
return (async (): Promise<OpenClawAgentToolResult<unknown>> => {
|
|
402
|
+
const built = buildClient();
|
|
403
|
+
if (!built.ok) return built.error;
|
|
404
|
+
try {
|
|
405
|
+
const data = await fn(built.client);
|
|
406
|
+
return jsonResponse(data);
|
|
407
|
+
} catch (err) {
|
|
408
|
+
if (err instanceof ClawlingApiError) {
|
|
409
|
+
const code = typeof err.meta?.code === "number" ? err.meta.code : null;
|
|
410
|
+
if (code !== null) {
|
|
411
|
+
const rawData = err.meta?.data;
|
|
412
|
+
const gateData =
|
|
413
|
+
rawData !== null && rawData !== undefined && typeof rawData === "object"
|
|
414
|
+
? (rawData as Record<string, unknown>)
|
|
415
|
+
: {};
|
|
416
|
+
const outcome = mapGateOutcome(code, gateData);
|
|
417
|
+
if (outcome !== null) return jsonResponse(outcome);
|
|
418
|
+
}
|
|
419
|
+
return apiError(err);
|
|
420
|
+
}
|
|
421
|
+
return genericError(err);
|
|
422
|
+
}
|
|
423
|
+
})();
|
|
424
|
+
}
|
|
425
|
+
|
|
392
426
|
function workspaceRoot(ctx: OpenClawPluginToolContext): string | null {
|
|
393
427
|
const root = typeof ctx.workspaceDir === "string" ? ctx.workspaceDir.trim() : "";
|
|
394
428
|
return root || null;
|
|
@@ -829,7 +863,7 @@ export function registerOpenclawClawlingTools(
|
|
|
829
863
|
async execute(_callId, params) {
|
|
830
864
|
return await recordClawchatToolCall("clawchat_send_friend_request", params, async () => {
|
|
831
865
|
const p = params as ClawchatSendFriendRequestParams;
|
|
832
|
-
return await
|
|
866
|
+
return await withGatedClient((c) => c.sendFriendRequest(p));
|
|
833
867
|
});
|
|
834
868
|
},
|
|
835
869
|
},
|
|
@@ -869,7 +903,7 @@ export function registerOpenclawClawlingTools(
|
|
|
869
903
|
async execute(_callId, params) {
|
|
870
904
|
return await recordClawchatToolCall("clawchat_accept_friend_request", params, async () => {
|
|
871
905
|
const p = params as ClawchatAcceptFriendRequestParams;
|
|
872
|
-
return await
|
|
906
|
+
return await withGatedClient((c) => c.acceptFriendRequest(p.requestId));
|
|
873
907
|
});
|
|
874
908
|
},
|
|
875
909
|
},
|
|
@@ -889,7 +923,7 @@ export function registerOpenclawClawlingTools(
|
|
|
889
923
|
async execute(_callId, params) {
|
|
890
924
|
return await recordClawchatToolCall("clawchat_reject_friend_request", params, async () => {
|
|
891
925
|
const p = params as ClawchatRejectFriendRequestParams;
|
|
892
|
-
return await
|
|
926
|
+
return await withGatedClient((c) => c.rejectFriendRequest(p.requestId));
|
|
893
927
|
});
|
|
894
928
|
},
|
|
895
929
|
},
|
|
@@ -909,7 +943,7 @@ export function registerOpenclawClawlingTools(
|
|
|
909
943
|
async execute(_callId, params) {
|
|
910
944
|
return await recordClawchatToolCall("clawchat_remove_friend", params, async () => {
|
|
911
945
|
const p = params as ClawchatRemoveFriendParams;
|
|
912
|
-
return await
|
|
946
|
+
return await withGatedClient((c) => c.removeFriend(p.friendUserId));
|
|
913
947
|
});
|
|
914
948
|
},
|
|
915
949
|
},
|
|
@@ -1084,7 +1118,7 @@ export function registerOpenclawClawlingTools(
|
|
|
1084
1118
|
if (!text && (!images || images.length === 0)) {
|
|
1085
1119
|
return validationError("clawchat-plugin-openclaw: at least one of text or images is required");
|
|
1086
1120
|
}
|
|
1087
|
-
return await
|
|
1121
|
+
return await withGatedClient((c) =>
|
|
1088
1122
|
c.createMoment({
|
|
1089
1123
|
...(text !== undefined ? { text } : {}),
|
|
1090
1124
|
...(images !== undefined ? { images } : {}),
|
|
@@ -1109,7 +1143,7 @@ export function registerOpenclawClawlingTools(
|
|
|
1109
1143
|
async execute(_callId, params) {
|
|
1110
1144
|
return await recordClawchatToolCall("clawchat_delete_moment", params, async () => {
|
|
1111
1145
|
const p = params as ClawchatDeleteMomentParams;
|
|
1112
|
-
return await
|
|
1146
|
+
return await withGatedClient((c) => c.deleteMoment(p.momentId));
|
|
1113
1147
|
});
|
|
1114
1148
|
},
|
|
1115
1149
|
},
|
|
@@ -1132,7 +1166,7 @@ export function registerOpenclawClawlingTools(
|
|
|
1132
1166
|
if (!p.emoji?.trim()) {
|
|
1133
1167
|
return validationError("clawchat-plugin-openclaw: emoji is required");
|
|
1134
1168
|
}
|
|
1135
|
-
return await
|
|
1169
|
+
return await withGatedClient((c) =>
|
|
1136
1170
|
c.toggleMomentReaction({ momentId: p.momentId, emoji: p.emoji }),
|
|
1137
1171
|
);
|
|
1138
1172
|
});
|
|
@@ -1157,7 +1191,7 @@ export function registerOpenclawClawlingTools(
|
|
|
1157
1191
|
if (!p.text?.trim()) {
|
|
1158
1192
|
return validationError("clawchat-plugin-openclaw: text is required");
|
|
1159
1193
|
}
|
|
1160
|
-
return await
|
|
1194
|
+
return await withGatedClient((c) =>
|
|
1161
1195
|
c.createMomentComment({ momentId: p.momentId, text: p.text }),
|
|
1162
1196
|
);
|
|
1163
1197
|
});
|
|
@@ -1182,7 +1216,7 @@ export function registerOpenclawClawlingTools(
|
|
|
1182
1216
|
if (!p.text?.trim()) {
|
|
1183
1217
|
return validationError("clawchat-plugin-openclaw: text is required");
|
|
1184
1218
|
}
|
|
1185
|
-
return await
|
|
1219
|
+
return await withGatedClient((c) =>
|
|
1186
1220
|
c.replyMomentComment({
|
|
1187
1221
|
momentId: p.momentId,
|
|
1188
1222
|
replyToCommentId: p.replyToCommentId,
|
|
@@ -1208,7 +1242,7 @@ export function registerOpenclawClawlingTools(
|
|
|
1208
1242
|
async execute(_callId, params) {
|
|
1209
1243
|
return await recordClawchatToolCall("clawchat_delete_moment_comment", params, async () => {
|
|
1210
1244
|
const p = params as ClawchatDeleteMomentCommentParams;
|
|
1211
|
-
return await
|
|
1245
|
+
return await withGatedClient((c) =>
|
|
1212
1246
|
c.deleteMomentComment({ momentId: p.momentId, commentId: p.commentId }),
|
|
1213
1247
|
);
|
|
1214
1248
|
});
|
package/src/ws-client.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
TransportError,
|
|
10
10
|
isBusinessDispatchEvent,
|
|
11
11
|
type ConnState,
|
|
12
|
+
type ConnectCapabilities,
|
|
12
13
|
type ConnectPayload,
|
|
13
14
|
type EmptyPayload,
|
|
14
15
|
type Envelope,
|
|
@@ -146,6 +147,24 @@ type PendingAck = {
|
|
|
146
147
|
reject: (err: Error) => void;
|
|
147
148
|
};
|
|
148
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Returns the capability set advertised in the connect handshake.
|
|
152
|
+
* Exported so tests can assert on the exact fields without a full WS setup.
|
|
153
|
+
*
|
|
154
|
+
* permission_events: forward-compat: advertises readiness for a future native
|
|
155
|
+
* `permission.receipt` wire frame; today the permission-decision receipt is
|
|
156
|
+
* delivered as a system message until msghub emits the native frame.
|
|
157
|
+
*/
|
|
158
|
+
export function buildConnectCapabilities(): ConnectCapabilities {
|
|
159
|
+
return {
|
|
160
|
+
multi_device: false,
|
|
161
|
+
device_replay: true,
|
|
162
|
+
chat_meta_events: true,
|
|
163
|
+
notify_signals: true,
|
|
164
|
+
permission_events: true,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
149
168
|
export class ClawChatClient extends EventEmitter {
|
|
150
169
|
private currentState: ConnState = "idle";
|
|
151
170
|
private connectResolve?: () => void;
|
|
@@ -398,12 +417,8 @@ export class ClawChatClient extends EventEmitter {
|
|
|
398
417
|
// *send* history.transit, which a single-device agent never does, and we
|
|
399
418
|
// do not handle inbound history.transit — advertising it would invite
|
|
400
419
|
// frames we cannot process. This is a deliberate, spec-legal omission.
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
device_replay: true,
|
|
404
|
-
chat_meta_events: true,
|
|
405
|
-
notify_signals: true,
|
|
406
|
-
},
|
|
420
|
+
// permission_events: forward-compat; see buildConnectCapabilities().
|
|
421
|
+
capabilities: buildConnectCapabilities(),
|
|
407
422
|
};
|
|
408
423
|
const traceId = this.nextTraceId();
|
|
409
424
|
this.expectedConnectTraceId = traceId;
|