@clawling/clawchat-plugin-openclaw 2026.7.29-1 → 2026.8.2-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 +3 -0
- package/dist/src/awareness-note-turn.js +27 -1
- package/dist/src/client.js +1 -0
- package/dist/src/liveware-sample.js +272 -41
- package/dist/src/no-reply.js +6 -4
- package/dist/src/outbound.js +81 -3
- package/dist/src/permission-result.js +20 -5
- package/dist/src/protocol-types.js +18 -0
- package/dist/src/reply-dispatcher.js +15 -3
- package/dist/src/runtime.js +125 -12
- package/dist/src/tools-schema.js +3 -0
- package/dist/src/tools.js +14 -2
- package/dist/src/ws-alignment.js +18 -1
- package/dist/src/ws-client.js +246 -34
- package/openclaw.plugin.json +1 -0
- package/package.json +1 -1
- package/skills/clawchat-core/SKILL.md +3 -2
- package/skills/manifest.json +6 -6
- package/src/api-client.ts +4 -0
- package/src/awareness-note-turn.ts +54 -5
- package/src/client.ts +3 -0
- package/src/liveware-sample.ts +287 -41
- package/src/no-reply.ts +6 -4
- package/src/outbound.ts +118 -4
- package/src/permission-result.ts +20 -4
- package/src/protocol-types.ts +19 -0
- package/src/reply-dispatcher.ts +27 -3
- package/src/runtime.ts +145 -12
- package/src/storage.ts +8 -2
- package/src/tools-schema.ts +5 -0
- package/src/tools.ts +21 -1
- package/src/ws-alignment.ts +28 -1
- package/src/ws-client.ts +267 -32
- package/dist/src/friend-request-turn.js +0 -83
|
@@ -64,9 +64,16 @@ function buildOutcomeNote(metadata) {
|
|
|
64
64
|
* Mirrors the `buildAwarenessNoteEnvelope` pattern: a synthetic inbound
|
|
65
65
|
* `message.send` targeting the owner's direct conversation, routed through
|
|
66
66
|
* `handleInboundEnvelope` so the agent can reason about the outcome.
|
|
67
|
+
*
|
|
68
|
+
* `ownerConversationId` MUST be the owner's direct conversation id (`cnv_…`)
|
|
69
|
+
* recorded at activation, never `account.ownerUserId`. The agent's in-turn
|
|
70
|
+
* reply inherits this note's `chat_id`, and msghub resolves chat_id through
|
|
71
|
+
* member-backend — a `usr_…` there comes back "invalid conversation id", so the
|
|
72
|
+
* reply is dropped and the sender never learns. Hence the required non-null
|
|
73
|
+
* parameter: there is no safe value to fall back to.
|
|
67
74
|
*/
|
|
68
75
|
export function buildPermissionResultEnvelope(params) {
|
|
69
|
-
const { account, metadata } = params;
|
|
76
|
+
const { account, metadata, ownerConversationId } = params;
|
|
70
77
|
const text = buildOutcomeNote(metadata);
|
|
71
78
|
const now = Date.now();
|
|
72
79
|
return {
|
|
@@ -74,7 +81,7 @@ export function buildPermissionResultEnvelope(params) {
|
|
|
74
81
|
event: EVENT.MESSAGE_SEND,
|
|
75
82
|
trace_id: `clawchat-plugin-openclaw-permission-result-${metadata.request_id}-${now}`,
|
|
76
83
|
emitted_at: now,
|
|
77
|
-
chat_id:
|
|
84
|
+
chat_id: ownerConversationId,
|
|
78
85
|
chat_type: "direct",
|
|
79
86
|
to: { id: account.userId, type: "direct" },
|
|
80
87
|
sender: {
|
|
@@ -109,12 +116,20 @@ export function buildPermissionResultEnvelope(params) {
|
|
|
109
116
|
*
|
|
110
117
|
* Returns a synthetic context-note {@link Envelope} on a fresh receipt so
|
|
111
118
|
* the caller can feed it to `handleInboundEnvelope`; returns `null` when
|
|
112
|
-
* the `request_id` has already been processed
|
|
119
|
+
* the `request_id` has already been processed, or when `ownerConversationId`
|
|
120
|
+
* is absent.
|
|
121
|
+
*
|
|
122
|
+
* A null `ownerConversationId` (activation has not recorded the owner's direct
|
|
123
|
+
* conversation yet) is checked BEFORE the dedup mark on purpose: the receipt
|
|
124
|
+
* cannot be routed anywhere, so burning its dedup slot would permanently
|
|
125
|
+
* swallow it — the replayed copy must still render once the id is known.
|
|
113
126
|
*/
|
|
114
|
-
export function handlePermissionResult(metadata, account) {
|
|
127
|
+
export function handlePermissionResult(metadata, account, ownerConversationId) {
|
|
128
|
+
if (!ownerConversationId)
|
|
129
|
+
return null;
|
|
115
130
|
if (metadata.request_id && _seenRequestIds.has(metadata.request_id))
|
|
116
131
|
return null;
|
|
117
132
|
if (metadata.request_id)
|
|
118
133
|
_seenRequestIds.add(metadata.request_id);
|
|
119
|
-
return buildPermissionResultEnvelope({ account, metadata });
|
|
134
|
+
return buildPermissionResultEnvelope({ account, metadata, ownerConversationId });
|
|
120
135
|
}
|
|
@@ -58,6 +58,24 @@ export class MessageSendError extends Error {
|
|
|
58
58
|
this.chatId = chatId;
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* `message.error` codes that PROVE the target conversation is unreachable for
|
|
63
|
+
* this sender right now (docs/client-integration.md §14.3). Both are documented
|
|
64
|
+
* as terminal for the affected uplink.
|
|
65
|
+
*
|
|
66
|
+
* Deliberately excludes `capability_missing` and `unsupported_version`: those
|
|
67
|
+
* are terminal too, but they indict THIS CLIENT's handshake/envelope, not the
|
|
68
|
+
* conversation — muting the chat on them would be a false positive.
|
|
69
|
+
*
|
|
70
|
+
* NOTE: "terminal for this uplink" is NOT "terminal forever". A deleted
|
|
71
|
+
* conversation can be revived in place under the same `cnv_` id (that is the
|
|
72
|
+
* path an agent re-pairing takes), so anything derived from these codes must
|
|
73
|
+
* be recoverable — see SERVER_REJECTION_TTL_MS in ws-client.ts.
|
|
74
|
+
*/
|
|
75
|
+
export const TERMINAL_CHAT_CODES = new Set([
|
|
76
|
+
"chat_not_found",
|
|
77
|
+
"not_member",
|
|
78
|
+
]);
|
|
61
79
|
export class StateError extends Error {
|
|
62
80
|
name = "StateError";
|
|
63
81
|
}
|
|
@@ -256,14 +256,23 @@ function formatApprovalSummary(payload) {
|
|
|
256
256
|
* as separate ClawChat messages.
|
|
257
257
|
*/
|
|
258
258
|
export function createOpenclawClawlingReplyDispatcher(options) {
|
|
259
|
-
const { cfg, runtime, account, client, target, replyCtx, inboundMessageId, store, log, } = options;
|
|
259
|
+
const { cfg, runtime, account, client, target, replyCtx, inboundMessageId, store, resolveOwnerConversationId, log, } = options;
|
|
260
260
|
const isGroupTarget = target.chatType === "group";
|
|
261
261
|
const outputVisibility = effectiveOutputVisibility(account, target.chatId, target.chatType);
|
|
262
262
|
const splitFullOutput = outputVisibility === "full";
|
|
263
263
|
const splitNormalBlockOutput = outputVisibility === "normal";
|
|
264
|
+
// The escalation target is the owner's ACTIVATION CONVERSATION id, never the
|
|
265
|
+
// owner's usr_… id: chat_id is resolved by msghub through member-backend, and
|
|
266
|
+
// a usr_… comes back "invalid conversation id" — the frame is dropped with no
|
|
267
|
+
// negative ack to this client, so the owner silently never gets the approval
|
|
268
|
+
// request. Both ids are required: ownerUserId gates whether an owner exists at
|
|
269
|
+
// all, the conversation id is where a frame can actually be delivered.
|
|
264
270
|
const ownerDirectTarget = () => {
|
|
265
271
|
const ownerUserId = account.ownerUserId?.trim();
|
|
266
|
-
|
|
272
|
+
if (!ownerUserId)
|
|
273
|
+
return null;
|
|
274
|
+
const conversationId = resolveOwnerConversationId?.()?.trim();
|
|
275
|
+
return conversationId ? { chatId: conversationId, chatType: "direct" } : null;
|
|
267
276
|
};
|
|
268
277
|
const humanDelay = runtime.channel.reply.resolveHumanDelayConfig(cfg, account.userId);
|
|
269
278
|
const buildApiClient = () => {
|
|
@@ -448,7 +457,10 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
448
457
|
const sendOwnerAttention = async (fallbackText, richFragment = null) => {
|
|
449
458
|
const ownerTarget = ownerDirectTarget();
|
|
450
459
|
if (!ownerTarget) {
|
|
451
|
-
|
|
460
|
+
const reason = account.ownerUserId?.trim()
|
|
461
|
+
? "missing_owner_conversation"
|
|
462
|
+
: "missing_owner_user_id";
|
|
463
|
+
log?.error?.(`[${account.accountId}] clawchat-plugin-openclaw group owner attention suppressed reason=${reason} group=${target.chatId}`);
|
|
452
464
|
return;
|
|
453
465
|
}
|
|
454
466
|
await sendOpenclawClawlingText({
|
package/dist/src/runtime.js
CHANGED
|
@@ -8,7 +8,7 @@ import { buildActivationBootstrapText } from "./activation-greeting.js";
|
|
|
8
8
|
import { reportPluginVersionSafe, resolvePluginVersion } from "./plugin-report.js";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import { ensureLivewareCli, resolveLivewarePath } from "./liveware-cli.js";
|
|
11
|
-
import { LivewareSampleSupervisor } from "./liveware-sample.js";
|
|
11
|
+
import { LivewareSampleSupervisor, } from "./liveware-sample.js";
|
|
12
12
|
import { ClawlingApiError } from "./api-types.js";
|
|
13
13
|
import { RefreshManager } from "./refresh-manager.js";
|
|
14
14
|
import { runOpenclawClawlingLogin, } from "./login.runtime.js";
|
|
@@ -32,11 +32,20 @@ import { createGroupMessageCoalescer } from "./group-message-coalescer.js";
|
|
|
32
32
|
import { openclawLlmContextDebug } from "./llm-context-debug.js";
|
|
33
33
|
import { GroupSettingsCache } from "./group-settings.js";
|
|
34
34
|
import { PermissionCache } from "./permissions.js";
|
|
35
|
-
import { buildAwarenessNoteEnvelope } from "./awareness-note-turn.js";
|
|
35
|
+
import { buildAwarenessNoteEnvelope, buildMomentCommentNoteEnvelope } from "./awareness-note-turn.js";
|
|
36
36
|
import { handlePermissionResult } from "./permission-result.js";
|
|
37
37
|
const { setRuntime: setOpenclawClawlingRuntime, getRuntime: getOpenclawClawlingRuntime } = createPluginRuntimeStore("clawchat-plugin-openclaw runtime not initialized");
|
|
38
38
|
export { setOpenclawClawlingRuntime, getOpenclawClawlingRuntime };
|
|
39
39
|
const activeClients = new Map();
|
|
40
|
+
// Live liveware-sample supervisors, keyed by the gateway's abortSignal and then by
|
|
41
|
+
// `platform:accountId`. startOpenclawClawlingGateway re-enters ITSELF (token-refresh
|
|
42
|
+
// reconnect, transport backoff, post-auth-failed re-activation) carrying the same
|
|
43
|
+
// `...params` — so the same abortSignal — and a per-call `new LivewareSampleSupervisor`
|
|
44
|
+
// left the previous one running: both then raced the bootstrap "account has zero apps"
|
|
45
|
+
// gate and each minted its own liveware app, leaving a duplicate app tile that no
|
|
46
|
+
// SQLite row tracks (the single-row PK means the loser is orphaned forever). Keyed by
|
|
47
|
+
// signal so an entry cannot outlive the channel it belongs to.
|
|
48
|
+
const livewareSampleSupervisors = new WeakMap();
|
|
40
49
|
const CLAWCHAT_PLUGIN_SLASH_COMMANDS = new Set(["clawchat-activate", "clawchat-output"]);
|
|
41
50
|
const ACTIVATION_CREDENTIAL_POLL_INTERVAL_MS = 2_000;
|
|
42
51
|
// Activation-bootstrap retry on the live connection. The bootstrap greeting is
|
|
@@ -791,7 +800,7 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
791
800
|
let livewareSampleNotify = async () => false;
|
|
792
801
|
if (store) {
|
|
793
802
|
const stateDirForSample = runtime.state?.resolveStateDir?.();
|
|
794
|
-
const
|
|
803
|
+
const livewareSampleDeps = {
|
|
795
804
|
platform: "openclaw",
|
|
796
805
|
accountId,
|
|
797
806
|
enabled: account.livewareSample,
|
|
@@ -819,9 +828,29 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
819
828
|
debug: (m) => log?.debug?.(m),
|
|
820
829
|
error: (m) => log?.error?.(m),
|
|
821
830
|
},
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
|
|
831
|
+
};
|
|
832
|
+
const supervisorsForSignal = livewareSampleSupervisors.get(abortSignal) ?? new Map();
|
|
833
|
+
livewareSampleSupervisors.set(abortSignal, supervisorsForSignal);
|
|
834
|
+
const supervisorKey = `openclaw:${accountId}`;
|
|
835
|
+
const runningSupervisor = supervisorsForSignal.get(supervisorKey);
|
|
836
|
+
if (runningSupervisor) {
|
|
837
|
+
// A gateway re-entry (refresh/backoff reconnect), not a fresh channel start:
|
|
838
|
+
// hand the live supervisor this closure's token + REST client + notify path
|
|
839
|
+
// instead of constructing a second one that would register a duplicate app.
|
|
840
|
+
log?.debug?.(`[${accountId}] clawchat-plugin-openclaw liveware-sample supervisor already live; re-pointing it at the re-entered gateway`);
|
|
841
|
+
runningSupervisor.adoptDeps(livewareSampleDeps);
|
|
842
|
+
}
|
|
843
|
+
else {
|
|
844
|
+
const livewareSampleSupervisor = new LivewareSampleSupervisor(livewareSampleDeps);
|
|
845
|
+
supervisorsForSignal.set(supervisorKey, livewareSampleSupervisor);
|
|
846
|
+
abortSignal.addEventListener("abort", () => {
|
|
847
|
+
livewareSampleSupervisor.stop();
|
|
848
|
+
if (supervisorsForSignal.get(supervisorKey) === livewareSampleSupervisor) {
|
|
849
|
+
supervisorsForSignal.delete(supervisorKey);
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
void livewareSampleSupervisor.start();
|
|
853
|
+
}
|
|
825
854
|
}
|
|
826
855
|
const resolveMutateConfigFile = () => {
|
|
827
856
|
if (params.mutateConfigFile)
|
|
@@ -1840,6 +1869,30 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
1840
1869
|
],
|
|
1841
1870
|
}));
|
|
1842
1871
|
}
|
|
1872
|
+
else if (env.event === "message.error") {
|
|
1873
|
+
// Never fold this into `inbound_ignored`: `code` + `chat_id` are the
|
|
1874
|
+
// only in-plugin evidence that an uplink was refused, and the
|
|
1875
|
+
// 2026-07-19 incident needed a server-log dig purely because this line
|
|
1876
|
+
// did not exist. `action=message_error` marks it as semantically seen.
|
|
1877
|
+
const current = wsLogContext();
|
|
1878
|
+
const errorPayload = env.payload && typeof env.payload === "object"
|
|
1879
|
+
? env.payload
|
|
1880
|
+
: undefined;
|
|
1881
|
+
log?.info?.(formatWsLog({
|
|
1882
|
+
event: "inbound_control",
|
|
1883
|
+
accountId,
|
|
1884
|
+
attempt: current.attempt,
|
|
1885
|
+
reconnectCount: current.reconnectCount,
|
|
1886
|
+
state: "ready",
|
|
1887
|
+
action: "message_error",
|
|
1888
|
+
fields: [
|
|
1889
|
+
["event_name", env.event],
|
|
1890
|
+
["trace_id", env.trace_id],
|
|
1891
|
+
["chat_id", env.chat_id],
|
|
1892
|
+
["code", typeof errorPayload?.code === "string" ? errorPayload.code : "-"],
|
|
1893
|
+
],
|
|
1894
|
+
}));
|
|
1895
|
+
}
|
|
1843
1896
|
else if (env.event !== "ping" && env.event !== "pong") {
|
|
1844
1897
|
const current = wsLogContext();
|
|
1845
1898
|
log?.info?.(formatWsLog({
|
|
@@ -1909,6 +1962,12 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
1909
1962
|
?.conversationId?.trim();
|
|
1910
1963
|
return conversationId || null;
|
|
1911
1964
|
};
|
|
1965
|
+
// R3: the owner's direct conversation is never gated by the dead-chat send
|
|
1966
|
+
// guard. If it were, the very message explaining why the agent went dark
|
|
1967
|
+
// (skill-update notices, auth/logout notices, group owner-attention
|
|
1968
|
+
// forwards) would be unreachable. Registered lazily — the activation
|
|
1969
|
+
// conversation may be recorded after the client is constructed.
|
|
1970
|
+
client.setOwnerDirectChatResolver(resolveOwnerDirectConversationId);
|
|
1912
1971
|
const sendOwnerSkillMessage = async (text) => {
|
|
1913
1972
|
const conversationId = resolveOwnerDirectConversationId();
|
|
1914
1973
|
if (!conversationId) {
|
|
@@ -1996,7 +2055,11 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
1996
2055
|
// The conversation is gone. Evict it so no further uplink is emitted
|
|
1997
2056
|
// for it. Falls through to the awareness branch so the owner is told.
|
|
1998
2057
|
const entityId = typeof payload?.entity_id === "string" ? payload.entity_id : "";
|
|
1999
|
-
|
|
2058
|
+
// "signal" tier: this gates typing only. The protocol forbids gating
|
|
2059
|
+
// message.send on a dissolved signal — only a server rejection
|
|
2060
|
+
// (message.error, see src/outbound.ts) may do that, and unlike this
|
|
2061
|
+
// one that rejection expires.
|
|
2062
|
+
const evicted = client.markChatDead(entityId, "signal");
|
|
2000
2063
|
log?.info?.(evicted
|
|
2001
2064
|
? `[${accountId}] clawchat-plugin-openclaw conversation.dissolved signal received; evicting ${entityId}`
|
|
2002
2065
|
: `[${accountId}] clawchat-plugin-openclaw conversation.dissolved signal received with missing/invalid entity_id; no eviction performed`);
|
|
@@ -2018,6 +2081,32 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
2018
2081
|
log?.error?.(`[${accountId}] clawchat-plugin-openclaw skill-update check failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2019
2082
|
});
|
|
2020
2083
|
}
|
|
2084
|
+
else if (type === "moment.comment.created" || type === "moment.comment.replied") {
|
|
2085
|
+
// Content-free trigger: the note carries only the scenario + moment id
|
|
2086
|
+
// and points the agent at the get_moment tool — never the comment
|
|
2087
|
+
// text. Not gated behind account.awarenessNote and not debounced:
|
|
2088
|
+
// each moment-comment signal yields its own note (msghub already
|
|
2089
|
+
// coalesces multiple comments on one moment by entity_id).
|
|
2090
|
+
const momentId = typeof payload?.entity_id === "string" ? payload.entity_id : "";
|
|
2091
|
+
// No activation conversation ⇒ no routable chat_id. Skip the note
|
|
2092
|
+
// rather than address it to the owner's usr_… id, which
|
|
2093
|
+
// member-backend rejects as "invalid conversation id" (the agent's
|
|
2094
|
+
// in-turn replies are then dropped with no negative ack). Resolve
|
|
2095
|
+
// this FIRST, mirroring the friend.*/conversation.* awareness
|
|
2096
|
+
// branch's guard below.
|
|
2097
|
+
const ownerConversationId = resolveOwnerDirectConversationId();
|
|
2098
|
+
if (!ownerConversationId) {
|
|
2099
|
+
log?.info?.(`[${accountId}] clawchat-plugin-openclaw ${type} signal received but no owner conversation resolved; skipping moment awareness note (moment ${momentId})`);
|
|
2100
|
+
return;
|
|
2101
|
+
}
|
|
2102
|
+
log?.info?.(`[${accountId}] clawchat-plugin-openclaw ${type} signal received; synthesizing moment awareness note (moment ${momentId})`);
|
|
2103
|
+
void handleInboundEnvelope(buildMomentCommentNoteEnvelope({
|
|
2104
|
+
account,
|
|
2105
|
+
ownerConversationId,
|
|
2106
|
+
momentId,
|
|
2107
|
+
replied: type === "moment.comment.replied",
|
|
2108
|
+
}));
|
|
2109
|
+
}
|
|
2021
2110
|
else if (type === "friend.added" ||
|
|
2022
2111
|
type === "friend.removed" ||
|
|
2023
2112
|
type === "friend.profile_updated" ||
|
|
@@ -2028,10 +2117,16 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
2028
2117
|
pendingAwarenessNote = true;
|
|
2029
2118
|
queueMicrotask(() => {
|
|
2030
2119
|
pendingAwarenessNote = false;
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2120
|
+
// No activation conversation ⇒ no routable chat_id. Skip the note
|
|
2121
|
+
// rather than address it to the owner's usr_… id, which
|
|
2122
|
+
// member-backend rejects as "invalid conversation id" (the agent's
|
|
2123
|
+
// in-turn replies are then dropped with no negative ack).
|
|
2124
|
+
const ownerConversationId = resolveOwnerDirectConversationId();
|
|
2125
|
+
if (!ownerConversationId) {
|
|
2126
|
+
log?.info?.(`[${accountId}] clawchat-plugin-openclaw awareness note skipped: no activation conversation recorded`);
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
void handleInboundEnvelope(buildAwarenessNoteEnvelope({ account, ownerConversationId }));
|
|
2035
2130
|
});
|
|
2036
2131
|
}
|
|
2037
2132
|
}
|
|
@@ -2405,6 +2500,10 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
2405
2500
|
updateMessageByIdentity: (input) => store.updateMessageByIdentity?.(input),
|
|
2406
2501
|
}
|
|
2407
2502
|
: null,
|
|
2503
|
+
// Group owner-attention escalations address the owner's activation
|
|
2504
|
+
// conversation, not `ownerUserId` (a usr_… chat_id is rejected by
|
|
2505
|
+
// member-backend). Passed as a thunk so it is resolved at send time.
|
|
2506
|
+
resolveOwnerConversationId: resolveOwnerDirectConversationId,
|
|
2408
2507
|
log,
|
|
2409
2508
|
});
|
|
2410
2509
|
const agentsConfigured = Object.keys(cfg.agents ?? {});
|
|
@@ -2658,7 +2757,21 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
2658
2757
|
// A permission_result receipt closes the loop on an earlier request.
|
|
2659
2758
|
// Dedup by request_id; on a fresh receipt feed the synthetic context
|
|
2660
2759
|
// note back to the agent so it can react to the outcome.
|
|
2661
|
-
|
|
2760
|
+
//
|
|
2761
|
+
// The note is addressed to the owner's ACTIVATION CONVERSATION id
|
|
2762
|
+
// (cnv_…), never `account.ownerUserId`: the agent's in-turn reply
|
|
2763
|
+
// inherits this chat_id, and member-backend rejects a usr_… with
|
|
2764
|
+
// "invalid conversation id" — the reply is then dropped server-side
|
|
2765
|
+
// with no negative ack, so the outcome silently never reaches the
|
|
2766
|
+
// owner. With no conversation on record the receipt is skipped
|
|
2767
|
+
// outright (see handlePermissionResult — the dedup slot is preserved
|
|
2768
|
+
// so a replay after activation still renders).
|
|
2769
|
+
const ownerConversationId = resolveOwnerDirectConversationId();
|
|
2770
|
+
if (!ownerConversationId) {
|
|
2771
|
+
log?.info?.(`[${accountId}] clawchat-plugin-openclaw permission_result receipt op=${metadata.operation} outcome=${metadata.outcome} request_id=${metadata.request_id} skipped: no activation conversation recorded`);
|
|
2772
|
+
return;
|
|
2773
|
+
}
|
|
2774
|
+
const note = handlePermissionResult(metadata, account, ownerConversationId);
|
|
2662
2775
|
if (note) {
|
|
2663
2776
|
log?.info?.(`[${accountId}] clawchat-plugin-openclaw permission_result receipt op=${metadata.operation} outcome=${metadata.outcome} request_id=${metadata.request_id}; dispatching context note`);
|
|
2664
2777
|
void handleInboundEnvelope(note);
|
package/dist/src/tools-schema.js
CHANGED
|
@@ -198,6 +198,9 @@ export const ClawchatDeleteMomentSchema = Type.Object({
|
|
|
198
198
|
description: "Concrete ClawChat moment id to delete",
|
|
199
199
|
}),
|
|
200
200
|
});
|
|
201
|
+
export const ClawchatGetMomentSchema = Type.Object({
|
|
202
|
+
momentId: Type.Integer({ minimum: 1, description: "Concrete ClawChat moment id to fetch (with its visible comments)" }),
|
|
203
|
+
});
|
|
201
204
|
export const ClawchatToggleMomentReactionSchema = Type.Object({
|
|
202
205
|
momentId: Type.Integer({
|
|
203
206
|
minimum: 1,
|
package/dist/src/tools.js
CHANGED
|
@@ -12,7 +12,7 @@ import { getOpenclawClawlingClient, } from "./runtime.js";
|
|
|
12
12
|
import { markTerminalClawChatSend, getCurrentTerminalSendScope } from "./terminal-send.js";
|
|
13
13
|
import { editClawChatMemoryBody, readClawChatMemoryFile, resolveClawChatMemoryPath, searchClawChatMemory, writeClawChatMemoryBody, } from "./clawchat-memory.js";
|
|
14
14
|
import { pullGroupMetadata, pullOwnerMetadata, pullUserMetadata, pushMetadata, updateMetadata, } from "./clawchat-metadata.js";
|
|
15
|
-
import { ClawchatGetAccountProfileSchema, ClawchatGetConversationSchema, ClawchatGetUserProfileSchema, ClawchatLeaveGroupSchema, ClawchatAcceptFriendRequestSchema, ClawchatMemoryEditSchema, ClawchatMemoryReadSchema, ClawchatMemorySearchSchema, ClawchatMemoryWriteSchema, ClawchatMetadataSyncSchema, ClawchatMetadataUpdateSchema, ClawchatCreateMomentCommentSchema, ClawchatCreateMomentSchema, ClawchatDeleteMomentCommentSchema, ClawchatDeleteMomentSchema, ClawchatListAccountFriendsSchema, ClawchatListFriendRequestsSchema, ClawchatListMomentsSchema, ClawchatMentionMessageSchema, ClawchatReactMessageSchema, ClawchatReplyMomentCommentSchema, ClawchatRejectFriendRequestSchema, ClawchatRemoveFriendSchema, ClawchatSearchUsersSchema, ClawchatSendFriendRequestSchema, ClawchatToggleMomentReactionSchema, ClawchatUpdateAccountProfileSchema, ClawchatUploadAvatarImageSchema, ClawchatRegisterAppSchema, ClawchatListAppsSchema, ClawchatUnregisterAppSchema, ClawchatLivewareLoginSchema, } from "./tools-schema.js";
|
|
15
|
+
import { ClawchatGetAccountProfileSchema, ClawchatGetConversationSchema, ClawchatGetMomentSchema, ClawchatGetUserProfileSchema, ClawchatLeaveGroupSchema, ClawchatAcceptFriendRequestSchema, ClawchatMemoryEditSchema, ClawchatMemoryReadSchema, ClawchatMemorySearchSchema, ClawchatMemoryWriteSchema, ClawchatMetadataSyncSchema, ClawchatMetadataUpdateSchema, ClawchatCreateMomentCommentSchema, ClawchatCreateMomentSchema, ClawchatDeleteMomentCommentSchema, ClawchatDeleteMomentSchema, ClawchatListAccountFriendsSchema, ClawchatListFriendRequestsSchema, ClawchatListMomentsSchema, ClawchatMentionMessageSchema, ClawchatReactMessageSchema, ClawchatReplyMomentCommentSchema, ClawchatRejectFriendRequestSchema, ClawchatRemoveFriendSchema, ClawchatSearchUsersSchema, ClawchatSendFriendRequestSchema, ClawchatToggleMomentReactionSchema, ClawchatUpdateAccountProfileSchema, ClawchatUploadAvatarImageSchema, ClawchatRegisterAppSchema, ClawchatListAppsSchema, ClawchatUnregisterAppSchema, ClawchatLivewareLoginSchema, } from "./tools-schema.js";
|
|
16
16
|
const MAX_UPLOAD_BYTES = 20 * 1024 * 1024;
|
|
17
17
|
// Owner-approval gate business codes (must match the ClawChat backend's owner-approval codes).
|
|
18
18
|
const CODE_PENDING_APPROVAL = 21001;
|
|
@@ -935,6 +935,18 @@ export function registerOpenclawClawlingTools(api, options = {}) {
|
|
|
935
935
|
});
|
|
936
936
|
},
|
|
937
937
|
}, { name: "clawchat_list_moments" });
|
|
938
|
+
api.registerTool({
|
|
939
|
+
name: "clawchat_get_moment",
|
|
940
|
+
label: "Get ClawChat Moment",
|
|
941
|
+
description: toolDescription("Fetch a single ClawChat moment by id together with the comments visible to this agent. TRIGGER - after a moment.comment.created / moment.comment.replied awareness note, call this with the moment id to read the new comment before deciding whether to reply."),
|
|
942
|
+
parameters: ClawchatGetMomentSchema,
|
|
943
|
+
async execute(_callId, params) {
|
|
944
|
+
return await recordClawchatToolCall("clawchat_get_moment", params, async () => {
|
|
945
|
+
const p = (params ?? {});
|
|
946
|
+
return await withClient((c) => c.getMoment(p.momentId));
|
|
947
|
+
});
|
|
948
|
+
},
|
|
949
|
+
}, { name: "clawchat_get_moment" });
|
|
938
950
|
api.registerTool({
|
|
939
951
|
name: "clawchat_create_moment",
|
|
940
952
|
label: "Create ClawChat Moment",
|
|
@@ -1196,5 +1208,5 @@ export function registerOpenclawClawlingTools(api, options = {}) {
|
|
|
1196
1208
|
});
|
|
1197
1209
|
},
|
|
1198
1210
|
}, { name: "clawchat_liveware_login" });
|
|
1199
|
-
api.logger.debug?.("clawchat-plugin-openclaw: registered
|
|
1211
|
+
api.logger.debug?.("clawchat-plugin-openclaw: registered 31 clawchat_* tools (get_account_profile, get_user_profile, list_account_friends, send_friend_request, list_friend_requests, accept_friend_request, reject_friend_request, remove_friend, search_users, get_conversation, mention_message, list_moments, get_moment, create_moment, delete_moment, toggle_moment_reaction, create_moment_comment, reply_moment_comment, delete_moment_comment, update_account_profile, upload_avatar_image, memory_search, memory_read, memory_write, memory_edit, metadata_sync, metadata_update, register_app, list_apps, unregister_app, liveware_login)");
|
|
1200
1212
|
}
|
package/dist/src/ws-alignment.js
CHANGED
|
@@ -40,9 +40,26 @@ export function createAlignedWsQueue(options) {
|
|
|
40
40
|
queue.push(item);
|
|
41
41
|
logFrame("send_queued", "queue", item, [["queue_size", queue.length]]);
|
|
42
42
|
},
|
|
43
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Drain the queue. `isBlocked` is consulted PER ITEM at write time, not at
|
|
45
|
+
* enqueue time: a conversation can be rejected by the server *after* a
|
|
46
|
+
* frame was queued, and this replay path never re-enters the send function
|
|
47
|
+
* that guards the live path. Without this hook the outbound gate is
|
|
48
|
+
* bypassable by any reconnect (and by any batch flush that happens to carry
|
|
49
|
+
* a live chat's frame).
|
|
50
|
+
*/
|
|
51
|
+
flush(write, options = {}) {
|
|
44
52
|
while (queue.length > 0) {
|
|
45
53
|
const item = queue[0];
|
|
54
|
+
if (options.isBlocked?.(item)) {
|
|
55
|
+
queue.shift();
|
|
56
|
+
logFrame("send_blocked", "drop", item, [
|
|
57
|
+
["reason", "chat_rejected_by_server"],
|
|
58
|
+
["remaining", queue.length],
|
|
59
|
+
]);
|
|
60
|
+
item.onBlock?.();
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
46
63
|
try {
|
|
47
64
|
write(item.wire);
|
|
48
65
|
queue.shift();
|