mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 08:21:26 +00:00
feat: add sessions tools and send policy
This commit is contained in:
@@ -107,6 +107,39 @@ describe("trigger handling", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("allows owner to set send policy", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const cfg = {
|
||||
agent: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "clawd"),
|
||||
},
|
||||
whatsapp: {
|
||||
allowFrom: ["+1000"],
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{
|
||||
Body: "/send off",
|
||||
From: "+1000",
|
||||
To: "+2000",
|
||||
Surface: "whatsapp",
|
||||
SenderE164: "+1000",
|
||||
},
|
||||
{},
|
||||
cfg,
|
||||
);
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text).toContain("Send policy set to off");
|
||||
|
||||
const storeRaw = await fs.readFile(cfg.session.store, "utf-8");
|
||||
const store = JSON.parse(storeRaw) as Record<string, { sendPolicy?: string }>;
|
||||
expect(store.main?.sendPolicy).toBe("deny");
|
||||
});
|
||||
});
|
||||
|
||||
it("returns a context overflow fallback when the embedded agent throws", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockRejectedValue(
|
||||
|
||||
@@ -55,6 +55,7 @@ import {
|
||||
} from "../infra/system-events.js";
|
||||
import { clearCommandLane, getQueueSize } from "../process/command-queue.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { resolveSendPolicy } from "../sessions/send-policy.js";
|
||||
import { normalizeE164 } from "../utils.js";
|
||||
import { resolveHeartbeatSeconds } from "../web/reconnect.js";
|
||||
import { getWebAuthAgeMs, webAuthExists } from "../web/session.js";
|
||||
@@ -63,6 +64,7 @@ import {
|
||||
normalizeGroupActivation,
|
||||
parseActivationCommand,
|
||||
} from "./group-activation.js";
|
||||
import { parseSendPolicyCommand } from "./send-policy.js";
|
||||
import { stripHeartbeatToken } from "./heartbeat.js";
|
||||
import { extractModelDirective } from "./model.js";
|
||||
import { buildStatusMessage } from "./status.js";
|
||||
@@ -986,6 +988,7 @@ export async function getReplyFromConfig(
|
||||
verboseLevel: persistedVerbose ?? baseEntry?.verboseLevel,
|
||||
modelOverride: persistedModelOverride ?? baseEntry?.modelOverride,
|
||||
providerOverride: persistedProviderOverride ?? baseEntry?.providerOverride,
|
||||
sendPolicy: baseEntry?.sendPolicy,
|
||||
queueMode: baseEntry?.queueMode,
|
||||
queueDebounceMs: baseEntry?.queueDebounceMs,
|
||||
queueCap: baseEntry?.queueCap,
|
||||
@@ -1587,6 +1590,7 @@ export async function getReplyFromConfig(
|
||||
? stripMentions(rawBodyNormalized, ctx, cfg)
|
||||
: rawBodyNormalized;
|
||||
const activationCommand = parseActivationCommand(commandBodyNormalized);
|
||||
const sendPolicyCommand = parseSendPolicyCommand(commandBodyNormalized);
|
||||
const senderE164 = normalizeE164(ctx.SenderE164 ?? "");
|
||||
const ownerCandidates = isWhatsAppSurface
|
||||
? (allowFrom ?? []).filter((entry) => entry && entry !== "*")
|
||||
@@ -1633,6 +1637,38 @@ export async function getReplyFromConfig(
|
||||
};
|
||||
}
|
||||
|
||||
if (sendPolicyCommand.hasCommand) {
|
||||
if (!isOwnerSender) {
|
||||
logVerbose(
|
||||
`Ignoring /send from non-owner: ${senderE164 || "<unknown>"}`,
|
||||
);
|
||||
cleanupTyping();
|
||||
return undefined;
|
||||
}
|
||||
if (!sendPolicyCommand.mode) {
|
||||
cleanupTyping();
|
||||
return { text: "⚙️ Usage: /send on|off|inherit" };
|
||||
}
|
||||
if (sessionEntry && sessionStore && sessionKey) {
|
||||
if (sendPolicyCommand.mode === "inherit") {
|
||||
delete sessionEntry.sendPolicy;
|
||||
} else {
|
||||
sessionEntry.sendPolicy = sendPolicyCommand.mode;
|
||||
}
|
||||
sessionEntry.updatedAt = Date.now();
|
||||
sessionStore[sessionKey] = sessionEntry;
|
||||
await saveSessionStore(storePath, sessionStore);
|
||||
}
|
||||
cleanupTyping();
|
||||
const label =
|
||||
sendPolicyCommand.mode === "inherit"
|
||||
? "inherit"
|
||||
: sendPolicyCommand.mode === "allow"
|
||||
? "on"
|
||||
: "off";
|
||||
return { text: `⚙️ Send policy set to ${label}.` };
|
||||
}
|
||||
|
||||
if (
|
||||
commandBodyNormalized === "/restart" ||
|
||||
commandBodyNormalized === "restart" ||
|
||||
@@ -1710,6 +1746,21 @@ export async function getReplyFromConfig(
|
||||
return { text: "⚙️ Agent was aborted." };
|
||||
}
|
||||
|
||||
const sendPolicy = resolveSendPolicy({
|
||||
cfg,
|
||||
entry: sessionEntry,
|
||||
sessionKey,
|
||||
surface: sessionEntry?.surface ?? surface,
|
||||
chatType: sessionEntry?.chatType,
|
||||
});
|
||||
if (sendPolicy === "deny") {
|
||||
logVerbose(
|
||||
`Send blocked by policy for session ${sessionKey ?? "unknown"}`,
|
||||
);
|
||||
cleanupTyping();
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const isFirstTurnInSession = isNewSession || !systemSent;
|
||||
const isGroupChat = sessionCtx.ChatType === "group";
|
||||
const wasMentioned = ctx.WasMentioned === true;
|
||||
|
||||
29
src/auto-reply/send-policy.ts
Normal file
29
src/auto-reply/send-policy.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export type SendPolicyOverride = "allow" | "deny";
|
||||
|
||||
export function normalizeSendPolicyOverride(
|
||||
raw?: string | null,
|
||||
): SendPolicyOverride | undefined {
|
||||
const value = raw?.trim().toLowerCase();
|
||||
if (!value) return undefined;
|
||||
if (value === "allow" || value === "on") return "allow";
|
||||
if (value === "deny" || value === "off") return "deny";
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function parseSendPolicyCommand(raw?: string): {
|
||||
hasCommand: boolean;
|
||||
mode?: SendPolicyOverride | "inherit";
|
||||
} {
|
||||
if (!raw) return { hasCommand: false };
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) return { hasCommand: false };
|
||||
const match = trimmed.match(/^\/?send\b(?:\s+([a-zA-Z]+))?/i);
|
||||
if (!match) return { hasCommand: false };
|
||||
const token = match[1]?.trim().toLowerCase();
|
||||
if (!token) return { hasCommand: true };
|
||||
if (token === "inherit" || token === "default" || token === "reset") {
|
||||
return { hasCommand: true, mode: "inherit" };
|
||||
}
|
||||
const mode = normalizeSendPolicyOverride(token);
|
||||
return { hasCommand: true, mode };
|
||||
}
|
||||
Reference in New Issue
Block a user