@clawling/clawchat-plugin-openclaw 2026.5.12-28
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/INSTALL.md +64 -0
- package/README.md +227 -0
- package/dist/index.js +20 -0
- package/dist/setup-entry.js +3 -0
- package/dist/src/api-client.js +263 -0
- package/dist/src/api-types.js +17 -0
- package/dist/src/api-types.test-d.js +10 -0
- package/dist/src/buffered-stream.js +177 -0
- package/dist/src/channel.js +66 -0
- package/dist/src/channel.setup.js +119 -0
- package/dist/src/clawchat-memory.js +403 -0
- package/dist/src/clawchat-metadata.js +310 -0
- package/dist/src/client.js +35 -0
- package/dist/src/commands.js +35 -0
- package/dist/src/config.js +274 -0
- package/dist/src/group-message-coalescer.js +119 -0
- package/dist/src/inbound.js +170 -0
- package/dist/src/llm-context-debug.js +86 -0
- package/dist/src/login.runtime.js +204 -0
- package/dist/src/media-runtime.js +85 -0
- package/dist/src/message-mapper.js +146 -0
- package/dist/src/mock-transport.js +31 -0
- package/dist/src/outbound.js +628 -0
- package/dist/src/plugin-prompts.js +89 -0
- package/dist/src/profile-prompt.js +269 -0
- package/dist/src/profile-sync.js +110 -0
- package/dist/src/prompt-injection.js +25 -0
- package/dist/src/protocol-types.js +63 -0
- package/dist/src/protocol-types.typecheck.js +1 -0
- package/dist/src/protocol.js +33 -0
- package/dist/src/reply-dispatcher.js +422 -0
- package/dist/src/runtime.js +1254 -0
- package/dist/src/storage.js +525 -0
- package/dist/src/streaming.js +65 -0
- package/dist/src/terminal-send.js +36 -0
- package/dist/src/tools-schema.js +208 -0
- package/dist/src/tools.js +920 -0
- package/dist/src/ws-alignment.js +178 -0
- package/dist/src/ws-client.js +588 -0
- package/dist/src/ws-log.js +19 -0
- package/index.ts +24 -0
- package/openclaw.plugin.json +169 -0
- package/package.json +80 -0
- package/prompts/default-group-bio.md +19 -0
- package/prompts/default-owner-behavior.md +27 -0
- package/prompts/platform.md +13 -0
- package/setup-entry.ts +4 -0
- package/skills/clawchat/SKILL.md +91 -0
- package/src/api-client.test.ts +827 -0
- package/src/api-client.ts +414 -0
- package/src/api-types.ts +146 -0
- package/src/channel.outbound.test.ts +433 -0
- package/src/channel.setup.ts +145 -0
- package/src/channel.test.ts +262 -0
- package/src/channel.ts +81 -0
- package/src/clawchat-memory.test.ts +480 -0
- package/src/clawchat-memory.ts +533 -0
- package/src/clawchat-metadata.test.ts +477 -0
- package/src/clawchat-metadata.ts +429 -0
- package/src/client.test.ts +169 -0
- package/src/client.ts +56 -0
- package/src/commands.test.ts +39 -0
- package/src/commands.ts +41 -0
- package/src/config.test.ts +344 -0
- package/src/config.ts +404 -0
- package/src/group-message-coalescer.test.ts +237 -0
- package/src/group-message-coalescer.ts +171 -0
- package/src/inbound.test.ts +508 -0
- package/src/inbound.ts +278 -0
- package/src/llm-context-debug.test.ts +55 -0
- package/src/llm-context-debug.ts +139 -0
- package/src/login.runtime.test.ts +737 -0
- package/src/login.runtime.ts +277 -0
- package/src/manifest.test.ts +352 -0
- package/src/media-runtime.test.ts +207 -0
- package/src/media-runtime.ts +152 -0
- package/src/message-mapper.test.ts +201 -0
- package/src/message-mapper.ts +174 -0
- package/src/mock-transport.test.ts +35 -0
- package/src/mock-transport.ts +38 -0
- package/src/outbound.test.ts +1269 -0
- package/src/outbound.ts +803 -0
- package/src/plugin-entry.test.ts +38 -0
- package/src/plugin-prompts.test.ts +94 -0
- package/src/plugin-prompts.ts +107 -0
- package/src/profile-prompt.test.ts +274 -0
- package/src/profile-prompt.ts +351 -0
- package/src/profile-sync.test.ts +539 -0
- package/src/profile-sync.ts +191 -0
- package/src/prompt-injection.test.ts +39 -0
- package/src/prompt-injection.ts +45 -0
- package/src/protocol-types.test.ts +69 -0
- package/src/protocol-types.ts +296 -0
- package/src/protocol-types.typecheck.ts +89 -0
- package/src/protocol.test.ts +39 -0
- package/src/protocol.ts +42 -0
- package/src/reply-dispatcher.test.ts +1324 -0
- package/src/reply-dispatcher.ts +555 -0
- package/src/runtime.test.ts +4719 -0
- package/src/runtime.ts +1493 -0
- package/src/scripts.test.ts +85 -0
- package/src/storage.test.ts +560 -0
- package/src/storage.ts +807 -0
- package/src/terminal-send.test.ts +81 -0
- package/src/terminal-send.ts +56 -0
- package/src/tools-schema.ts +337 -0
- package/src/tools.test.ts +933 -0
- package/src/tools.ts +1185 -0
- package/src/ws-alignment.test.ts +103 -0
- package/src/ws-alignment.ts +275 -0
- package/src/ws-client.test.ts +1217 -0
- package/src/ws-client.ts +662 -0
- package/src/ws-log.test.ts +32 -0
- package/src/ws-log.ts +31 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
const MEDIA_KINDS = new Set(["image", "file", "audio", "video"]);
|
|
2
|
+
function isMediaKind(kind) {
|
|
3
|
+
return MEDIA_KINDS.has(kind);
|
|
4
|
+
}
|
|
5
|
+
function renderMediaPlaceholder(fragment) {
|
|
6
|
+
const kind = String(fragment.kind ?? "");
|
|
7
|
+
const url = typeof fragment.url === "string" ? fragment.url : "";
|
|
8
|
+
if (!url)
|
|
9
|
+
return "";
|
|
10
|
+
const name = typeof fragment.name === "string" && fragment.name.trim()
|
|
11
|
+
? fragment.name
|
|
12
|
+
: kind === "image"
|
|
13
|
+
? "image"
|
|
14
|
+
: kind === "audio"
|
|
15
|
+
? "audio"
|
|
16
|
+
: kind === "video"
|
|
17
|
+
? "video"
|
|
18
|
+
: "file";
|
|
19
|
+
return kind === "image" ? `` : `[${name}](${url})`;
|
|
20
|
+
}
|
|
21
|
+
export function fragmentsToText(fragments, opts = {}) {
|
|
22
|
+
const fallback = opts.mentionFallbackIds ?? [];
|
|
23
|
+
let fallbackCursor = 0;
|
|
24
|
+
const parts = fragments.map((fragment) => {
|
|
25
|
+
const f = fragment;
|
|
26
|
+
if (f.kind === "text" && typeof f.text === "string") {
|
|
27
|
+
return f.text;
|
|
28
|
+
}
|
|
29
|
+
if (f.kind === "mention") {
|
|
30
|
+
const display = typeof f.display === "string" ? f.display : undefined;
|
|
31
|
+
if (display && display.trim()) {
|
|
32
|
+
const trimmed = display.trim();
|
|
33
|
+
return trimmed.startsWith("@") ? trimmed : `@${trimmed}`;
|
|
34
|
+
}
|
|
35
|
+
const id = typeof f.user_id === "string" ? f.user_id : undefined;
|
|
36
|
+
if (id && id.trim())
|
|
37
|
+
return `@${id}`;
|
|
38
|
+
const fallbackId = fallback[fallbackCursor++];
|
|
39
|
+
if (fallbackId)
|
|
40
|
+
return `@${fallbackId}`;
|
|
41
|
+
return "";
|
|
42
|
+
}
|
|
43
|
+
if (typeof f.kind === "string" && isMediaKind(f.kind)) {
|
|
44
|
+
return renderMediaPlaceholder(f);
|
|
45
|
+
}
|
|
46
|
+
return "";
|
|
47
|
+
});
|
|
48
|
+
return parts.join("").trim();
|
|
49
|
+
}
|
|
50
|
+
export function textToFragments(text) {
|
|
51
|
+
if (!text || !text.trim())
|
|
52
|
+
return [];
|
|
53
|
+
return [{ kind: "text", text }];
|
|
54
|
+
}
|
|
55
|
+
export function normalizeMentionTargets(mentions) {
|
|
56
|
+
if (!Array.isArray(mentions) || mentions.length === 0) {
|
|
57
|
+
throw new Error("clawchat_mention_message requires at least one mention");
|
|
58
|
+
}
|
|
59
|
+
const seen = new Set();
|
|
60
|
+
const normalized = [];
|
|
61
|
+
for (let i = 0; i < mentions.length; i += 1) {
|
|
62
|
+
const mention = mentions[i];
|
|
63
|
+
const userId = typeof mention?.userId === "string" ? mention.userId.trim() : "";
|
|
64
|
+
if (!userId) {
|
|
65
|
+
throw new Error(`clawchat_mention_message requires mentions[${i}].userId`);
|
|
66
|
+
}
|
|
67
|
+
if (seen.has(userId))
|
|
68
|
+
continue;
|
|
69
|
+
const rawDisplay = typeof mention?.display === "string" ? mention.display.trim() : "";
|
|
70
|
+
const display = rawDisplay ? (rawDisplay.startsWith("@") ? rawDisplay.slice(1) : rawDisplay) : undefined;
|
|
71
|
+
seen.add(userId);
|
|
72
|
+
normalized.push(display ? { userId, display } : { userId });
|
|
73
|
+
}
|
|
74
|
+
return normalized;
|
|
75
|
+
}
|
|
76
|
+
const MENTION_LABEL_RE = /^@(?<label>\S+)(?<rest>(?:\s+.*)?)$/s;
|
|
77
|
+
export function applyTextMentionLabels(mentions, text) {
|
|
78
|
+
let remaining = typeof text === "string" ? text.trim() : "";
|
|
79
|
+
if (!remaining)
|
|
80
|
+
return { mentions, text: "" };
|
|
81
|
+
const normalized = mentions.map((mention) => ({ ...mention }));
|
|
82
|
+
const missingDisplay = normalized.filter((mention) => !mention.display);
|
|
83
|
+
if (normalized.length === 1 && missingDisplay.length === 1 && remaining.startsWith("@")) {
|
|
84
|
+
const label = remaining.slice(1).trim();
|
|
85
|
+
if (label) {
|
|
86
|
+
missingDisplay[0].display = label;
|
|
87
|
+
return { mentions: normalized, text: "" };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
for (const mention of normalized) {
|
|
91
|
+
if (mention.display)
|
|
92
|
+
continue;
|
|
93
|
+
const match = MENTION_LABEL_RE.exec(remaining);
|
|
94
|
+
if (!match?.groups?.label)
|
|
95
|
+
break;
|
|
96
|
+
mention.display = match.groups.label.trim();
|
|
97
|
+
remaining = (match.groups.rest ?? "").trim();
|
|
98
|
+
}
|
|
99
|
+
return { mentions: normalized, text: remaining };
|
|
100
|
+
}
|
|
101
|
+
export function buildMentionMessageFragments({ mentions, text, }) {
|
|
102
|
+
const { mentions: normalized, text: remainingText } = applyTextMentionLabels(normalizeMentionTargets(mentions), text);
|
|
103
|
+
const fragments = normalized.map((mention) => {
|
|
104
|
+
const fragment = {
|
|
105
|
+
kind: "mention",
|
|
106
|
+
user_id: mention.userId,
|
|
107
|
+
};
|
|
108
|
+
if (mention.display)
|
|
109
|
+
fragment.display = mention.display;
|
|
110
|
+
return fragment;
|
|
111
|
+
});
|
|
112
|
+
if (remainingText) {
|
|
113
|
+
fragments.push({ kind: "text", text: ` ${remainingText}` });
|
|
114
|
+
}
|
|
115
|
+
return fragments;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Extract media fragments from a body (image/file/audio/video). Skips
|
|
119
|
+
* entries missing `url`. Preserves all optional metadata fields the
|
|
120
|
+
* protocol carries through (mime/size/width/height/duration/name).
|
|
121
|
+
*/
|
|
122
|
+
export function extractMediaFragments(fragments) {
|
|
123
|
+
const out = [];
|
|
124
|
+
for (const fragment of fragments) {
|
|
125
|
+
const f = fragment;
|
|
126
|
+
if (typeof f.kind !== "string" || !isMediaKind(f.kind))
|
|
127
|
+
continue;
|
|
128
|
+
if (typeof f.url !== "string" || !f.url)
|
|
129
|
+
continue;
|
|
130
|
+
const item = { kind: f.kind, url: f.url };
|
|
131
|
+
if (typeof f.name === "string")
|
|
132
|
+
item.name = f.name;
|
|
133
|
+
if (typeof f.mime === "string")
|
|
134
|
+
item.mime = f.mime;
|
|
135
|
+
if (typeof f.size === "number")
|
|
136
|
+
item.size = f.size;
|
|
137
|
+
if (typeof f.width === "number")
|
|
138
|
+
item.width = f.width;
|
|
139
|
+
if (typeof f.height === "number")
|
|
140
|
+
item.height = f.height;
|
|
141
|
+
if (typeof f.duration === "number")
|
|
142
|
+
item.duration = f.duration;
|
|
143
|
+
out.push(item);
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export class MockTransport {
|
|
2
|
+
handlers;
|
|
3
|
+
currentState = "closed";
|
|
4
|
+
sent = [];
|
|
5
|
+
get state() {
|
|
6
|
+
return this.currentState;
|
|
7
|
+
}
|
|
8
|
+
async connect(_url, handlers) {
|
|
9
|
+
this.handlers = handlers;
|
|
10
|
+
this.currentState = "open";
|
|
11
|
+
handlers.onOpen();
|
|
12
|
+
}
|
|
13
|
+
send(data) {
|
|
14
|
+
if (this.currentState !== "open") {
|
|
15
|
+
throw new Error("transport is not open");
|
|
16
|
+
}
|
|
17
|
+
this.sent.push(data);
|
|
18
|
+
}
|
|
19
|
+
close(code = 1000, reason = "client close") {
|
|
20
|
+
if (this.currentState === "closed")
|
|
21
|
+
return;
|
|
22
|
+
this.currentState = "closed";
|
|
23
|
+
this.handlers?.onClose(code, reason);
|
|
24
|
+
}
|
|
25
|
+
emitInbound(data) {
|
|
26
|
+
this.handlers?.onMessage(data);
|
|
27
|
+
}
|
|
28
|
+
emitError(err) {
|
|
29
|
+
this.handlers?.onError(err);
|
|
30
|
+
}
|
|
31
|
+
}
|