@newbase-clawchat/openclaw-clawchat 2026.5.12-2 → 2026.5.12-21

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.
Files changed (99) hide show
  1. package/README.md +39 -17
  2. package/dist/index.js +3 -1
  3. package/dist/src/api-client.js +71 -12
  4. package/dist/src/api-types.test-d.js +10 -0
  5. package/dist/src/channel.js +5 -5
  6. package/dist/src/channel.setup.js +4 -17
  7. package/dist/src/clawchat-memory.js +290 -0
  8. package/dist/src/clawchat-metadata.js +235 -0
  9. package/dist/src/client.js +31 -93
  10. package/dist/src/commands.js +3 -3
  11. package/dist/src/config.js +58 -3
  12. package/dist/src/group-message-coalescer.js +107 -0
  13. package/dist/src/inbound.js +24 -28
  14. package/dist/src/login.runtime.js +82 -19
  15. package/dist/src/media-runtime.js +2 -3
  16. package/dist/src/message-mapper.js +1 -1
  17. package/dist/src/mock-transport.js +31 -0
  18. package/dist/src/outbound.js +281 -56
  19. package/dist/src/plugin-prompts.js +76 -0
  20. package/dist/src/profile-prompt.js +150 -0
  21. package/dist/src/profile-sync.js +169 -0
  22. package/dist/src/prompt-injection.js +25 -0
  23. package/dist/src/protocol-types.js +63 -0
  24. package/dist/src/protocol-types.typecheck.js +1 -0
  25. package/dist/src/protocol.js +2 -2
  26. package/dist/src/reply-dispatcher.js +143 -40
  27. package/dist/src/runtime.js +813 -109
  28. package/dist/src/storage.js +636 -0
  29. package/dist/src/tools-schema.js +70 -10
  30. package/dist/src/tools.js +600 -112
  31. package/dist/src/ws-alignment.js +8 -0
  32. package/dist/src/ws-client.js +588 -0
  33. package/index.ts +6 -1
  34. package/openclaw.plugin.json +44 -4
  35. package/package.json +4 -3
  36. package/prompts/platform.md +7 -0
  37. package/skills/clawchat/SKILL.md +90 -0
  38. package/src/api-client.test.ts +360 -15
  39. package/src/api-client.ts +127 -25
  40. package/src/api-types.test-d.ts +12 -0
  41. package/src/api-types.ts +71 -4
  42. package/src/buffered-stream.test.ts +1 -1
  43. package/src/buffered-stream.ts +1 -1
  44. package/src/channel.outbound.test.ts +270 -60
  45. package/src/channel.setup.ts +9 -18
  46. package/src/channel.test.ts +33 -25
  47. package/src/channel.ts +5 -7
  48. package/src/clawchat-memory.test.ts +372 -0
  49. package/src/clawchat-memory.ts +363 -0
  50. package/src/clawchat-metadata.test.ts +350 -0
  51. package/src/clawchat-metadata.ts +352 -0
  52. package/src/client.test.ts +57 -48
  53. package/src/client.ts +37 -129
  54. package/src/commands.test.ts +2 -2
  55. package/src/commands.ts +3 -3
  56. package/src/config.test.ts +169 -4
  57. package/src/config.ts +86 -6
  58. package/src/group-message-coalescer.test.ts +223 -0
  59. package/src/group-message-coalescer.ts +154 -0
  60. package/src/inbound.test.ts +106 -19
  61. package/src/inbound.ts +31 -35
  62. package/src/login.runtime.test.ts +294 -11
  63. package/src/login.runtime.ts +90 -21
  64. package/src/manifest.test.ts +86 -14
  65. package/src/media-runtime.test.ts +31 -2
  66. package/src/media-runtime.ts +7 -10
  67. package/src/message-mapper.test.ts +2 -2
  68. package/src/message-mapper.ts +2 -2
  69. package/src/mock-transport.test.ts +35 -0
  70. package/src/mock-transport.ts +38 -0
  71. package/src/outbound.test.ts +811 -95
  72. package/src/outbound.ts +332 -65
  73. package/src/plugin-entry.test.ts +3 -1
  74. package/src/plugin-prompts.test.ts +78 -0
  75. package/src/plugin-prompts.ts +92 -0
  76. package/src/profile-prompt.test.ts +435 -0
  77. package/src/profile-prompt.ts +208 -0
  78. package/src/profile-sync.test.ts +611 -0
  79. package/src/profile-sync.ts +268 -0
  80. package/src/prompt-injection.test.ts +39 -0
  81. package/src/prompt-injection.ts +45 -0
  82. package/src/protocol-types.test.ts +69 -0
  83. package/src/protocol-types.ts +296 -0
  84. package/src/protocol-types.typecheck.ts +89 -0
  85. package/src/protocol.ts +2 -2
  86. package/src/reply-dispatcher.test.ts +720 -135
  87. package/src/reply-dispatcher.ts +174 -42
  88. package/src/runtime.test.ts +3884 -337
  89. package/src/runtime.ts +956 -128
  90. package/src/storage.test.ts +692 -0
  91. package/src/storage.ts +989 -0
  92. package/src/streaming.test.ts +1 -1
  93. package/src/streaming.ts +1 -1
  94. package/src/tools-schema.ts +115 -13
  95. package/src/tools.test.ts +501 -10
  96. package/src/tools.ts +739 -133
  97. package/src/ws-alignment.ts +9 -0
  98. package/src/ws-client.test.ts +1218 -0
  99. package/src/ws-client.ts +662 -0
