@clawling/clawchat-plugin-openclaw 2026.6.24-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 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
- See the repository root.
189
+ MIT — see [LICENSE](./LICENSE).
@@ -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" },
@@ -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
@@ -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 msghub Phase-4 validation will reject it).
421
+ // different scheme (planned backend validation will reject it).
422
422
  const mintStaticMessageId = () => mintMessageId();
423
423
  const emitTyping = (isTyping) => {
424
424
  if (!isTyping && !typingActive)
@@ -11,6 +11,7 @@ import { RefreshManager } from "./refresh-manager.js";
11
11
  import { runOpenclawClawlingLogin, } from "./login.runtime.js";
12
12
  import { CHANNEL_ID, effectiveOutputVisibility, effectiveGroupCommandMode, effectiveGroupMode, hasOpenclawClawlingConnectCredentials, resolveOpenclawClawlingAccount, } from "./config.js";
13
13
  import { dispatchOpenclawClawlingInbound } from "./inbound.js";
14
+ import { PendingConsentStore, handleOwnerConsentReply, readEffectiveSkillVersions, resolveBundledSkillsDir, resolveManagedSkillsDir, runSkillUpdateCheck, } from "./skill-update.js";
14
15
  import { fetchInboundMedia } from "./media-runtime.js";
15
16
  import { createOpenclawClawlingReplyDispatcher } from "./reply-dispatcher.js";
16
17
  import { runWithTerminalClawChatSendScope } from "./terminal-send.js";
@@ -1655,6 +1656,75 @@ export async function startOpenclawClawlingGateway(params) {
1655
1656
  client.on("metadata:invalidated", (env) => {
1656
1657
  void handleMetadataInvalidation(env);
1657
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
+ };
1658
1728
  client.on("notify:signal", (env) => {
1659
1729
  // §9.4 reliable system notification. The plugin holds no friend/roster
1660
1730
  // cache (friends are fetched on demand via REST tools), so there is nothing
@@ -1670,6 +1740,15 @@ export async function startOpenclawClawlingGateway(params) {
1670
1740
  log?.info?.(`[${accountId}] clawchat-plugin-openclaw agent.config.changed signal received; re-pulling group settings`);
1671
1741
  pullGroupSettings();
1672
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
+ }
1673
1752
  }
1674
1753
  });
1675
1754
  client.on("replay:done", (env) => {
@@ -2278,6 +2357,7 @@ export async function startOpenclawClawlingGateway(params) {
2278
2357
  runtime,
2279
2358
  account,
2280
2359
  log,
2360
+ consumeOwnerConsent: consumeOwnerSkillConsent,
2281
2361
  ingest: async (turn) => {
2282
2362
  ingestResult = await ingestTurn(turn);
2283
2363
  },