@clawling/clawchat-plugin-openclaw 2026.6.23-1 → 2026.6.30-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/README.md +11 -3
- package/dist/src/api-client.js +3 -0
- package/dist/src/inbound.js +21 -0
- package/dist/src/liveware-cli.js +93 -0
- package/dist/src/login.runtime.js +2 -0
- package/dist/src/reply-dispatcher.js +1 -1
- package/dist/src/runtime.js +90 -0
- package/dist/src/skill-update.js +559 -0
- package/dist/src/tools.js +11 -4
- package/dist/src/ws-client.js +1 -1
- package/package.json +18 -1
- package/skills/clawchat/SKILL.md +1 -0
- package/skills/liveware-app/SKILL.md +1 -0
- package/src/api-client.ts +13 -2
- package/src/inbound.ts +41 -0
- package/src/liveware-cli.ts +106 -0
- package/src/login.runtime.ts +2 -0
- package/src/reply-dispatcher.ts +1 -1
- package/src/runtime.ts +110 -0
- package/src/skill-update.ts +752 -0
- package/src/tools.ts +11 -4
- package/src/ws-client.ts +1 -1
package/README.md
CHANGED
|
@@ -90,20 +90,26 @@ src/
|
|
|
90
90
|
api-types.ts /v1/* request/response type definitions
|
|
91
91
|
inbound.ts envelope → agent turn
|
|
92
92
|
outbound.ts agent reply → envelope
|
|
93
|
-
streaming.ts progressive reply emitter
|
|
94
|
-
buffered-stream.ts chunk coalescing + flush policy
|
|
95
93
|
message-mapper.ts fragment ↔ text/media mapping
|
|
96
94
|
group-message-coalescer.ts
|
|
97
95
|
per-group idle/max batching for non-mention turns
|
|
96
|
+
group-settings.ts per-group agent settings cache (GET /v1/agents/me/
|
|
97
|
+
group-settings)
|
|
98
98
|
reply-dispatcher.ts static vs stream routing
|
|
99
|
+
no-reply.ts clawchat:no-reply / :silent suppression token guard
|
|
99
100
|
login.runtime.ts invite-code exchange flow
|
|
101
|
+
refresh-manager.ts ClawChat token refresh + auto-logout orchestration
|
|
102
|
+
skill-update.ts conversational, adapter-driven skill hot-update
|
|
100
103
|
media-runtime.ts media download/upload pipeline
|
|
104
|
+
liveware-cli.ts liveware asset download helper (custom CDN User-Agent)
|
|
101
105
|
tools.ts clawchat_* agent tools (account / media / memory /
|
|
102
106
|
metadata / moments / mention)
|
|
103
107
|
tools-schema.ts JSON schemas for the clawchat_* tools
|
|
104
108
|
protocol.ts inbound payload type guards
|
|
105
109
|
protocol-types.ts v2 envelope, fragment, transport shapes
|
|
106
110
|
config.ts defaults + typebox schema
|
|
111
|
+
config-compat.ts migration for the plugin rename (openclaw-clawchat →
|
|
112
|
+
clawchat-plugin-openclaw id/channel key)
|
|
107
113
|
storage.ts plugin-owned SQLite (activations, connections,
|
|
108
114
|
clawchat_messages, tool_calls)
|
|
109
115
|
clawchat-memory.ts file-backed memory tools (see docs/clawchat-memory.md)
|
|
@@ -114,6 +120,8 @@ src/
|
|
|
114
120
|
plugin-prompts.ts loads prompts/platform.md and default-* prompts
|
|
115
121
|
ws-alignment.ts handshake capabilities + hello-ok diagnostics
|
|
116
122
|
ws-log.ts clawchat.ws single-line log formatting
|
|
123
|
+
llm-context-debug.ts opt-in LLM input/output snapshot capture for debugging
|
|
124
|
+
plugin-report.ts best-effort read of this plugin's package version
|
|
117
125
|
commands.ts /clawchat-activate runtime slash command
|
|
118
126
|
terminal-send.ts terminal-send suppression for clawchat_mention_message
|
|
119
127
|
mock-transport.ts in-memory Transport double for tests
|
|
@@ -178,4 +186,4 @@ plugin tests or publish the package.
|
|
|
178
186
|
|
|
179
187
|
## License
|
|
180
188
|
|
|
181
|
-
|
|
189
|
+
MIT — see [LICENSE](./LICENSE).
|
package/dist/src/api-client.js
CHANGED
|
@@ -418,6 +418,9 @@ export function createOpenclawClawlingApiClient(opts) {
|
|
|
418
418
|
if (userId?.trim()) {
|
|
419
419
|
body.user_id = userId.trim();
|
|
420
420
|
}
|
|
421
|
+
if (opts.pluginVersion?.trim()) {
|
|
422
|
+
body.plugin_version = opts.pluginVersion.trim();
|
|
423
|
+
}
|
|
421
424
|
return await call("POST", "/v1/agents/connect", {
|
|
422
425
|
// `X-Device-Id` is added globally via `authHeaders` on every request.
|
|
423
426
|
headers: { "content-type": "application/json" },
|
package/dist/src/inbound.js
CHANGED
|
@@ -149,6 +149,27 @@ export async function dispatchOpenclawClawlingInbound(params) {
|
|
|
149
149
|
chatType,
|
|
150
150
|
userId: account.userId,
|
|
151
151
|
});
|
|
152
|
+
// Pending-consent gate for conversational skill hot-update. Only owner direct
|
|
153
|
+
// messages can answer a pending skill-update ask; the gate parses consent
|
|
154
|
+
// conservatively and, when it consumes the reply (affirm/deny), the message
|
|
155
|
+
// does NOT continue to the LLM. Ambiguous/unrelated replies return false and
|
|
156
|
+
// flow on normally (pending survives until timeout). See src/skill-update.ts.
|
|
157
|
+
if (chatType === "direct" &&
|
|
158
|
+
params.consumeOwnerConsent &&
|
|
159
|
+
account.ownerUserId &&
|
|
160
|
+
sender.id === account.ownerUserId) {
|
|
161
|
+
let consumed = false;
|
|
162
|
+
try {
|
|
163
|
+
consumed = await params.consumeOwnerConsent({ senderId: sender.id, text: rawBody, chatId });
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
log?.error?.(`[${account.accountId}] clawchat-plugin-openclaw skill-update consent gate error msg=${payload.message_id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
167
|
+
}
|
|
168
|
+
if (consumed) {
|
|
169
|
+
log?.info?.(`[${account.accountId}] clawchat-plugin-openclaw owner reply consumed as skill-update consent msg=${payload.message_id}`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
152
173
|
// NOTE: the static mention-mode early-drop was removed here (Task 7 fix).
|
|
153
174
|
// Non-mention group messages now always flow through to ingestTurn, which
|
|
154
175
|
// applies the dynamic gate (GroupSettingsCache.effective) and returns
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
|
|
4
|
+
export const LIVEWARE_BASE_URL = "https://media.clawling.chat/liveware/";
|
|
5
|
+
// A non-default User-Agent is required: the CDN (Cloudflare) returns 403 for
|
|
6
|
+
// known scripting/default UAs, so download requests must identify themselves.
|
|
7
|
+
const LIVEWARE_USER_AGENT = "clawchat-liveware-installer";
|
|
8
|
+
/** Directory where the downloaded binaries live: <stateDir>/clawchat/liveware. */
|
|
9
|
+
export function livewareDir(stateDir) {
|
|
10
|
+
const root = stateDir ?? resolveStateDir();
|
|
11
|
+
return path.join(root, "clawchat", "liveware");
|
|
12
|
+
}
|
|
13
|
+
/** Map host platform/arch to the two asset filenames, or null if unsupported. */
|
|
14
|
+
export function platformAssets(plat = process.platform, arch = process.arch) {
|
|
15
|
+
const os = plat === "darwin" ? "darwin" : plat === "linux" ? "linux" : null;
|
|
16
|
+
const cpu = arch === "x64" ? "amd64" : arch === "arm64" ? "arm64" : null;
|
|
17
|
+
if (!os || !cpu)
|
|
18
|
+
return null;
|
|
19
|
+
return { liveware: `liveware-${os}-${cpu}`, tunneAgent: `tunnel-agent-${os}-${cpu}` };
|
|
20
|
+
}
|
|
21
|
+
function findOnPath(name) {
|
|
22
|
+
const raw = process.env.PATH ?? "";
|
|
23
|
+
for (const dir of raw.split(path.delimiter)) {
|
|
24
|
+
if (!dir)
|
|
25
|
+
continue;
|
|
26
|
+
const candidate = path.join(dir, name);
|
|
27
|
+
try {
|
|
28
|
+
fs.accessSync(candidate, fs.constants.X_OK);
|
|
29
|
+
return candidate;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// not here; keep scanning
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Resolve the liveware executable to invoke:
|
|
39
|
+
* 1. on PATH → "liveware" (let the OS resolve it)
|
|
40
|
+
* 2. downloaded dir → absolute path
|
|
41
|
+
* 3. otherwise → null
|
|
42
|
+
*/
|
|
43
|
+
export function resolveLivewarePath(stateDir) {
|
|
44
|
+
if (findOnPath("liveware"))
|
|
45
|
+
return "liveware";
|
|
46
|
+
const local = path.join(livewareDir(stateDir), "liveware");
|
|
47
|
+
try {
|
|
48
|
+
fs.accessSync(local, fs.constants.X_OK);
|
|
49
|
+
return local;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function downloadTo(url, dest) {
|
|
56
|
+
const res = await fetch(url, { headers: { "user-agent": LIVEWARE_USER_AGENT } });
|
|
57
|
+
if (!res.ok)
|
|
58
|
+
throw new Error(`download ${url} failed: HTTP ${res.status}`);
|
|
59
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
60
|
+
if (buf.length === 0)
|
|
61
|
+
throw new Error(`download ${url} returned empty body`);
|
|
62
|
+
const tmp = `${dest}.tmp`;
|
|
63
|
+
await fs.promises.writeFile(tmp, buf);
|
|
64
|
+
await fs.promises.chmod(tmp, 0o755);
|
|
65
|
+
await fs.promises.rename(tmp, dest);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Startup hook: ensure the liveware CLI is available, downloading it when not.
|
|
69
|
+
* Fire-and-forget — never throws; logs and returns on any failure.
|
|
70
|
+
*/
|
|
71
|
+
export async function ensureLivewareCli(opts = {}) {
|
|
72
|
+
const { stateDir, log } = opts;
|
|
73
|
+
try {
|
|
74
|
+
if (findOnPath("liveware"))
|
|
75
|
+
return; // PATH wins
|
|
76
|
+
const dir = livewareDir(stateDir);
|
|
77
|
+
const target = path.join(dir, "liveware");
|
|
78
|
+
if (fs.existsSync(target))
|
|
79
|
+
return;
|
|
80
|
+
const assets = platformAssets();
|
|
81
|
+
if (!assets) {
|
|
82
|
+
log?.warn?.(`clawchat-plugin-openclaw: unsupported platform ${process.platform}/${process.arch}; skipping liveware download`);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
86
|
+
await downloadTo(LIVEWARE_BASE_URL + assets.liveware, path.join(dir, "liveware"));
|
|
87
|
+
await downloadTo(LIVEWARE_BASE_URL + assets.tunneAgent, path.join(dir, "tunnel-agent"));
|
|
88
|
+
log?.debug?.(`clawchat-plugin-openclaw: liveware CLI downloaded to ${dir}`);
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
log?.warn?.(`clawchat-plugin-openclaw: liveware CLI download skipped: ${String(err)}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createInterface } from "node:readline/promises";
|
|
2
2
|
import { createOpenclawClawlingApiClient } from "./api-client.js";
|
|
3
3
|
import { ClawlingApiError } from "./api-types.js";
|
|
4
|
+
import { resolvePluginVersion } from "./plugin-report.js";
|
|
4
5
|
import { CHANNEL_ID, mergeOpenclawClawchatRuntimePluginActivation, mergeOpenclawClawchatToolAllow, resolveOpenclawClawlingAccount, } from "./config.js";
|
|
5
6
|
import { getClawChatStore } from "./storage.js";
|
|
6
7
|
/**
|
|
@@ -135,6 +136,7 @@ export async function runOpenclawClawlingLogin(params) {
|
|
|
135
136
|
// Pre-login we may not have a token yet. Send the current one (or empty)
|
|
136
137
|
// — the server should accept an unauthenticated invite-code exchange.
|
|
137
138
|
token: account.token || "",
|
|
139
|
+
pluginVersion: resolvePluginVersion(),
|
|
138
140
|
});
|
|
139
141
|
runtime.log("Verifying invite code …");
|
|
140
142
|
let result;
|
|
@@ -418,7 +418,7 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
418
418
|
};
|
|
419
419
|
// §7.6 (binding): client-supplied message_id MUST be `msg-` + a 26-char
|
|
420
420
|
// Crockford base32 ULID. Use the shared conformant minter — do not invent a
|
|
421
|
-
// different scheme (planned
|
|
421
|
+
// different scheme (planned backend validation will reject it).
|
|
422
422
|
const mintStaticMessageId = () => mintMessageId();
|
|
423
423
|
const emitTyping = (isTyping) => {
|
|
424
424
|
if (!isTyping && !typingActive)
|
package/dist/src/runtime.js
CHANGED
|
@@ -5,11 +5,13 @@ import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
|
|
|
5
5
|
import { createOpenclawClawlingClient, resolveOpenclawClawlingDeviceId } from "./client.js";
|
|
6
6
|
import { createOpenclawClawlingApiClient } from "./api-client.js";
|
|
7
7
|
import { reportPluginVersionSafe, resolvePluginVersion } from "./plugin-report.js";
|
|
8
|
+
import { ensureLivewareCli } from "./liveware-cli.js";
|
|
8
9
|
import { ClawlingApiError } from "./api-types.js";
|
|
9
10
|
import { RefreshManager } from "./refresh-manager.js";
|
|
10
11
|
import { runOpenclawClawlingLogin, } from "./login.runtime.js";
|
|
11
12
|
import { CHANNEL_ID, effectiveOutputVisibility, effectiveGroupCommandMode, effectiveGroupMode, hasOpenclawClawlingConnectCredentials, resolveOpenclawClawlingAccount, } from "./config.js";
|
|
12
13
|
import { dispatchOpenclawClawlingInbound } from "./inbound.js";
|
|
14
|
+
import { PendingConsentStore, handleOwnerConsentReply, readEffectiveSkillVersions, resolveBundledSkillsDir, resolveManagedSkillsDir, runSkillUpdateCheck, } from "./skill-update.js";
|
|
13
15
|
import { fetchInboundMedia } from "./media-runtime.js";
|
|
14
16
|
import { createOpenclawClawlingReplyDispatcher } from "./reply-dispatcher.js";
|
|
15
17
|
import { runWithTerminalClawChatSendScope } from "./terminal-send.js";
|
|
@@ -618,6 +620,15 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
618
620
|
authenticated: false,
|
|
619
621
|
log,
|
|
620
622
|
});
|
|
623
|
+
// Ensure the liveware CLI is installed (async, never blocks startup).
|
|
624
|
+
void ensureLivewareCli({
|
|
625
|
+
stateDir: runtime.state?.resolveStateDir?.(),
|
|
626
|
+
log: {
|
|
627
|
+
warn: (m) => log?.info?.(m),
|
|
628
|
+
debug: (m) => log?.debug?.(m),
|
|
629
|
+
error: (m) => log?.error?.(m),
|
|
630
|
+
},
|
|
631
|
+
});
|
|
621
632
|
// §B self-heal — non-interactive env connect-code activation BEFORE blocking on
|
|
622
633
|
// the wait-for-activation loop. Gated so the common path (no env code, or an
|
|
623
634
|
// already-credentialed account) adds NO async work: on success it credentials
|
|
@@ -1645,6 +1656,75 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
1645
1656
|
client.on("metadata:invalidated", (env) => {
|
|
1646
1657
|
void handleMetadataInvalidation(env);
|
|
1647
1658
|
});
|
|
1659
|
+
// --- Conversational skill hot-update (adapter-driven; see src/skill-update.ts) ---
|
|
1660
|
+
// One pending-consent record per runtime.
|
|
1661
|
+
// WRITE target = the OpenClaw-managed skills dir (`<stateDir>/skills`): it is
|
|
1662
|
+
// chokidar-watched (host src/skills/runtime/refresh.ts:107) so a write bumps
|
|
1663
|
+
// the snapshot + rebuilds the prompt next turn, and it loads at HIGHER
|
|
1664
|
+
// precedence than plugin-bundled skills (managed=3 > bundled=2, host
|
|
1665
|
+
// src/skills/loading/workspace.ts:1220-1239), overriding the bundled copy.
|
|
1666
|
+
// The bundled `./skills` dir is READ-ONLY here: a first-boot fallback for the
|
|
1667
|
+
// "current installed version" only — never written (it lives in node_modules
|
|
1668
|
+
// → may be read-only and is clobbered on `openclaw plugins update`).
|
|
1669
|
+
const skillUpdateConsent = new PendingConsentStore();
|
|
1670
|
+
const managedSkillsDir = resolveManagedSkillsDir();
|
|
1671
|
+
let bundledSkillsDir = null;
|
|
1672
|
+
try {
|
|
1673
|
+
bundledSkillsDir = resolveBundledSkillsDir();
|
|
1674
|
+
}
|
|
1675
|
+
catch (err) {
|
|
1676
|
+
log?.info?.(`[${accountId}] clawchat-plugin-openclaw skill-update: bundled skills dir not resolvable (using managed-only current): ${err instanceof Error ? err.message : String(err)}`);
|
|
1677
|
+
}
|
|
1678
|
+
const sendOwnerSkillMessage = async (text) => {
|
|
1679
|
+
const ownerUserId = account.ownerUserId?.trim();
|
|
1680
|
+
if (!ownerUserId)
|
|
1681
|
+
return;
|
|
1682
|
+
await sendOpenclawClawlingText({
|
|
1683
|
+
client,
|
|
1684
|
+
account,
|
|
1685
|
+
to: { chatId: ownerUserId, chatType: "direct" },
|
|
1686
|
+
text,
|
|
1687
|
+
log,
|
|
1688
|
+
});
|
|
1689
|
+
};
|
|
1690
|
+
const triggerSkillUpdateCheck = async () => {
|
|
1691
|
+
const ownerUserId = account.ownerUserId?.trim();
|
|
1692
|
+
if (!ownerUserId) {
|
|
1693
|
+
log?.info?.(`[${accountId}] clawchat-plugin-openclaw skill-update check skipped: no owner user id`);
|
|
1694
|
+
return;
|
|
1695
|
+
}
|
|
1696
|
+
// Effective current = managed copy if present, else bundled (host precedence).
|
|
1697
|
+
const localVersions = await readEffectiveSkillVersions(managedSkillsDir, bundledSkillsDir);
|
|
1698
|
+
await runSkillUpdateCheck({
|
|
1699
|
+
ownerUserId,
|
|
1700
|
+
localVersions,
|
|
1701
|
+
fetchFn: globalThis.fetch,
|
|
1702
|
+
store: skillUpdateConsent,
|
|
1703
|
+
sendOwnerMessage: sendOwnerSkillMessage,
|
|
1704
|
+
log: {
|
|
1705
|
+
info: (m) => log?.info?.(`[${accountId}] clawchat-plugin-openclaw ${m}`),
|
|
1706
|
+
error: (m) => log?.error?.(`[${accountId}] clawchat-plugin-openclaw ${m}`),
|
|
1707
|
+
},
|
|
1708
|
+
});
|
|
1709
|
+
};
|
|
1710
|
+
const consumeOwnerSkillConsent = async (args) => {
|
|
1711
|
+
const ownerUserId = account.ownerUserId?.trim();
|
|
1712
|
+
if (!ownerUserId)
|
|
1713
|
+
return false;
|
|
1714
|
+
return handleOwnerConsentReply({
|
|
1715
|
+
ownerUserId,
|
|
1716
|
+
senderId: args.senderId,
|
|
1717
|
+
text: args.text,
|
|
1718
|
+
store: skillUpdateConsent,
|
|
1719
|
+
fetchFn: globalThis.fetch,
|
|
1720
|
+
skillsDir: managedSkillsDir,
|
|
1721
|
+
sendOwnerMessage: sendOwnerSkillMessage,
|
|
1722
|
+
log: {
|
|
1723
|
+
info: (m) => log?.info?.(`[${accountId}] clawchat-plugin-openclaw ${m}`),
|
|
1724
|
+
error: (m) => log?.error?.(`[${accountId}] clawchat-plugin-openclaw ${m}`),
|
|
1725
|
+
},
|
|
1726
|
+
});
|
|
1727
|
+
};
|
|
1648
1728
|
client.on("notify:signal", (env) => {
|
|
1649
1729
|
// §9.4 reliable system notification. The plugin holds no friend/roster
|
|
1650
1730
|
// cache (friends are fetched on demand via REST tools), so there is nothing
|
|
@@ -1660,6 +1740,15 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
1660
1740
|
log?.info?.(`[${accountId}] clawchat-plugin-openclaw agent.config.changed signal received; re-pulling group settings`);
|
|
1661
1741
|
pullGroupSettings();
|
|
1662
1742
|
}
|
|
1743
|
+
else if (type === "clawchat.skill.update.check") {
|
|
1744
|
+
// Content-free trigger: check the official skill source in code and, if
|
|
1745
|
+
// there is an update, ask the owner in chat for consent. The atomic
|
|
1746
|
+
// overwrite happens later when the owner affirms (consent gate).
|
|
1747
|
+
log?.info?.(`[${accountId}] clawchat-plugin-openclaw clawchat.skill.update.check signal received; checking official skill source`);
|
|
1748
|
+
void triggerSkillUpdateCheck().catch((err) => {
|
|
1749
|
+
log?.error?.(`[${accountId}] clawchat-plugin-openclaw skill-update check failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1750
|
+
});
|
|
1751
|
+
}
|
|
1663
1752
|
}
|
|
1664
1753
|
});
|
|
1665
1754
|
client.on("replay:done", (env) => {
|
|
@@ -2268,6 +2357,7 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
2268
2357
|
runtime,
|
|
2269
2358
|
account,
|
|
2270
2359
|
log,
|
|
2360
|
+
consumeOwnerConsent: consumeOwnerSkillConsent,
|
|
2271
2361
|
ingest: async (turn) => {
|
|
2272
2362
|
ingestResult = await ingestTurn(turn);
|
|
2273
2363
|
},
|