@@ -0,0 +1,150 @@
1
+ import { readClawChatMemoryFile } from "./clawchat-memory.js";
2
+ export const CLAWCHAT_SILENT_RESPONSE = "<clawchat:silent/>";
3
+ export const CLAWCHAT_EMPTY_RESPONSE = '""';
4
+ const GROUP_BATCH_REPLY_GUIDANCE = 'Hard no-reply rules: if mentioned_user_ids is not "-" and mentions_current_agent is false, return exactly "" and nothing else. ' +
5
+ 'If the input is unrelated to ClawChat Agent Behavior, return exactly "" and nothing else. These rules override sender_is_owner, ' +
6
+ "group usefulness, and general helpfulness. Reply only if mentions_current_agent is true, or there is no mention and the text " +
7
+ 'explicitly asks this agent to participate. Otherwise return exactly "" and nothing else.';
8
+ const GROUP_BATCH_MENTION_REPLY_GUIDANCE = "You were directly addressed in this group batch. Reply by default, including when the message contains only a mention. " +
9
+ "Stay silent only if the group profile/regulation explicitly forbids replying.";
10
+ export function isClawChatNoopResponseText(value) {
11
+ const text = value.trim();
12
+ return text === CLAWCHAT_EMPTY_RESPONSE;
13
+ }
14
+ export function resolveSenderRelation(params) {
15
+ if (params.senderId === params.accountUserId)
16
+ return "self_agent";
17
+ if (params.senderId === params.accountOwnerUserId)
18
+ return "owner";
19
+ if (params.senderProfileType === "agent")
20
+ return "peer_agent";
21
+ return "peer_user";
22
+ }
23
+ function formatValue(value) {
24
+ if (value == null)
25
+ return "null";
26
+ if (typeof value !== "string")
27
+ return String(value);
28
+ return value.replace(/[\\\r\n\u2028\u2029\u0000-\u001f\u007f-\u009f]/g, (char) => {
29
+ if (char === "\\")
30
+ return "\\\\";
31
+ if (char === "\r")
32
+ return "\\r";
33
+ if (char === "\n")
34
+ return "\\n";
35
+ return `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`;
36
+ });
37
+ }
38
+ function renderFields(fields) {
39
+ return fields.map(([name, value]) => `${name}: ${formatValue(value)}`).join("\n");
40
+ }
41
+ function renderMetadataFields(metadata) {
42
+ return Object.entries(metadata).map(([name, value]) => `${name}: ${formatValue(value)}`).join("\n");
43
+ }
44
+ function renderProfileSection(title, fields) {
45
+ return `## ${title}\n${renderFields(fields)}`;
46
+ }
47
+ function renderMetadataSection(title, metadata) {
48
+ return `## ${title}\n${renderMetadataFields(metadata)}`;
49
+ }
50
+ function renderResponseProtocol(turn) {
51
+ const wasMentioned = turn.wasMentioned ?? false;
52
+ const replyGuidance = turn.chatType === "group"
53
+ ? wasMentioned
54
+ ? GROUP_BATCH_MENTION_REPLY_GUIDANCE
55
+ : GROUP_BATCH_REPLY_GUIDANCE
56
+ : "Direct messages are normally addressed to you. Reply unless the agent behavior says this message should not be answered.";
57
+ return renderProfileSection("ClawChat Response Protocol", [
58
+ [
59
+ "response_decision",
60
+ turn.chatType === "group"
61
+ ? "Decide whether this group input needs a reply from you."
62
+ : "Decide whether this direct message needs a reply from you.",
63
+ ],
64
+ ["allowed_outputs", "normal_reply OR exact_empty_response"],
65
+ ["exact_empty_response", CLAWCHAT_EMPTY_RESPONSE],
66
+ ["reply_guidance", replyGuidance],
67
+ ["no_reply_protocol", 'If you choose not to reply, return exactly "" and nothing else.'],
68
+ ]);
69
+ }
70
+ export function renderClawChatProfilePrompt(params) {
71
+ const sections = [params.basePrompt.trim()];
72
+ if (params.ownerMetadata && Object.keys(params.ownerMetadata).length > 0) {
73
+ sections.push(renderMetadataSection("Current ClawChat Owner Metadata", params.ownerMetadata));
74
+ }
75
+ if (params.turn.chatType === "dm" &&
76
+ !params.turn.senderIsOwner &&
77
+ params.userMetadata &&
78
+ Object.keys(params.userMetadata).length > 0) {
79
+ sections.push(renderMetadataSection("Current ClawChat User Metadata", params.userMetadata));
80
+ }
81
+ if (params.turn.chatType === "group" &&
82
+ params.groupMetadata &&
83
+ Object.keys(params.groupMetadata).length > 0) {
84
+ sections.push(renderMetadataSection("Current ClawChat Group Metadata", params.groupMetadata));
85
+ }
86
+ if (params.turn.chatType === "group") {
87
+ const wasMentioned = params.turn.wasMentioned ?? false;
88
+ sections.push(renderProfileSection("Current ClawChat Message Metadata", [
89
+ ["chat_type", params.turn.chatType],
90
+ ["group_id", params.turn.groupId],
91
+ ]));
92
+ }
93
+ else {
94
+ sections.push(renderProfileSection("Current ClawChat Message Metadata", [
95
+ ["chat_type", params.turn.chatType],
96
+ ["sender_id", params.turn.senderId],
97
+ ["sender_name", params.turn.senderName],
98
+ ["sender_profile_type", params.turn.senderProfileType],
99
+ ["sender_is_owner", params.turn.senderIsOwner],
100
+ ]));
101
+ }
102
+ sections.push(renderResponseProtocol(params.turn));
103
+ return sections.filter(Boolean).join("\n\n");
104
+ }
105
+ function hasBrokenMetadataBlock(raw) {
106
+ const start = "<!-- clawchat:metadata:start -->";
107
+ const end = "<!-- clawchat:metadata:end -->";
108
+ return raw.startsWith(start) && !raw.includes(end);
109
+ }
110
+ async function loadMetadataFile(params) {
111
+ try {
112
+ const file = await readClawChatMemoryFile(params.memoryRoot, params.target);
113
+ if (!file.exists)
114
+ return null;
115
+ if (hasBrokenMetadataBlock(file.raw)) {
116
+ params.log?.error?.(`openclaw-clawchat: skipping broken ${params.label} metadata block`);
117
+ return null;
118
+ }
119
+ return Object.keys(file.metadata).length > 0 ? file.metadata : null;
120
+ }
121
+ catch (err) {
122
+ params.log?.error?.(`openclaw-clawchat: failed to read ${params.label} metadata block: ${err instanceof Error ? err.message : String(err)}`);
123
+ return null;
124
+ }
125
+ }
126
+ export async function loadClawChatPromptMetadata(params) {
127
+ const ownerMetadata = await loadMetadataFile({
128
+ memoryRoot: params.memoryRoot,
129
+ target: { targetType: "owner", targetId: "owner" },
130
+ label: "owner",
131
+ log: params.log,
132
+ });
133
+ const userMetadata = params.turn.chatType === "dm" && !params.turn.senderIsOwner
134
+ ? await loadMetadataFile({
135
+ memoryRoot: params.memoryRoot,
136
+ target: { targetType: "user", targetId: params.turn.senderId },
137
+ label: "user",
138
+ log: params.log,
139
+ })
140
+ : null;
141
+ const groupMetadata = params.turn.chatType === "group" && params.turn.groupId
142
+ ? await loadMetadataFile({
143
+ memoryRoot: params.memoryRoot,
144
+ target: { targetType: "group", targetId: params.turn.groupId },
145
+ label: "group",
146
+ log: params.log,
147
+ })
148
+ : null;
149
+ return { ownerMetadata, userMetadata, groupMetadata };
150
+ }
@@ -0,0 +1,169 @@
1
+ import { pullGroupMetadata, pullOwnerMetadata, pullUserMetadata, } from "./clawchat-metadata.js";
2
+ function parseApiTimestamp(value) {
3
+ if (typeof value !== "string")
4
+ return null;
5
+ const parsed = Date.parse(value);
6
+ return Number.isFinite(parsed) ? parsed : null;
7
+ }
8
+ function buildConversationDetailsCacheInput(params) {
9
+ const refreshedAt = Date.now();
10
+ const participants = Array.isArray(params.conversation.participants)
11
+ ? params.conversation.participants
12
+ : [];
13
+ return {
14
+ platform: params.platform,
15
+ accountId: params.accountId,
16
+ conversationId: params.conversation.id,
17
+ conversationType: params.conversation.type,
18
+ lastSeenAt: parseApiTimestamp(params.conversation.updated_at),
19
+ lastRefreshedAt: refreshedAt,
20
+ members: participants.map((participant) => ({
21
+ userId: participant.user_id,
22
+ role: participant.role,
23
+ lastSeenAt: parseApiTimestamp(participant.joined_at),
24
+ })),
25
+ membersComplete: true,
26
+ };
27
+ }
28
+ function requireMemoryRoot(memoryRoot, log, label) {
29
+ if (typeof memoryRoot === "string" && memoryRoot.trim())
30
+ return true;
31
+ log?.error?.(`openclaw-clawchat ${label} metadata refresh requires memoryRoot`);
32
+ return false;
33
+ }
34
+ export async function ensureUserProfileForSender(params) {
35
+ void params.accountUserId;
36
+ void params.accountOwnerUserId;
37
+ void params.lastSeenAt;
38
+ void params.sender.nickname;
39
+ if (!requireMemoryRoot(params.memoryRoot, params.log, "user"))
40
+ return;
41
+ try {
42
+ await pullUserMetadata({
43
+ memoryRoot: params.memoryRoot,
44
+ userId: params.sender.id,
45
+ api: params.api,
46
+ });
47
+ }
48
+ catch (err) {
49
+ params.log?.error?.(`openclaw-clawchat first-seen user metadata refresh failed: ${err instanceof Error ? err.message : String(err)}`);
50
+ }
51
+ }
52
+ export async function refreshGroupProfile(params) {
53
+ const conversationId = params.conversationId ?? params.chatId ?? "";
54
+ if (!conversationId || !params.api.getConversation)
55
+ return false;
56
+ try {
57
+ let conversation;
58
+ if (requireMemoryRoot(params.memoryRoot, params.log, "group")) {
59
+ const result = await pullGroupMetadata({
60
+ memoryRoot: params.memoryRoot,
61
+ groupId: conversationId,
62
+ api: params.api,
63
+ });
64
+ if (result.failures.length > 0) {
65
+ params.log?.error?.(`openclaw-clawchat group participant metadata refresh partially failed: ${result.failures.map((failure) => `${failure.targetId}: ${failure.error}`).join("; ")}`);
66
+ }
67
+ if (!result.conversation)
68
+ return false;
69
+ conversation = result.conversation;
70
+ }
71
+ else {
72
+ const data = await params.api.getConversation(conversationId);
73
+ conversation = data.conversation;
74
+ }
75
+ params.store.upsertConversationDetails?.({
76
+ ...buildConversationDetailsCacheInput({
77
+ platform: params.platform,
78
+ accountId: params.accountId,
79
+ conversation,
80
+ }),
81
+ });
82
+ return true;
83
+ }
84
+ catch (err) {
85
+ params.log?.error?.(`openclaw-clawchat group metadata refresh failed: ${err instanceof Error ? err.message : String(err)}`);
86
+ return false;
87
+ }
88
+ }
89
+ export async function ensureGroupProfileForChat(params) {
90
+ if (params.chat.type !== "group")
91
+ return false;
92
+ return await refreshGroupProfile({
93
+ platform: params.platform,
94
+ accountId: params.accountId,
95
+ conversationId: params.chat.id,
96
+ api: params.api,
97
+ store: params.store,
98
+ memoryRoot: params.memoryRoot,
99
+ log: params.log,
100
+ });
101
+ }
102
+ export async function refreshAgentBehaviorProfile(params) {
103
+ if (!requireMemoryRoot(params.memoryRoot, params.log, "owner"))
104
+ return;
105
+ try {
106
+ await pullOwnerMetadata({
107
+ memoryRoot: params.memoryRoot,
108
+ agentId: params.agentId ?? "",
109
+ accountUserId: params.accountUserId,
110
+ accountOwnerUserId: params.accountOwnerUserId,
111
+ api: params.api,
112
+ });
113
+ }
114
+ catch (err) {
115
+ params.log?.error?.(`openclaw-clawchat owner metadata refresh failed: ${err instanceof Error ? err.message : String(err)}`);
116
+ }
117
+ }
118
+ export async function syncFirstSeenClawChatProfiles(params) {
119
+ const { platform, accountId, accountUserId, accountOwnerUserId, chat, sender, api, store } = params;
120
+ const cachedConversation = store.getCachedConversation?.({
121
+ platform,
122
+ accountId,
123
+ conversationId: chat.id,
124
+ });
125
+ const isNewChat = !cachedConversation;
126
+ if (isNewChat) {
127
+ store.upsertConversationSummary?.({
128
+ platform,
129
+ accountId,
130
+ conversationId: chat.id,
131
+ conversationType: chat.type,
132
+ lastSeenAt: chat.lastSeenAt ?? null,
133
+ });
134
+ }
135
+ const refreshedGroupDetails = await ensureGroupProfileForChat({
136
+ platform,
137
+ accountId,
138
+ chat,
139
+ api,
140
+ store,
141
+ memoryRoot: params.memoryRoot,
142
+ log: params.log,
143
+ });
144
+ await ensureUserProfileForSender({
145
+ platform,
146
+ accountId,
147
+ accountUserId,
148
+ accountOwnerUserId,
149
+ sender,
150
+ lastSeenAt: chat.lastSeenAt ?? null,
151
+ api,
152
+ store,
153
+ memoryRoot: params.memoryRoot,
154
+ log: params.log,
155
+ });
156
+ if (isNewChat && chat.type === "group" && !refreshedGroupDetails && api.getConversation && store.upsertConversationDetails) {
157
+ try {
158
+ const data = await api.getConversation(chat.id);
159
+ store.upsertConversationDetails(buildConversationDetailsCacheInput({
160
+ platform,
161
+ accountId,
162
+ conversation: data.conversation,
163
+ }));
164
+ }
165
+ catch (err) {
166
+ params.log?.error?.(`openclaw-clawchat first-seen conversation detail refresh failed: ${err instanceof Error ? err.message : String(err)}`);
167
+ }
168
+ }
169
+ }
@@ -0,0 +1,25 @@
1
+ const promptInjectionsBySessionKey = new Map();
2
+ export function clearClawChatPromptInjections() {
3
+ promptInjectionsBySessionKey.clear();
4
+ }
5
+ export function clearClawChatPromptInjectionForSession(sessionKey) {
6
+ promptInjectionsBySessionKey.delete(sessionKey);
7
+ }
8
+ export function stageClawChatPromptInjection(params) {
9
+ promptInjectionsBySessionKey.set(params.sessionKey, params.prompt);
10
+ }
11
+ export function renderClawChatPromptInjectionForSession(sessionKey) {
12
+ return promptInjectionsBySessionKey.get(sessionKey);
13
+ }
14
+ export function registerClawChatPromptInjection(api) {
15
+ api.on("before_prompt_build", async (_event, ctx) => {
16
+ const sessionKey = ctx.sessionKey;
17
+ if (!sessionKey)
18
+ return undefined;
19
+ const prompt = promptInjectionsBySessionKey.get(sessionKey);
20
+ if (!prompt)
21
+ return undefined;
22
+ promptInjectionsBySessionKey.delete(sessionKey);
23
+ return { appendSystemContext: prompt };
24
+ }, { priority: 100 });
25
+ }
@@ -0,0 +1,63 @@
1
+ export const EVENT = {
2
+ CONNECT_CHALLENGE: "connect.challenge",
3
+ CONNECT: "connect",
4
+ HELLO_OK: "hello-ok",
5
+ HELLO_FAIL: "hello-fail",
6
+ MESSAGE_SEND: "message.send",
7
+ MESSAGE_ACK: "message.ack",
8
+ MESSAGE_ERROR: "message.error",
9
+ MESSAGE_REPLY: "message.reply",
10
+ MESSAGE_CREATED: "message.created",
11
+ MESSAGE_ADD: "message.add",
12
+ MESSAGE_DONE: "message.done",
13
+ MESSAGE_FAILED: "message.failed",
14
+ TYPING_UPDATE: "typing.update",
15
+ CHAT_METADATA_INVALIDATED: "chat.metadata.invalidated",
16
+ OFFLINE_BATCH: "offline.batch",
17
+ OFFLINE_ACK: "offline.ack",
18
+ OFFLINE_DONE: "offline.done",
19
+ PING: "ping",
20
+ PONG: "pong",
21
+ };
22
+ export class AuthError extends Error {
23
+ name = "AuthError";
24
+ }
25
+ export class TransportError extends Error {
26
+ name = "TransportError";
27
+ }
28
+ export class ProtocolError extends Error {
29
+ envelope;
30
+ name = "ProtocolError";
31
+ constructor(message, envelope) {
32
+ super(message);
33
+ this.envelope = envelope;
34
+ }
35
+ }
36
+ export class AckTimeoutError extends Error {
37
+ traceId;
38
+ timeoutMs;
39
+ name = "AckTimeoutError";
40
+ constructor(traceId, timeoutMs) {
41
+ super(`ack timeout after ${timeoutMs}ms for trace_id=${traceId}`);
42
+ this.traceId = traceId;
43
+ this.timeoutMs = timeoutMs;
44
+ }
45
+ }
46
+ export class MessageSendError extends Error {
47
+ traceId;
48
+ code;
49
+ chatId;
50
+ name = "MessageSendError";
51
+ constructor(traceId, code, message, chatId) {
52
+ super(`message.error ${code}: ${message} for trace_id=${traceId}`);
53
+ this.traceId = traceId;
54
+ this.code = code;
55
+ this.chatId = chatId;
56
+ }
57
+ }
58
+ export class StateError extends Error {
59
+ name = "StateError";
60
+ }
61
+ export function isBusinessDispatchEvent(event) {
62
+ return event === EVENT.MESSAGE_SEND || event === EVENT.MESSAGE_REPLY || event === EVENT.MESSAGE_DONE;
63
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Local narrow guards for inbound protocol envelopes.
3
3
  *
4
- * The SDK's `message` event hands us `Envelope<unknown>`. Before casting the
5
- * payload to `DownlinkMessageSendPayload` we run these cheap structural checks
4
+ * Raw client events hand us `Envelope<unknown>`. Before casting the
5
+ * payload to `MessagePayload` we run these cheap structural checks
6
6
  * so runtime errors surface as skipped messages, not crashes.
7
7
  */
8
8
  export function isInboundMessagePayload(payload) {