@clawling/clawchat-plugin-openclaw 2026.10.9-1 → 2026.10.9-3
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 +1 -1
- package/dist/index.js +4 -0
- package/dist/src/clawchat-memory.js +6 -1
- package/dist/src/config.js +6 -0
- package/dist/src/connect-check.js +8 -1
- package/dist/src/connect-failure.js +78 -0
- package/dist/src/host-tools-guard.js +163 -0
- package/dist/src/host-version.js +35 -0
- package/dist/src/login.runtime.js +31 -4
- package/dist/src/memory-scope.js +166 -0
- package/dist/src/owner-identity.js +29 -0
- package/dist/src/reply-dispatcher.js +26 -13
- package/dist/src/runtime.js +73 -12
- package/dist/src/storage.js +41 -2
- package/dist/src/tools.js +122 -16
- package/dist/src/turn-boundary.js +51 -0
- package/index.ts +4 -0
- package/openclaw.plugin.json +4 -0
- package/package.json +3 -3
- package/src/clawchat-memory.ts +13 -0
- package/src/config.ts +11 -0
- package/src/connect-check.ts +8 -1
- package/src/connect-failure.ts +109 -0
- package/src/host-tools-guard.ts +186 -0
- package/src/host-version.ts +35 -0
- package/src/login.runtime.ts +36 -4
- package/src/memory-scope.ts +191 -0
- package/src/owner-identity.ts +49 -0
- package/src/reply-dispatcher.ts +31 -13
- package/src/runtime.ts +80 -12
- package/src/storage.ts +42 -1
- package/src/tools.ts +133 -20
- package/src/turn-boundary.ts +61 -0
|
@@ -268,6 +268,20 @@ function formatApprovalSummary(payload) {
|
|
|
268
268
|
export function createOpenclawClawlingReplyDispatcher(options) {
|
|
269
269
|
const { cfg, runtime, account, client, target, replyCtx, inboundMessageId, store, resolveOwnerConversationId, resolveOwnerLocale, log, } = options;
|
|
270
270
|
const isGroupTarget = target.chatType === "group";
|
|
271
|
+
let warnedLocalReplyMedia = false;
|
|
272
|
+
// Non-owner turns may only send remote (http/https) media. Local sources are
|
|
273
|
+
// dropped here, before any load, because the host's local media roots include
|
|
274
|
+
// the agent workspace. Warns once per dispatcher and never names the source.
|
|
275
|
+
function filterReplyMedia(urls) {
|
|
276
|
+
if (!options.nonOwnerTurn)
|
|
277
|
+
return urls;
|
|
278
|
+
const remote = urls.filter((source) => /^https?:\/\//i.test(source.trim()));
|
|
279
|
+
if (remote.length !== urls.length && !warnedLocalReplyMedia) {
|
|
280
|
+
warnedLocalReplyMedia = true;
|
|
281
|
+
log?.warn?.("clawchat-plugin-openclaw: non-owner reply media dropped; only http(s) URLs are allowed");
|
|
282
|
+
}
|
|
283
|
+
return remote;
|
|
284
|
+
}
|
|
271
285
|
const outputVisibility = effectiveOutputVisibility(account, target.chatId, target.chatType);
|
|
272
286
|
const splitFullOutput = outputVisibility === "full";
|
|
273
287
|
const splitNormalBlockOutput = outputVisibility === "normal";
|
|
@@ -275,12 +289,10 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
275
289
|
// owner's usr_… id: chat_id is resolved by msghub through member-backend, and
|
|
276
290
|
// a usr_… comes back "invalid conversation id" — the frame is dropped with no
|
|
277
291
|
// negative ack to this client, so the owner silently never gets the approval
|
|
278
|
-
// request.
|
|
279
|
-
//
|
|
292
|
+
// request. The resolver is the shared strict owner-identity lookup
|
|
293
|
+
// (src/owner-identity.ts): it yields a conversation only for an activation
|
|
294
|
+
// row with an owner, so the config `ownerUserId` mirror is not consulted.
|
|
280
295
|
const ownerDirectTarget = () => {
|
|
281
|
-
const ownerUserId = account.ownerUserId?.trim();
|
|
282
|
-
if (!ownerUserId)
|
|
283
|
-
return null;
|
|
284
296
|
const conversationId = resolveOwnerConversationId?.()?.trim();
|
|
285
297
|
return conversationId ? { chatId: conversationId, chatType: "direct" } : null;
|
|
286
298
|
};
|
|
@@ -295,7 +307,8 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
295
307
|
userId: account.userId,
|
|
296
308
|
});
|
|
297
309
|
};
|
|
298
|
-
async function uploadMediaUrls(
|
|
310
|
+
async function uploadMediaUrls(inputUrls) {
|
|
311
|
+
const urls = filterReplyMedia(inputUrls);
|
|
299
312
|
if (urls.length === 0)
|
|
300
313
|
return [];
|
|
301
314
|
const apiClient = buildApiClient();
|
|
@@ -551,10 +564,7 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
551
564
|
const sendOwnerAttention = async (fallbackText, richFragment = null) => {
|
|
552
565
|
const ownerTarget = ownerDirectTarget();
|
|
553
566
|
if (!ownerTarget) {
|
|
554
|
-
|
|
555
|
-
? "missing_owner_conversation"
|
|
556
|
-
: "missing_owner_user_id";
|
|
557
|
-
log?.error?.(`[${account.accountId}] clawchat-plugin-openclaw group owner attention suppressed reason=${reason} group=${target.chatId}`);
|
|
567
|
+
log?.error?.(`[${account.accountId}] clawchat-plugin-openclaw group owner attention suppressed reason=missing_owner_conversation group=${target.chatId}`);
|
|
558
568
|
return;
|
|
559
569
|
}
|
|
560
570
|
await sendOpenclawClawlingText({
|
|
@@ -726,7 +736,7 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
726
736
|
return;
|
|
727
737
|
const richFragment = buildRichInteractionFragment(payload);
|
|
728
738
|
const text = richFragment && account.richInteractions ? "" : resolvePayloadText(payload);
|
|
729
|
-
const urls = resolveOutboundMediaUrls(payload).filter(Boolean);
|
|
739
|
+
const urls = filterReplyMedia(resolveOutboundMediaUrls(payload).filter(Boolean));
|
|
730
740
|
log?.info?.(`[${account.accountId}] clawchat-plugin-openclaw deliver kind=${info?.kind ?? "unknown"} text_len=${text.length} media_urls=${urls.length} reasoning=${payload.isReasoning === true}`);
|
|
731
741
|
if (isGroupTarget && richFragment) {
|
|
732
742
|
if (info?.kind === "final")
|
|
@@ -820,6 +830,9 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
820
830
|
log?.info?.(`[${account.accountId}] clawchat-plugin-openclaw final skipped: already delivered at a message boundary`);
|
|
821
831
|
return;
|
|
822
832
|
}
|
|
833
|
+
// A non-owner final whose only content was dropped local media sends nothing.
|
|
834
|
+
if (options.nonOwnerTurn && !finalText.trim() && !richFragment && finalUrls.length === 0)
|
|
835
|
+
return;
|
|
823
836
|
const mediaFragments = await uploadMediaUrls(finalUrls);
|
|
824
837
|
const result = await sendStatic(finalText, mediaFragments, richFragment && account.richInteractions ? [richFragment] : [], { recordMessage: true });
|
|
825
838
|
if (result?.messageId)
|
|
@@ -897,7 +910,7 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
897
910
|
if (consumeTerminalSend("reasoning"))
|
|
898
911
|
return;
|
|
899
912
|
const text = resolvePayloadText(payload);
|
|
900
|
-
await emitFullRuntimeText("reasoning", text, resolveOutboundMediaUrls(payload).filter(Boolean));
|
|
913
|
+
await emitFullRuntimeText("reasoning", text, filterReplyMedia(resolveOutboundMediaUrls(payload).filter(Boolean)));
|
|
901
914
|
const trimmed = text.trim();
|
|
902
915
|
if (trimmed)
|
|
903
916
|
reasoningText = reasoningText ? `${reasoningText}\n${trimmed}` : trimmed;
|
|
@@ -926,7 +939,7 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
926
939
|
const text = resolvePayloadText(payload);
|
|
927
940
|
if (isDefaultToolResultText(text))
|
|
928
941
|
return;
|
|
929
|
-
await emitFullRuntimeText("tool result", text, resolveOutboundMediaUrls(payload).filter(Boolean));
|
|
942
|
+
await emitFullRuntimeText("tool result", text, filterReplyMedia(resolveOutboundMediaUrls(payload).filter(Boolean)));
|
|
930
943
|
}
|
|
931
944
|
: undefined,
|
|
932
945
|
onItemEvent: splitFullOutput
|
package/dist/src/runtime.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { applyDmRecallDefault } from "./dm-recall-default.js";
|
|
2
|
+
import { HOST_FLOOR, HOST_TOOL_SEARCH_DEFAULT_ON, hostAtLeast, hostToolSearchEnabled } from "./host-version.js";
|
|
2
3
|
import { AckTimeoutError, AuthError, ProtocolError, StateError, TransportError, EVENT, recallTargetMessageId, } from "./protocol-types.js";
|
|
3
4
|
import { waitUntilAbort } from "openclaw/plugin-sdk/channel-lifecycle";
|
|
4
5
|
import { hasControlCommand } from "openclaw/plugin-sdk/command-detection";
|
|
@@ -29,7 +30,9 @@ import { flushAlignedOutboundQueue, getAlignedOutboundQueueSize, sendOpenclawCla
|
|
|
29
30
|
import { formatWsLog } from "./ws-log.js";
|
|
30
31
|
import { createNotifySignalObserver, createProtocolControlHandler, createReconnectTracker } from "./ws-alignment.js";
|
|
31
32
|
import { clawChatDbPathForStateDir, getClawChatStore, makeOwnerProfilePersister, } from "./storage.js";
|
|
32
|
-
import { getClawChatGroupPrompt, getClawChatUserPrompt } from "./plugin-prompts.js";
|
|
33
|
+
import { getClawChatGroupPrompt, getClawChatPlatformPrompt, getClawChatUserPrompt } from "./plugin-prompts.js";
|
|
34
|
+
import { classifyChat, NON_OWNER_TOOL_ALLOWLIST } from "./turn-boundary.js";
|
|
35
|
+
import { resolveOwnerIdentity } from "./owner-identity.js";
|
|
33
36
|
import { loadClawChatPromptMetadata, renderClawChatProfilePrompt, resolveSenderRelation, } from "./profile-prompt.js";
|
|
34
37
|
import { buildNoteMemorySection } from "./note-injection.js";
|
|
35
38
|
import { buildGroupCatchUp, CATCH_UP_FETCH_ROWS, DeliveredGroupMessages } from "./group-catch-up.js";
|
|
@@ -713,6 +716,8 @@ async function waitForActivationCredentials(params) {
|
|
|
713
716
|
});
|
|
714
717
|
return null;
|
|
715
718
|
}
|
|
719
|
+
let warnedBelowHostFloor = false;
|
|
720
|
+
let loggedHostToolSearch = false;
|
|
716
721
|
export async function startOpenclawClawlingGateway(params) {
|
|
717
722
|
const { cfg, abortSignal, setStatus, getStatus, log } = params;
|
|
718
723
|
let account = params.account;
|
|
@@ -720,6 +725,19 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
720
725
|
const runtime = getOpenclawClawlingRuntime();
|
|
721
726
|
const accountId = account.accountId;
|
|
722
727
|
const store = resolveConnectionStore(params, runtime);
|
|
728
|
+
if (!warnedBelowHostFloor && !hostAtLeast(runtime.version, HOST_FLOOR)) {
|
|
729
|
+
warnedBelowHostFloor = true;
|
|
730
|
+
log?.warn?.(`clawchat-plugin-openclaw: host ${runtime.version ?? "unknown"} is below ${HOST_FLOOR}; turns your owner did not start keep the host tools in their schema and rely on the before_tool_call block`);
|
|
731
|
+
}
|
|
732
|
+
if (!loggedHostToolSearch &&
|
|
733
|
+
hostAtLeast(runtime.version, HOST_TOOL_SEARCH_DEFAULT_ON) &&
|
|
734
|
+
hostToolSearchEnabled(cfg)) {
|
|
735
|
+
loggedHostToolSearch = true;
|
|
736
|
+
log?.info?.("clawchat-plugin-openclaw: host tool search is on; turns your owner did not start can still use only the allowlisted tools (set tools.toolSearch.enabled=false to turn tool search off; this affects every turn)");
|
|
737
|
+
}
|
|
738
|
+
if (!params.boundaryWarningIssued && account.nonOwnerHostTools === "approve") {
|
|
739
|
+
log?.warn?.("clawchat-plugin-openclaw: non-owner-host-tools approve is not supported yet; using off");
|
|
740
|
+
}
|
|
723
741
|
log?.info?.(`[${accountId}] clawchat-plugin-openclaw runtime start entered configured=${account.configured} enabled=${account.enabled} hasToken=${Boolean(account.token)} hasUserId=${Boolean(account.userId)} hasOwnerUserId=${Boolean(account.ownerUserId)} websocketUrl=${account.websocketUrl || "(empty)"}`);
|
|
724
742
|
// Freeze one report device_id for this process; reused for the paired report
|
|
725
743
|
// so the backend links both to the same row. Imported from ./client.ts.
|
|
@@ -1488,6 +1506,7 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
1488
1506
|
return;
|
|
1489
1507
|
void startOpenclawClawlingGateway({
|
|
1490
1508
|
...params,
|
|
1509
|
+
boundaryWarningIssued: true,
|
|
1491
1510
|
account: { ...params.account },
|
|
1492
1511
|
// Carry the live refresh token forward (a rotation earlier this lifecycle
|
|
1493
1512
|
// leaves the in-memory `cfg` stale; see refreshTokenOverride).
|
|
@@ -1535,6 +1554,7 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
1535
1554
|
// agent must NOT re-read it — thread the live refresh token forward instead.
|
|
1536
1555
|
await startOpenclawClawlingGateway({
|
|
1537
1556
|
...params,
|
|
1557
|
+
boundaryWarningIssued: true,
|
|
1538
1558
|
account: {
|
|
1539
1559
|
...params.account,
|
|
1540
1560
|
configured: true,
|
|
@@ -2116,15 +2136,25 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
2116
2136
|
// "invalid conversation id" and msghub never acks the send. Proactive owner
|
|
2117
2137
|
// messages must be addressed to the direct conversation recorded at
|
|
2118
2138
|
// activation (parity with the hermes adapter's _owner_direct_chat_id).
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2139
|
+
// One warning per failure class for this lifecycle; never logs ids or SQL.
|
|
2140
|
+
const ownerIdentityWarnings = new Set();
|
|
2141
|
+
const warnOwnerIdentity = (failure) => {
|
|
2142
|
+
if (ownerIdentityWarnings.has(failure))
|
|
2143
|
+
return;
|
|
2144
|
+
ownerIdentityWarnings.add(failure);
|
|
2145
|
+
try {
|
|
2146
|
+
log?.warn?.(`clawchat-plugin-openclaw: ${failure}; owner identity unavailable`);
|
|
2147
|
+
}
|
|
2148
|
+
catch {
|
|
2149
|
+
// Logging cannot change the (fail-closed) answer.
|
|
2150
|
+
}
|
|
2127
2151
|
};
|
|
2152
|
+
const resolveOwnerDirectConversationId = () =>
|
|
2153
|
+
// Shared strict resolver (src/owner-identity.ts): the SQLite activation
|
|
2154
|
+
// row decides; the config `ownerUserId` mirror may be stale or absent and
|
|
2155
|
+
// can neither hide nor invent the owner's direct chat. Unavailable storage
|
|
2156
|
+
// or a cleared/rejected token yields no owner DM (fail closed).
|
|
2157
|
+
resolveOwnerIdentity(store, accountId, account.ownerUserId, warnOwnerIdentity, params.rejectedActivationToken).conversationId;
|
|
2128
2158
|
// R3: the owner's direct conversation is never gated by the dead-chat send
|
|
2129
2159
|
// guard. If it were, the very message explaining why the agent went dark
|
|
2130
2160
|
// (skill-update notices, auth/logout notices, group owner-attention
|
|
@@ -2836,8 +2866,28 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
2836
2866
|
}
|
|
2837
2867
|
turn = await withCachedGroupSenderNames(turn, memoryRoot);
|
|
2838
2868
|
const conversationTarget = `${CHANNEL_ID}:${formatConversationSubject(turn.peer)}`;
|
|
2839
|
-
const turnPrompt = await buildPromptForTurn(turn, memoryRoot);
|
|
2840
2869
|
const senderIsOwner = turn.senderIsOwner === true || turn.senderId === account.ownerUserId;
|
|
2870
|
+
// Owner turn <=> the owner's activation direct chat. Resolved per turn;
|
|
2871
|
+
// any failure leaves it null, which classifies as non-owner (fail closed).
|
|
2872
|
+
let ownerConversationId = null;
|
|
2873
|
+
try {
|
|
2874
|
+
ownerConversationId = resolveOwnerDirectConversationId();
|
|
2875
|
+
}
|
|
2876
|
+
catch {
|
|
2877
|
+
ownerConversationId = null;
|
|
2878
|
+
}
|
|
2879
|
+
const turnClass = classifyChat({
|
|
2880
|
+
kind: turn.peer.kind,
|
|
2881
|
+
chatId: turn.peer.id,
|
|
2882
|
+
ownerDirectConversationId: ownerConversationId,
|
|
2883
|
+
});
|
|
2884
|
+
const narrowHostTools = turnClass === "non-owner";
|
|
2885
|
+
const baseTurnPrompt = await buildPromptForTurn(turn, memoryRoot);
|
|
2886
|
+
const nonOwnerMediaGuidance = "To send an image or file here, include it in your final reply as an http(s) URL; local files cannot be sent from this chat.";
|
|
2887
|
+
const turnPrompt = [
|
|
2888
|
+
...(narrowHostTools ? [getClawChatPlatformPrompt(), nonOwnerMediaGuidance] : []),
|
|
2889
|
+
baseTurnPrompt,
|
|
2890
|
+
].filter(Boolean).join("\n\n");
|
|
2841
2891
|
const ctxPayload = resolveChannelContextBuilder(rt)({
|
|
2842
2892
|
channel: CHANNEL_ID,
|
|
2843
2893
|
accountId: route.accountId ?? accountId,
|
|
@@ -2883,6 +2933,8 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
2883
2933
|
...(isOpenClawTextSlashCommand(turn.rawBody, cfg) && senderIsOwner
|
|
2884
2934
|
? { commands: { authorized: true, source: "text" } }
|
|
2885
2935
|
: {}),
|
|
2936
|
+
// toolPolicy: host 2026.8.1+ (floor)
|
|
2937
|
+
...(narrowHostTools ? { toolPolicy: { allow: [...NON_OWNER_TOOL_ALLOWLIST] } } : {}),
|
|
2886
2938
|
},
|
|
2887
2939
|
...(memoryRoot ? { extra: { memoryRoot } } : {}),
|
|
2888
2940
|
// Deliver the rendered ClawChat per-turn prompt (owner agent_behavior,
|
|
@@ -2934,7 +2986,7 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
2934
2986
|
if (stagedDirectPrompt) {
|
|
2935
2987
|
stageClawChatPromptInjection({
|
|
2936
2988
|
sessionKey: resolvedSessionKey,
|
|
2937
|
-
prompt:
|
|
2989
|
+
prompt: baseTurnPrompt,
|
|
2938
2990
|
});
|
|
2939
2991
|
}
|
|
2940
2992
|
try {
|
|
@@ -2988,6 +3040,7 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
2988
3040
|
// conversation, not `ownerUserId` (a usr_… chat_id is rejected by
|
|
2989
3041
|
// member-backend). Passed as a thunk so it is resolved at send time.
|
|
2990
3042
|
resolveOwnerConversationId: resolveOwnerDirectConversationId,
|
|
3043
|
+
nonOwnerTurn: narrowHostTools,
|
|
2991
3044
|
log,
|
|
2992
3045
|
});
|
|
2993
3046
|
const agentsConfigured = Object.keys(cfg.agents ?? {});
|
|
@@ -2996,7 +3049,14 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
2996
3049
|
const dispatchResult = await rt.reply.withReplyDispatcher({
|
|
2997
3050
|
dispatcher,
|
|
2998
3051
|
onSettled: () => markDispatchIdle(),
|
|
2999
|
-
run: () => runWithTerminalClawChatSendScope(terminalSendScopeId, () => rt.reply.dispatchReplyFromConfig({
|
|
3052
|
+
run: () => runWithTerminalClawChatSendScope(terminalSendScopeId, () => rt.reply.dispatchReplyFromConfig({
|
|
3053
|
+
ctx: ctxPayload,
|
|
3054
|
+
cfg: dispatchCfg,
|
|
3055
|
+
dispatcher,
|
|
3056
|
+
replyOptions: narrowHostTools
|
|
3057
|
+
? { ...replyOptions, toolsAllow: [...NON_OWNER_TOOL_ALLOWLIST] }
|
|
3058
|
+
: replyOptions,
|
|
3059
|
+
})),
|
|
3000
3060
|
});
|
|
3001
3061
|
const counts = dispatchResult?.counts ?? {};
|
|
3002
3062
|
const queuedFinal = Boolean(dispatchResult?.queuedFinal);
|
|
@@ -3516,6 +3576,7 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
3516
3576
|
if (activationAccount.source === "sqlite" && !abortSignal.aborted) {
|
|
3517
3577
|
await startOpenclawClawlingGateway({
|
|
3518
3578
|
...params,
|
|
3579
|
+
boundaryWarningIssued: true,
|
|
3519
3580
|
account: {
|
|
3520
3581
|
...params.account,
|
|
3521
3582
|
configured: false,
|
package/dist/src/storage.js
CHANGED
|
@@ -362,6 +362,40 @@ export class ClawChatStore {
|
|
|
362
362
|
};
|
|
363
363
|
}) ?? null;
|
|
364
364
|
}
|
|
365
|
+
/**
|
|
366
|
+
* Strict owner-identity read for the non-owner turn boundary. Unlike the
|
|
367
|
+
* other getters (which collapse failure into null), this keeps "storage
|
|
368
|
+
* unavailable", "no activation row" and "row" apart — including incomplete
|
|
369
|
+
* rows — because only a proven-absent row may fall back to the config mirror
|
|
370
|
+
* (see `resolveOwnerIdentity`). Nothing is logged here: the caller warns
|
|
371
|
+
* once per failure class, and no SQL or credentials reach the log.
|
|
372
|
+
*/
|
|
373
|
+
lookupActivationIdentity(input) {
|
|
374
|
+
if (!this.ensureInitialized())
|
|
375
|
+
return { status: "unavailable" };
|
|
376
|
+
try {
|
|
377
|
+
const row = this.requireDb()
|
|
378
|
+
.prepare("SELECT owner_user_id, conversation_id, access_token FROM activations WHERE platform = ? AND account_id = ?")
|
|
379
|
+
.get(input.platform, input.accountId);
|
|
380
|
+
if (!row)
|
|
381
|
+
return { status: "absent" };
|
|
382
|
+
const value = (key) => {
|
|
383
|
+
const raw = row[key];
|
|
384
|
+
return typeof raw === "string" ? raw.trim() || null : null;
|
|
385
|
+
};
|
|
386
|
+
return {
|
|
387
|
+
status: "row",
|
|
388
|
+
row: {
|
|
389
|
+
ownerUserId: value("owner_user_id"),
|
|
390
|
+
conversationId: value("conversation_id"),
|
|
391
|
+
accessToken: value("access_token"),
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
catch {
|
|
396
|
+
return { status: "unavailable" };
|
|
397
|
+
}
|
|
398
|
+
}
|
|
365
399
|
claimPendingActivationBootstrap(input) {
|
|
366
400
|
return this.write(() => {
|
|
367
401
|
const now = Date.now();
|
|
@@ -710,13 +744,18 @@ export class ClawChatStore {
|
|
|
710
744
|
* as `'thinking'` are excluded so the @-mention prior-context block never
|
|
711
745
|
* surfaces the agent's intermediate output as conversation history.
|
|
712
746
|
* Uses `idx_clawchat_messages_chat_created` for performance.
|
|
747
|
+
* `ignoreCase` matches `chat_id` case-insensitively (rows keep their stored
|
|
748
|
+
* case) so memory scope can recover a group id's original case from a
|
|
749
|
+
* lowercased session key; it cannot use that index, so callers that already
|
|
750
|
+
* know the exact id leave it off.
|
|
713
751
|
* Returns an empty array if the store is unavailable.
|
|
714
752
|
*/
|
|
715
|
-
listRecentGroupMessages(platform, accountId, chatId, limit) {
|
|
753
|
+
listRecentGroupMessages(platform, accountId, chatId, limit, options = {}) {
|
|
754
|
+
const chatMatch = options.ignoreCase ? "chat_id = ? COLLATE NOCASE" : "chat_id = ?";
|
|
716
755
|
return this.read(() => {
|
|
717
756
|
const rows = this.requireDb()
|
|
718
757
|
.prepare(`SELECT * FROM clawchat_messages
|
|
719
|
-
WHERE platform = ? AND account_id = ? AND
|
|
758
|
+
WHERE platform = ? AND account_id = ? AND ${chatMatch} AND kind = 'message'
|
|
720
759
|
ORDER BY created_at DESC, rowid DESC LIMIT ?`)
|
|
721
760
|
.all(platform, accountId, chatId, limit);
|
|
722
761
|
return rows.reverse(); // oldest-first for prompt order
|
package/dist/src/tools.js
CHANGED
|
@@ -12,6 +12,8 @@ import { sendOpenclawClawlingMentionMessage, sendOpenclawClawlingReaction } from
|
|
|
12
12
|
import { getOpenclawClawlingClient, } from "./runtime.js";
|
|
13
13
|
import { markTerminalClawChatSend, getCurrentTerminalSendScope } from "./terminal-send.js";
|
|
14
14
|
import { editClawChatMemoryBody, readClawChatMemoryFile, resolveClawChatMemoryPath, searchClawChatMemory, writeClawChatMemoryBody, } from "./clawchat-memory.js";
|
|
15
|
+
import { canReadMemory, memoryScopeNote, memoryScopeRefusal, resolveMemoryScope, } from "./memory-scope.js";
|
|
16
|
+
import { clawChatSessionAccountId, NON_OWNER_TOOL_ALLOWLIST } from "./turn-boundary.js";
|
|
15
17
|
import { pullGroupMetadata, pullOwnerMetadata, pullUserMetadata, pushMetadata, updateMetadata, } from "./clawchat-metadata.js";
|
|
16
18
|
import { ClawchatGetAccountProfileSchema, ClawchatGetConversationSchema, ClawchatGetDirectConversationSchema, ClawchatGetMomentSchema, ClawchatGetUserProfileSchema, ClawchatLeaveGroupSchema, ClawchatAddGroupMemberSchema, 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, ClawchatOrchListAgentsSchema, ClawchatOrchGetAgentSchema, ClawchatOrchSetAgentBehaviorSchema, ClawchatOrchListGroupsSchema, ClawchatOrchGetGroupSchema, ClawchatOrchSetGroupPromptSchema, ClawchatOrchCreateGroupSchema, ClawchatOrchGroupMemberSchema, ClawchatOrchSetGroupAgentSettingsSchema, ClawchatOrchCreateConnectCodeSchema, ClawchatOrchGetConnectCodeSchema, } from "./tools-schema.js";
|
|
17
19
|
const MAX_UPLOAD_BYTES = 20 * 1024 * 1024;
|
|
@@ -123,8 +125,9 @@ const MEMORY_READ_MAX_LIMIT = 100_000;
|
|
|
123
125
|
function toolDescription(...parts) {
|
|
124
126
|
return `${parts.join("")} ${DIRECT_TOOL_GUARD}`;
|
|
125
127
|
}
|
|
128
|
+
const MEMORY_SCOPE_DESCRIPTION = "In a non-owner direct chat only that person's note is readable; in a group only that group's note and its members' notes are readable. owner.md is readable only in the owner's direct chat. Replace, edit and metadata operations require a readable target; append may add facts to any target without revealing existing content.";
|
|
126
129
|
function memoryToolDescription(...parts) {
|
|
127
|
-
return parts.join("")
|
|
130
|
+
return `${parts.join("")} ${MEMORY_SCOPE_DESCRIPTION}`;
|
|
128
131
|
}
|
|
129
132
|
function inferMimeFromPath(filePath) {
|
|
130
133
|
const ext = path.extname(filePath).toLowerCase();
|
|
@@ -241,6 +244,31 @@ export function resolveOpenclawClawlingToolAccountId(ctx) {
|
|
|
241
244
|
}
|
|
242
245
|
return null;
|
|
243
246
|
}
|
|
247
|
+
// Names this plugin registered through its wrapper below. The host-tool guard
|
|
248
|
+
// lets an allowlisted `clawchat_*` name through only when it is in this set,
|
|
249
|
+
// so another plugin's tool under a lookalike name is not trusted by prefix.
|
|
250
|
+
const registeredClawChatToolNames = new Set();
|
|
251
|
+
/** Snapshot of the tool names this plugin registered; callers cannot mutate the record. */
|
|
252
|
+
export function getRegisteredClawChatToolNames() {
|
|
253
|
+
return new Set(registeredClawChatToolNames);
|
|
254
|
+
}
|
|
255
|
+
// Host tool search (OpenClaw 2026.9.6+) hides every tool not marked
|
|
256
|
+
// `catalogMode: "direct-only"` behind tool_search/tool_call, whose hook ctx has
|
|
257
|
+
// no requester. The allowlisted tools stay directly callable so non-owner
|
|
258
|
+
// turns keep the full hook ctx; older hosts ignore the field.
|
|
259
|
+
function markDirectOnly(tool) {
|
|
260
|
+
return Object.assign(tool, { catalogMode: "direct-only" });
|
|
261
|
+
}
|
|
262
|
+
function directOnlyTool(tool) {
|
|
263
|
+
if (typeof tool !== "function")
|
|
264
|
+
return markDirectOnly(tool);
|
|
265
|
+
return (ctx) => {
|
|
266
|
+
const resolved = tool(ctx);
|
|
267
|
+
if (Array.isArray(resolved))
|
|
268
|
+
return resolved.map(markDirectOnly);
|
|
269
|
+
return resolved ? markDirectOnly(resolved) : resolved;
|
|
270
|
+
};
|
|
271
|
+
}
|
|
244
272
|
export function registerOpenclawClawlingTools(api, options = {}) {
|
|
245
273
|
if (!api.config) {
|
|
246
274
|
api.logger.debug?.("clawchat-plugin-openclaw: api.config missing; skipping tool registration");
|
|
@@ -250,9 +278,14 @@ export function registerOpenclawClawlingTools(api, options = {}) {
|
|
|
250
278
|
// the end is derived from what was actually registered, not hand-maintained.
|
|
251
279
|
const registeredToolNames = [];
|
|
252
280
|
const registerTool = (tool, opts) => {
|
|
253
|
-
|
|
281
|
+
const directOnly = Boolean(opts?.name && NON_OWNER_TOOL_ALLOWLIST.includes(opts.name));
|
|
282
|
+
api.registerTool(directOnly ? directOnlyTool(tool) : tool, opts);
|
|
283
|
+
// Recorded only after registerTool returned without throwing (the SDK
|
|
284
|
+
// returns void, so host-side duplicate rejection is not observable here).
|
|
285
|
+
if (opts?.name) {
|
|
254
286
|
registeredToolNames.push(opts.name);
|
|
255
|
-
|
|
287
|
+
registeredClawChatToolNames.add(opts.name);
|
|
288
|
+
}
|
|
256
289
|
};
|
|
257
290
|
// Re-resolve at call time so config reloads pick up new tokens / baseUrl.
|
|
258
291
|
function resolveCurrent(accountId) {
|
|
@@ -452,6 +485,49 @@ export function registerOpenclawClawlingTools(api, options = {}) {
|
|
|
452
485
|
function isUnsafeMemoryPathError(message) {
|
|
453
486
|
return /Unsafe clawchat memory|Resolved clawchat memory path is outside root/i.test(message);
|
|
454
487
|
}
|
|
488
|
+
// One warning per failure class per registration; never tool arguments.
|
|
489
|
+
const scopeWarnings = new Set();
|
|
490
|
+
function warnScopeOnce(failure) {
|
|
491
|
+
if (scopeWarnings.has(failure))
|
|
492
|
+
return;
|
|
493
|
+
scopeWarnings.add(failure);
|
|
494
|
+
try {
|
|
495
|
+
api.logger.warn(`clawchat-plugin-openclaw: memory scope ${failure}; no additional notes readable`);
|
|
496
|
+
}
|
|
497
|
+
catch {
|
|
498
|
+
// Logging must not widen or break the (restrictive) scope.
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Scope of the conversation behind this call, resolved fresh on every call
|
|
503
|
+
* (activation and group membership can change between calls). The account
|
|
504
|
+
* comes from the direct session key and/or the trusted tool context; a
|
|
505
|
+
* missing or contradictory account reads nothing.
|
|
506
|
+
*/
|
|
507
|
+
async function memoryScopeFor(ctx, root) {
|
|
508
|
+
const sessionAccount = clawChatSessionAccountId(ctx.sessionKey) ?? null;
|
|
509
|
+
const contextAccount = resolveOpenclawClawlingToolAccountId(ctx);
|
|
510
|
+
if (sessionAccount && contextAccount && sessionAccount !== contextAccount) {
|
|
511
|
+
warnScopeOnce("account-mismatch");
|
|
512
|
+
return { kind: "unknown", chatId: "", members: new Set() };
|
|
513
|
+
}
|
|
514
|
+
const accountId = sessionAccount ?? contextAccount;
|
|
515
|
+
if (!accountId)
|
|
516
|
+
return { kind: "unknown", chatId: "", members: new Set() };
|
|
517
|
+
const delivery = ctx.deliveryContext;
|
|
518
|
+
return resolveMemoryScope({
|
|
519
|
+
root,
|
|
520
|
+
ctx: {
|
|
521
|
+
sessionKey: ctx.sessionKey,
|
|
522
|
+
requesterSenderId: ctx.requesterSenderId,
|
|
523
|
+
deliveryContext: delivery?.channel === CHANNEL_ID ? { to: delivery.to } : undefined,
|
|
524
|
+
},
|
|
525
|
+
accountId,
|
|
526
|
+
configOwnerUserId: resolveCurrent(accountId).ownerUserId,
|
|
527
|
+
store: resolveStore(),
|
|
528
|
+
warn: warnScopeOnce,
|
|
529
|
+
});
|
|
530
|
+
}
|
|
455
531
|
function memoryToolError(err) {
|
|
456
532
|
if (err instanceof ClawlingApiError)
|
|
457
533
|
return apiError(err);
|
|
@@ -478,11 +554,14 @@ export function registerOpenclawClawlingTools(api, options = {}) {
|
|
|
478
554
|
}
|
|
479
555
|
try {
|
|
480
556
|
const p = params;
|
|
481
|
-
|
|
557
|
+
const scope = await memoryScopeFor(ctx, root);
|
|
558
|
+
const result = await searchClawChatMemory(root, {
|
|
482
559
|
query: p.query,
|
|
483
560
|
targetTypes: p.targetTypes,
|
|
484
561
|
maxResults: p.maxResults,
|
|
485
|
-
}));
|
|
562
|
+
}, { readable: (target) => canReadMemory(scope, target) });
|
|
563
|
+
const scopeNote = memoryScopeNote(scope);
|
|
564
|
+
return jsonResponse({ ...result, ...(scopeNote ? { scopeNote } : {}) });
|
|
486
565
|
}
|
|
487
566
|
catch (err) {
|
|
488
567
|
return memoryToolError(err);
|
|
@@ -507,8 +586,12 @@ export function registerOpenclawClawlingTools(api, options = {}) {
|
|
|
507
586
|
const targetError = validateTarget(root, p);
|
|
508
587
|
if (targetError)
|
|
509
588
|
return memoryToolError(new Error(targetError));
|
|
589
|
+
const target = targetFromParams(p);
|
|
590
|
+
const scope = await memoryScopeFor(ctx, root);
|
|
591
|
+
if (!canReadMemory(scope, target))
|
|
592
|
+
return jsonResponse(memoryScopeRefusal(scope, target));
|
|
510
593
|
const { offset, limit } = normalizeReadBounds(p);
|
|
511
|
-
const file = await readClawChatMemoryFile(root,
|
|
594
|
+
const file = await readClawChatMemoryFile(root, target);
|
|
512
595
|
if (!file.exists) {
|
|
513
596
|
return jsonResponse({
|
|
514
597
|
exists: false,
|
|
@@ -553,15 +636,26 @@ export function registerOpenclawClawlingTools(api, options = {}) {
|
|
|
553
636
|
if (!root) {
|
|
554
637
|
return validationError("clawchat-plugin-openclaw: OpenClaw workspaceDir is required");
|
|
555
638
|
}
|
|
639
|
+
const p = params;
|
|
640
|
+
let readable = false;
|
|
556
641
|
try {
|
|
557
|
-
const p = params;
|
|
558
642
|
const targetError = validateTarget(root, p);
|
|
559
643
|
if (targetError)
|
|
560
644
|
return memoryToolError(new Error(targetError));
|
|
561
|
-
|
|
645
|
+
const target = targetFromParams(p);
|
|
646
|
+
const scope = await memoryScopeFor(ctx, root);
|
|
647
|
+
readable = canReadMemory(scope, target);
|
|
648
|
+
// Replacing wipes what is there; append may go to any note.
|
|
649
|
+
if (p.mode !== "append" && !readable)
|
|
650
|
+
return jsonResponse(memoryScopeRefusal(scope, target));
|
|
651
|
+
await writeClawChatMemoryBody(root, target, p.mode, p.content);
|
|
562
652
|
return jsonResponse({ ok: true, targetType: p.targetType, targetId: p.targetId, mode: p.mode });
|
|
563
653
|
}
|
|
564
654
|
catch (err) {
|
|
655
|
+
// An unreadable note's failure text could reveal its state.
|
|
656
|
+
if (p.mode === "append" && !readable) {
|
|
657
|
+
return jsonResponse({ error: "memory_write_failed", message: "The memory append could not be completed." });
|
|
658
|
+
}
|
|
565
659
|
return memoryToolError(err);
|
|
566
660
|
}
|
|
567
661
|
},
|
|
@@ -586,7 +680,11 @@ export function registerOpenclawClawlingTools(api, options = {}) {
|
|
|
586
680
|
const targetError = validateTarget(root, p);
|
|
587
681
|
if (targetError)
|
|
588
682
|
return memoryToolError(new Error(targetError));
|
|
589
|
-
|
|
683
|
+
const target = targetFromParams(p);
|
|
684
|
+
const scope = await memoryScopeFor(ctx, root);
|
|
685
|
+
if (!canReadMemory(scope, target))
|
|
686
|
+
return jsonResponse(memoryScopeRefusal(scope, target));
|
|
687
|
+
await editClawChatMemoryBody(root, target, p.oldText, p.newText);
|
|
590
688
|
return jsonResponse({ ok: true, targetType: p.targetType, targetId: p.targetId });
|
|
591
689
|
}
|
|
592
690
|
catch (err) {
|
|
@@ -612,14 +710,18 @@ export function registerOpenclawClawlingTools(api, options = {}) {
|
|
|
612
710
|
if (!root) {
|
|
613
711
|
return validationError("clawchat-plugin-openclaw: OpenClaw workspaceDir is required");
|
|
614
712
|
}
|
|
615
|
-
const built = buildClient(accountId);
|
|
616
|
-
if (!built.ok)
|
|
617
|
-
return built.error;
|
|
618
713
|
try {
|
|
619
714
|
const p = params;
|
|
620
715
|
const targetError = validateTarget(root, p);
|
|
621
716
|
if (targetError)
|
|
622
717
|
return memoryToolError(new Error(targetError));
|
|
718
|
+
const target = targetFromParams(p);
|
|
719
|
+
const scope = await memoryScopeFor(ctx, root);
|
|
720
|
+
if (!canReadMemory(scope, target))
|
|
721
|
+
return jsonResponse(memoryScopeRefusal(scope, target));
|
|
722
|
+
const built = buildClient(accountId);
|
|
723
|
+
if (!built.ok)
|
|
724
|
+
return built.error;
|
|
623
725
|
const account = resolveCurrent(accountId);
|
|
624
726
|
if (p.direction === "push") {
|
|
625
727
|
if (!Array.isArray(p.fields) || p.fields.length === 0) {
|
|
@@ -683,9 +785,6 @@ export function registerOpenclawClawlingTools(api, options = {}) {
|
|
|
683
785
|
if (!root) {
|
|
684
786
|
return validationError("clawchat-plugin-openclaw: OpenClaw workspaceDir is required");
|
|
685
787
|
}
|
|
686
|
-
const built = buildClient(accountId);
|
|
687
|
-
if (!built.ok)
|
|
688
|
-
return built.error;
|
|
689
788
|
try {
|
|
690
789
|
const p = params;
|
|
691
790
|
const targetError = validateTarget(root, p);
|
|
@@ -694,6 +793,13 @@ export function registerOpenclawClawlingTools(api, options = {}) {
|
|
|
694
793
|
const patchError = validateMetadataUpdatePatch(p);
|
|
695
794
|
if (patchError)
|
|
696
795
|
return validationError(patchError);
|
|
796
|
+
const target = targetFromParams(p);
|
|
797
|
+
const scope = await memoryScopeFor(ctx, root);
|
|
798
|
+
if (!canReadMemory(scope, target))
|
|
799
|
+
return jsonResponse(memoryScopeRefusal(scope, target));
|
|
800
|
+
const built = buildClient(accountId);
|
|
801
|
+
if (!built.ok)
|
|
802
|
+
return built.error;
|
|
697
803
|
const account = resolveCurrent(accountId);
|
|
698
804
|
if (p.targetType === "owner" && !account.agentId) {
|
|
699
805
|
return configError("clawchat-plugin-openclaw: agentId is required");
|
|
@@ -1076,7 +1182,7 @@ export function registerOpenclawClawlingTools(api, options = {}) {
|
|
|
1076
1182
|
label: "React to ClawChat Message",
|
|
1077
1183
|
description: toolDescription("React to a ClawChat message with a single quick emoji (the bubble long-press reaction), instead of sending a text message. " +
|
|
1078
1184
|
"TRIGGER - invoke when a short acknowledgement or emotional beat (agreement, thanks, laughter, celebration, sympathy) reads more naturally as an emoji on the message than as a sentence, e.g. a simple 👍 to \"done\" or ❤️ to good news. " +
|
|
1079
|
-
"
|
|
1185
|
+
"In the owner's direct chat, omit targetMessageId and chatId to react to the message that triggered the current turn; pass targetMessageId to react to an earlier message. In group chats and other direct chats, always pass this chat's chatId. " +
|
|
1080
1186
|
"Set remove:true to take back your reaction. " +
|
|
1081
1187
|
"Prefer the quick set 👍 ❤️ 😂 😮 😢 🙏 🎉 👏 🔥 😍 🤔. " +
|
|
1082
1188
|
"When a reaction is all you want to send, do not also send a normal text reply."),
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Who started this turn? The owner's turns are exactly the ones in the owner's
|
|
2
|
+
// activation direct chat (whoever the sender is — plugin-started turns there
|
|
3
|
+
// count). Every group turn and every other direct chat is someone else's.
|
|
4
|
+
// Anything we cannot place is treated as someone else's (fail closed).
|
|
5
|
+
import { CHANNEL_ID, normalizeOpenclawClawlingAccountId } from "./config.js";
|
|
6
|
+
/** Tools a turn the owner did not start may still see and call. */
|
|
7
|
+
export const NON_OWNER_TOOL_ALLOWLIST = Object.freeze([
|
|
8
|
+
"clawchat_memory_search", "clawchat_memory_read", "clawchat_memory_write", "clawchat_memory_edit",
|
|
9
|
+
"clawchat_mention_message", "clawchat_react_message", "clawchat_get_user_profile", "clawchat_metadata_sync",
|
|
10
|
+
"web_search",
|
|
11
|
+
]);
|
|
12
|
+
const norm = (v) => (typeof v === "string" ? v.trim().toLowerCase() : "");
|
|
13
|
+
/**
|
|
14
|
+
* Host session keys (dmScope per-account-channel-peer):
|
|
15
|
+
* agent:<agent>:<channel>:<account>:direct:<chat>
|
|
16
|
+
* agent:<agent>:<channel>:group:<chat>
|
|
17
|
+
*/
|
|
18
|
+
export function parseClawChatSessionKey(sessionKey) {
|
|
19
|
+
const parts = norm(sessionKey).split(":");
|
|
20
|
+
if (parts.length < 5 || parts[0] !== "agent" || parts[2] !== CHANNEL_ID)
|
|
21
|
+
return null;
|
|
22
|
+
if (parts[3] === "group" && parts.length === 5 && parts[4])
|
|
23
|
+
return { kind: "group", chatId: parts[4] };
|
|
24
|
+
if (parts[4] === "direct" && parts.length === 6 && parts[5])
|
|
25
|
+
return { kind: "direct", chatId: parts[5] };
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Account id carried by a ClawChat direct session key, normalized exactly once
|
|
30
|
+
* with the plugin's account-id normalizer. Group keys carry no account
|
|
31
|
+
* segment, and a blank segment is missing (never silently "default").
|
|
32
|
+
*/
|
|
33
|
+
export function clawChatSessionAccountId(sessionKey) {
|
|
34
|
+
if (parseClawChatSessionKey(sessionKey)?.kind !== "direct")
|
|
35
|
+
return undefined;
|
|
36
|
+
const segment = (sessionKey ?? "").trim().split(":")[3]?.trim();
|
|
37
|
+
return segment ? normalizeOpenclawClawlingAccountId(segment) : undefined;
|
|
38
|
+
}
|
|
39
|
+
export function classifyChat(input) {
|
|
40
|
+
const owner = norm(input.ownerDirectConversationId);
|
|
41
|
+
const chat = norm(input.chatId);
|
|
42
|
+
if (input.kind !== "direct" || !owner || !chat)
|
|
43
|
+
return "non-owner";
|
|
44
|
+
return chat === owner ? "owner" : "non-owner";
|
|
45
|
+
}
|
|
46
|
+
export function classifyTurn(input) {
|
|
47
|
+
const parsed = parseClawChatSessionKey(input.sessionKey);
|
|
48
|
+
if (!parsed)
|
|
49
|
+
return "non-owner";
|
|
50
|
+
return classifyChat({ ...parsed, ownerDirectConversationId: input.ownerDirectConversationId });
|
|
51
|
+
}
|
package/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
|
3
3
|
import { openclawClawlingPlugin } from "./src/channel.ts";
|
|
4
4
|
import { registerOpenclawClawlingCommands } from "./src/commands.ts";
|
|
5
5
|
import { openclawClawlingConfigSchema } from "./src/config.ts";
|
|
6
|
+
import { registerHostToolsGuard } from "./src/host-tools-guard.ts";
|
|
6
7
|
import {
|
|
7
8
|
registerClawChatPromptInjection,
|
|
8
9
|
type ClawChatPromptInjectionApi,
|
|
@@ -20,6 +21,9 @@ const channelEntry = defineChannelPluginEntry({
|
|
|
20
21
|
registerFull(api) {
|
|
21
22
|
registerClawChatPromptInjection(api as unknown as ClawChatPromptInjectionApi);
|
|
22
23
|
registerOpenclawClawlingTools(api);
|
|
24
|
+
// Fail-closed host-tool guard for turns the owner did not start. After the
|
|
25
|
+
// tools so their names are recorded; not gated by conversation access.
|
|
26
|
+
registerHostToolsGuard(api);
|
|
23
27
|
// NOTE: the legacy-rename config migration is intentionally NOT registered
|
|
24
28
|
// here. The host's setup-migration runner only loads a plugin's SETUP source
|
|
25
29
|
// (`openclaw.setupEntry`/`runtimeSetupEntry` → setup-entry.ts) and calls its
|