style: run oxfmt
This commit is contained in:
@@ -33,9 +33,7 @@ const processSchema = Type.Object({
|
||||
keys: Type.Optional(
|
||||
Type.Array(Type.String(), { description: "Key tokens to send for send-keys" }),
|
||||
),
|
||||
hex: Type.Optional(
|
||||
Type.Array(Type.String(), { description: "Hex bytes to send for send-keys" }),
|
||||
),
|
||||
hex: Type.Optional(Type.Array(Type.String(), { description: "Hex bytes to send for send-keys" })),
|
||||
literal: Type.Optional(Type.String({ description: "Literal string for send-keys" })),
|
||||
text: Type.Optional(Type.String({ description: "Text to paste for paste" })),
|
||||
bracketed: Type.Optional(Type.Boolean({ description: "Wrap paste in bracketed mode" })),
|
||||
@@ -121,10 +119,7 @@ export function createProcessTool(
|
||||
.sort((a, b) => b.startedAt - a.startedAt)
|
||||
.map((s) => {
|
||||
const label = s.name ? truncateMiddle(s.name, 80) : truncateMiddle(s.command, 120);
|
||||
return `${s.sessionId} ${pad(
|
||||
s.status,
|
||||
9,
|
||||
)} ${formatDuration(s.runtimeMs)} :: ${label}`;
|
||||
return `${s.sessionId} ${pad(s.status, 9)} ${formatDuration(s.runtimeMs)} :: ${label}`;
|
||||
});
|
||||
return {
|
||||
content: [
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import { BRACKETED_PASTE_END, BRACKETED_PASTE_START, encodeKeySequence, encodePaste } from "./pty-keys.js";
|
||||
import {
|
||||
BRACKETED_PASTE_END,
|
||||
BRACKETED_PASTE_START,
|
||||
encodeKeySequence,
|
||||
encodePaste,
|
||||
} from "./pty-keys.js";
|
||||
|
||||
test("encodeKeySequence maps common keys and modifiers", () => {
|
||||
const enter = encodeKeySequence({ keys: ["Enter"] });
|
||||
|
||||
@@ -150,12 +150,12 @@ export function createSessionsListTool(opts?: {
|
||||
: undefined;
|
||||
const deliveryChannel =
|
||||
typeof deliveryContext?.channel === "string" ? deliveryContext.channel : undefined;
|
||||
const deliveryTo =
|
||||
typeof deliveryContext?.to === "string" ? deliveryContext.to : undefined;
|
||||
const deliveryTo = typeof deliveryContext?.to === "string" ? deliveryContext.to : undefined;
|
||||
const deliveryAccountId =
|
||||
typeof deliveryContext?.accountId === "string" ? deliveryContext.accountId : undefined;
|
||||
const lastChannel =
|
||||
deliveryChannel ?? (typeof entry.lastChannel === "string" ? entry.lastChannel : undefined);
|
||||
deliveryChannel ??
|
||||
(typeof entry.lastChannel === "string" ? entry.lastChannel : undefined);
|
||||
const lastAccountId =
|
||||
deliveryAccountId ??
|
||||
(typeof entry.lastAccountId === "string" ? entry.lastAccountId : undefined);
|
||||
|
||||
@@ -297,9 +297,7 @@ export async function runPreparedReply(
|
||||
abortKey: command.abortKey,
|
||||
messageId: sessionCtx.MessageSid,
|
||||
});
|
||||
const isGroupSession =
|
||||
sessionEntry?.chatType === "group" ||
|
||||
sessionEntry?.chatType === "channel";
|
||||
const isGroupSession = sessionEntry?.chatType === "group" || sessionEntry?.chatType === "channel";
|
||||
const isMainSession = !isGroupSession && sessionKey === normalizeMainKey(sessionCfg?.mainKey);
|
||||
prefixedBodyBase = await prependSystemEvents({
|
||||
cfg,
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
applyQueueDropPolicy,
|
||||
shouldSkipQueueItem,
|
||||
} from "../../../utils/queue-helpers.js";
|
||||
import { applyQueueDropPolicy, shouldSkipQueueItem } from "../../../utils/queue-helpers.js";
|
||||
import { FOLLOWUP_QUEUES, getFollowupQueue } from "./state.js";
|
||||
import type { FollowupRun, QueueDedupeMode, QueueSettings } from "./types.js";
|
||||
|
||||
|
||||
@@ -193,11 +193,9 @@ export async function initSessionState(params: {
|
||||
|
||||
const baseEntry = !isNewSession && freshEntry ? entry : undefined;
|
||||
// Track the originating channel/to for announce routing (subagent announce-back).
|
||||
const lastChannelRaw =
|
||||
(ctx.OriginatingChannel as string | undefined) || baseEntry?.lastChannel;
|
||||
const lastChannelRaw = (ctx.OriginatingChannel as string | undefined) || baseEntry?.lastChannel;
|
||||
const lastToRaw = (ctx.OriginatingTo as string | undefined) || ctx.To || baseEntry?.lastTo;
|
||||
const lastAccountIdRaw =
|
||||
(ctx.AccountId as string | undefined) || baseEntry?.lastAccountId;
|
||||
const lastAccountIdRaw = (ctx.AccountId as string | undefined) || baseEntry?.lastAccountId;
|
||||
const deliveryFields = normalizeSessionDeliveryFields({
|
||||
deliveryContext: {
|
||||
channel: lastChannelRaw,
|
||||
|
||||
@@ -70,4 +70,3 @@ describe("resolveCommandAuthorizedFromAuthorizers", () => {
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -21,4 +21,3 @@ export function resolveCommandAuthorizedFromAuthorizers(params: {
|
||||
}
|
||||
return authorizers.some((entry) => entry.configured && entry.allowed);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,10 +26,7 @@ import {
|
||||
} from "./config-helpers.js";
|
||||
import { resolveDiscordGroupRequireMention } from "./group-mentions.js";
|
||||
import { formatPairingApproveHint } from "./helpers.js";
|
||||
import {
|
||||
looksLikeDiscordTargetId,
|
||||
normalizeDiscordMessagingTarget,
|
||||
} from "./normalize-target.js";
|
||||
import { looksLikeDiscordTargetId, normalizeDiscordMessagingTarget } from "./normalize-target.js";
|
||||
import { discordOnboardingAdapter } from "./onboarding/discord.js";
|
||||
import { PAIRING_APPROVED_MESSAGE } from "./pairing-message.js";
|
||||
import {
|
||||
|
||||
@@ -18,10 +18,7 @@ import {
|
||||
} from "./config-helpers.js";
|
||||
import { formatPairingApproveHint } from "./helpers.js";
|
||||
import { resolveChannelMediaMaxBytes } from "./media-limits.js";
|
||||
import {
|
||||
looksLikeSignalTargetId,
|
||||
normalizeSignalMessagingTarget,
|
||||
} from "./normalize-target.js";
|
||||
import { looksLikeSignalTargetId, normalizeSignalMessagingTarget } from "./normalize-target.js";
|
||||
import { signalOnboardingAdapter } from "./onboarding/signal.js";
|
||||
import { PAIRING_APPROVED_MESSAGE } from "./pairing-message.js";
|
||||
import {
|
||||
|
||||
@@ -26,10 +26,7 @@ import {
|
||||
} from "./config-helpers.js";
|
||||
import { resolveTelegramGroupRequireMention } from "./group-mentions.js";
|
||||
import { formatPairingApproveHint } from "./helpers.js";
|
||||
import {
|
||||
looksLikeTelegramTargetId,
|
||||
normalizeTelegramMessagingTarget,
|
||||
} from "./normalize-target.js";
|
||||
import { looksLikeTelegramTargetId, normalizeTelegramMessagingTarget } from "./normalize-target.js";
|
||||
import { telegramOnboardingAdapter } from "./onboarding/telegram.js";
|
||||
import { PAIRING_APPROVED_MESSAGE } from "./pairing-message.js";
|
||||
import {
|
||||
|
||||
@@ -26,10 +26,7 @@ import { buildChannelConfigSchema } from "./config-schema.js";
|
||||
import { createWhatsAppLoginTool } from "./agent-tools/whatsapp-login.js";
|
||||
import { resolveWhatsAppGroupRequireMention } from "./group-mentions.js";
|
||||
import { formatPairingApproveHint } from "./helpers.js";
|
||||
import {
|
||||
looksLikeWhatsAppTargetId,
|
||||
normalizeWhatsAppMessagingTarget,
|
||||
} from "./normalize-target.js";
|
||||
import { looksLikeWhatsAppTargetId, normalizeWhatsAppMessagingTarget } from "./normalize-target.js";
|
||||
import { whatsappOnboardingAdapter } from "./onboarding/whatsapp.js";
|
||||
import {
|
||||
applyAccountNameToChannelSection,
|
||||
|
||||
@@ -129,7 +129,9 @@ export function normalizeAnyChannelId(raw?: string | null): ChannelId | null {
|
||||
if (!registry) return null;
|
||||
|
||||
const hit = registry.channels.find((entry) => {
|
||||
const id = String(entry.plugin.id ?? "").trim().toLowerCase();
|
||||
const id = String(entry.plugin.id ?? "")
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
if (id && id === key) return true;
|
||||
return (entry.plugin.meta.aliases ?? []).some((alias) => alias.trim().toLowerCase() === key);
|
||||
});
|
||||
|
||||
@@ -51,7 +51,9 @@ export async function deliverAgentCommandResult(params: {
|
||||
isInternalMessageChannel(deliveryChannel) || Boolean(deliveryPlugin);
|
||||
|
||||
const targetMode =
|
||||
opts.deliveryTargetMode ?? deliveryPlan.deliveryTargetMode ?? (opts.to ? "explicit" : "implicit");
|
||||
opts.deliveryTargetMode ??
|
||||
deliveryPlan.deliveryTargetMode ??
|
||||
(opts.to ? "explicit" : "implicit");
|
||||
const resolvedAccountId = deliveryPlan.resolvedAccountId;
|
||||
const resolved =
|
||||
deliver && isDeliveryChannelKnown && deliveryChannel
|
||||
|
||||
@@ -243,10 +243,7 @@ export function formatMessageCliText(result: MessageActionRunResult): string[] {
|
||||
const results = result.payload.results ?? [];
|
||||
const rows = results.map((entry) => ({
|
||||
Channel: resolveChannelLabel(entry.channel),
|
||||
Target: shortenText(
|
||||
formatTargetDisplay({ channel: entry.channel, target: entry.to }),
|
||||
36,
|
||||
),
|
||||
Target: shortenText(formatTargetDisplay({ channel: entry.channel, target: entry.to }), 36),
|
||||
Status: entry.ok ? "ok" : "error",
|
||||
Error: entry.ok ? "" : shortenText(entry.error ?? "unknown error", 48),
|
||||
}));
|
||||
|
||||
@@ -251,11 +251,7 @@ export const MediaUnderstandingScopeSchema = z
|
||||
.object({
|
||||
channel: z.string().optional(),
|
||||
chatType: z
|
||||
.union([
|
||||
z.literal("direct"),
|
||||
z.literal("group"),
|
||||
z.literal("channel"),
|
||||
])
|
||||
.union([z.literal("direct"), z.literal("group"), z.literal("channel")])
|
||||
.optional(),
|
||||
keyPrefix: z.string().optional(),
|
||||
})
|
||||
|
||||
@@ -39,11 +39,7 @@ export const SessionSchema = z
|
||||
.object({
|
||||
channel: z.string().optional(),
|
||||
chatType: z
|
||||
.union([
|
||||
z.literal("direct"),
|
||||
z.literal("group"),
|
||||
z.literal("channel"),
|
||||
])
|
||||
.union([z.literal("direct"), z.literal("group"), z.literal("channel")])
|
||||
.optional(),
|
||||
keyPrefix: z.string().optional(),
|
||||
})
|
||||
|
||||
@@ -319,38 +319,38 @@ export async function preflightDiscordMessage(
|
||||
surface: "discord",
|
||||
});
|
||||
|
||||
if (!isDirectMessage) {
|
||||
const ownerAllowList = normalizeDiscordAllowList(params.allowFrom, ["discord:", "user:"]);
|
||||
const ownerOk = ownerAllowList
|
||||
? allowListMatches(ownerAllowList, {
|
||||
if (!isDirectMessage) {
|
||||
const ownerAllowList = normalizeDiscordAllowList(params.allowFrom, ["discord:", "user:"]);
|
||||
const ownerOk = ownerAllowList
|
||||
? allowListMatches(ownerAllowList, {
|
||||
id: author.id,
|
||||
name: author.username,
|
||||
tag: formatDiscordUserTag(author),
|
||||
})
|
||||
: false;
|
||||
const channelUsers = channelConfig?.users ?? guildInfo?.users;
|
||||
const usersOk =
|
||||
Array.isArray(channelUsers) && channelUsers.length > 0
|
||||
? resolveDiscordUserAllowed({
|
||||
allowList: channelUsers,
|
||||
userId: author.id,
|
||||
userName: author.username,
|
||||
userTag: formatDiscordUserTag(author),
|
||||
})
|
||||
: false;
|
||||
const useAccessGroups = params.cfg.commands?.useAccessGroups !== false;
|
||||
commandAuthorized = resolveCommandAuthorizedFromAuthorizers({
|
||||
useAccessGroups,
|
||||
authorizers: [
|
||||
{ configured: ownerAllowList != null, allowed: ownerOk },
|
||||
{ configured: Array.isArray(channelUsers) && channelUsers.length > 0, allowed: usersOk },
|
||||
],
|
||||
modeWhenAccessGroupsOff: "configured",
|
||||
});
|
||||
const channelUsers = channelConfig?.users ?? guildInfo?.users;
|
||||
const usersOk =
|
||||
Array.isArray(channelUsers) && channelUsers.length > 0
|
||||
? resolveDiscordUserAllowed({
|
||||
allowList: channelUsers,
|
||||
userId: author.id,
|
||||
userName: author.username,
|
||||
userTag: formatDiscordUserTag(author),
|
||||
})
|
||||
: false;
|
||||
const useAccessGroups = params.cfg.commands?.useAccessGroups !== false;
|
||||
commandAuthorized = resolveCommandAuthorizedFromAuthorizers({
|
||||
useAccessGroups,
|
||||
authorizers: [
|
||||
{ configured: ownerAllowList != null, allowed: ownerOk },
|
||||
{ configured: Array.isArray(channelUsers) && channelUsers.length > 0, allowed: usersOk },
|
||||
],
|
||||
modeWhenAccessGroupsOff: "configured",
|
||||
});
|
||||
|
||||
if (allowTextCommands && hasControlCommand(baseText, params.cfg) && !commandAuthorized) {
|
||||
logVerbose(`Blocked discord control command from unauthorized sender ${author.id}`);
|
||||
return null;
|
||||
if (allowTextCommands && hasControlCommand(baseText, params.cfg) && !commandAuthorized) {
|
||||
logVerbose(`Blocked discord control command from unauthorized sender ${author.id}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -230,19 +230,19 @@ describe("gateway server agent", () => {
|
||||
await server.close();
|
||||
});
|
||||
|
||||
test("agent uses webchat for internal runs when last provider is webchat", async () => {
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-gw-"));
|
||||
testState.sessionStorePath = path.join(dir, "sessions.json");
|
||||
await writeSessionStore({
|
||||
entries: {
|
||||
main: {
|
||||
sessionId: "sess-main-webchat-internal",
|
||||
updatedAt: Date.now(),
|
||||
lastChannel: "webchat",
|
||||
lastTo: "+1555",
|
||||
},
|
||||
test("agent uses webchat for internal runs when last provider is webchat", async () => {
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-gw-"));
|
||||
testState.sessionStorePath = path.join(dir, "sessions.json");
|
||||
await writeSessionStore({
|
||||
entries: {
|
||||
main: {
|
||||
sessionId: "sess-main-webchat-internal",
|
||||
updatedAt: Date.now(),
|
||||
lastChannel: "webchat",
|
||||
lastTo: "+1555",
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const { server, ws } = await startServerWithClient();
|
||||
await connectOk(ws);
|
||||
|
||||
@@ -97,9 +97,8 @@ describe("hooks install (e2e)", () => {
|
||||
expect(installResult.ok).toBe(true);
|
||||
if (!installResult.ok) return;
|
||||
|
||||
const { clearInternalHooks, createInternalHookEvent, triggerInternalHook } = await import(
|
||||
"./internal-hooks.js"
|
||||
);
|
||||
const { clearInternalHooks, createInternalHookEvent, triggerInternalHook } =
|
||||
await import("./internal-hooks.js");
|
||||
const { loadInternalHooks } = await import("./loader.js");
|
||||
|
||||
clearInternalHooks();
|
||||
|
||||
@@ -62,7 +62,7 @@ describe("installHooksFromArchive", () => {
|
||||
"---",
|
||||
"name: zip-hook",
|
||||
"description: Zip hook",
|
||||
"metadata: {\"clawdbot\":{\"events\":[\"command:new\"]}}",
|
||||
'metadata: {"clawdbot":{"events":["command:new"]}}',
|
||||
"---",
|
||||
"",
|
||||
"# Zip Hook",
|
||||
@@ -82,9 +82,7 @@ describe("installHooksFromArchive", () => {
|
||||
expect(result.hookPackId).toBe("zip-hooks");
|
||||
expect(result.hooks).toContain("zip-hook");
|
||||
expect(result.targetDir).toBe(path.join(stateDir, "hooks", "zip-hooks"));
|
||||
expect(
|
||||
fs.existsSync(path.join(result.targetDir, "hooks", "zip-hook", "HOOK.md")),
|
||||
).toBe(true);
|
||||
expect(fs.existsSync(path.join(result.targetDir, "hooks", "zip-hook", "HOOK.md"))).toBe(true);
|
||||
});
|
||||
|
||||
it("installs hook packs from tar archives", async () => {
|
||||
@@ -109,7 +107,7 @@ describe("installHooksFromArchive", () => {
|
||||
"---",
|
||||
"name: tar-hook",
|
||||
"description: Tar hook",
|
||||
"metadata: {\"clawdbot\":{\"events\":[\"command:new\"]}}",
|
||||
'metadata: {"clawdbot":{"events":["command:new"]}}',
|
||||
"---",
|
||||
"",
|
||||
"# Tar Hook",
|
||||
@@ -148,7 +146,7 @@ describe("installHooksFromPath", () => {
|
||||
"---",
|
||||
"name: my-hook",
|
||||
"description: My hook",
|
||||
"metadata: {\"clawdbot\":{\"events\":[\"command:new\"]}}",
|
||||
'metadata: {"clawdbot":{"events":["command:new"]}}',
|
||||
"---",
|
||||
"",
|
||||
"# My Hook",
|
||||
|
||||
@@ -134,7 +134,9 @@ async function installHookPackageFromDir(params: {
|
||||
};
|
||||
}
|
||||
|
||||
const hooksDir = params.hooksDir ? resolveUserPath(params.hooksDir) : path.join(CONFIG_DIR, "hooks");
|
||||
const hooksDir = params.hooksDir
|
||||
? resolveUserPath(params.hooksDir)
|
||||
: path.join(CONFIG_DIR, "hooks");
|
||||
await fs.mkdir(hooksDir, { recursive: true });
|
||||
|
||||
const targetDir = resolveHookInstallDir(hookPackId, hooksDir);
|
||||
@@ -232,7 +234,9 @@ async function installHookFromDir(params: {
|
||||
};
|
||||
}
|
||||
|
||||
const hooksDir = params.hooksDir ? resolveUserPath(params.hooksDir) : path.join(CONFIG_DIR, "hooks");
|
||||
const hooksDir = params.hooksDir
|
||||
? resolveUserPath(params.hooksDir)
|
||||
: path.join(CONFIG_DIR, "hooks");
|
||||
await fs.mkdir(hooksDir, { recursive: true });
|
||||
|
||||
const targetDir = resolveHookInstallDir(hookName, hooksDir);
|
||||
|
||||
@@ -79,9 +79,7 @@ export async function loadInternalHooks(
|
||||
// Register for all events listed in metadata
|
||||
const events = entry.clawdbot?.events ?? [];
|
||||
if (events.length === 0) {
|
||||
console.warn(
|
||||
`Hook warning: Hook '${entry.hook.name}' has no events defined in metadata`,
|
||||
);
|
||||
console.warn(`Hook warning: Hook '${entry.hook.name}' has no events defined in metadata`);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -126,9 +124,7 @@ export async function loadInternalHooks(
|
||||
const handler = mod[exportName];
|
||||
|
||||
if (typeof handler !== "function") {
|
||||
console.error(
|
||||
`Hook error: Handler '${exportName}' from ${modulePath} is not a function`,
|
||||
);
|
||||
console.error(`Hook error: Handler '${exportName}' from ${modulePath} is not a function`);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,11 @@ function resolvePackageHooks(manifest: HookPackageManifest): string[] {
|
||||
return raw.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
|
||||
}
|
||||
|
||||
function loadHookFromDir(params: { hookDir: string; source: string; nameHint?: string }): Hook | null {
|
||||
function loadHookFromDir(params: {
|
||||
hookDir: string;
|
||||
source: string;
|
||||
nameHint?: string;
|
||||
}): Hook | null {
|
||||
const hookMdPath = path.join(params.hookDir, "HOOK.md");
|
||||
if (!fs.existsSync(hookMdPath)) return null;
|
||||
|
||||
|
||||
@@ -61,10 +61,7 @@ export async function withTimeout<T>(
|
||||
}
|
||||
}
|
||||
|
||||
async function extractZip(params: {
|
||||
archivePath: string;
|
||||
destDir: string;
|
||||
}): Promise<void> {
|
||||
async function extractZip(params: { archivePath: string; destDir: string }): Promise<void> {
|
||||
const buffer = await fs.readFile(params.archivePath);
|
||||
const zip = await JSZip.loadAsync(buffer);
|
||||
const entries = Object.values(zip.files);
|
||||
|
||||
@@ -9,7 +9,11 @@ import {
|
||||
normalizeMessageChannel,
|
||||
type GatewayMessageChannel,
|
||||
} from "../../utils/message-channel.js";
|
||||
import { resolveOutboundTarget, resolveSessionDeliveryTarget, type SessionDeliveryTarget } from "./targets.js";
|
||||
import {
|
||||
resolveOutboundTarget,
|
||||
resolveSessionDeliveryTarget,
|
||||
type SessionDeliveryTarget,
|
||||
} from "./targets.js";
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import type { OutboundTargetResolution } from "./targets.js";
|
||||
|
||||
|
||||
@@ -121,9 +121,7 @@ describe("applyMediaUnderstanding", () => {
|
||||
|
||||
expect(result.appliedAudio).toBe(true);
|
||||
expect(ctx.Transcript).toBe("transcribed text");
|
||||
expect(ctx.Body).toBe(
|
||||
"[Audio]\nUser text:\n/capture status\nTranscript:\ntranscribed text",
|
||||
);
|
||||
expect(ctx.Body).toBe("[Audio]\nUser text:\n/capture status\nTranscript:\ntranscribed text");
|
||||
expect(ctx.CommandBody).toBe("/capture status");
|
||||
expect(ctx.RawBody).toBe("/capture status");
|
||||
expect(ctx.BodyForCommands).toBe("/capture status");
|
||||
|
||||
@@ -78,10 +78,7 @@ export async function applyMediaUnderstanding(params: {
|
||||
}
|
||||
|
||||
if (decisions.length > 0) {
|
||||
ctx.MediaUnderstandingDecisions = [
|
||||
...(ctx.MediaUnderstandingDecisions ?? []),
|
||||
...decisions,
|
||||
];
|
||||
ctx.MediaUnderstandingDecisions = [...(ctx.MediaUnderstandingDecisions ?? []), ...decisions];
|
||||
}
|
||||
|
||||
if (outputs.length > 0) {
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import {
|
||||
resolveEntriesWithActiveFallback,
|
||||
resolveModelEntries,
|
||||
} from "./resolve.js";
|
||||
import { resolveEntriesWithActiveFallback, resolveModelEntries } from "./resolve.js";
|
||||
|
||||
const providerRegistry = new Map([
|
||||
["openai", { capabilities: ["image"] }],
|
||||
|
||||
@@ -100,7 +100,9 @@ function buildModelDecision(params: {
|
||||
|
||||
function formatDecisionSummary(decision: MediaUnderstandingDecision): string {
|
||||
const total = decision.attachments.length;
|
||||
const success = decision.attachments.filter((entry) => entry.chosen?.outcome === "success").length;
|
||||
const success = decision.attachments.filter(
|
||||
(entry) => entry.chosen?.outcome === "success",
|
||||
).length;
|
||||
const chosen = decision.attachments.find((entry) => entry.chosen)?.chosen;
|
||||
const provider = chosen?.provider?.trim();
|
||||
const model = chosen?.model?.trim();
|
||||
@@ -355,7 +357,10 @@ async function runAttachmentEntries(params: {
|
||||
cache: MediaAttachmentCache;
|
||||
entries: MediaUnderstandingModelConfig[];
|
||||
config?: MediaUnderstandingConfig;
|
||||
}): Promise<{ output: MediaUnderstandingOutput | null; attempts: MediaUnderstandingModelDecision[] }> {
|
||||
}): Promise<{
|
||||
output: MediaUnderstandingOutput | null;
|
||||
attempts: MediaUnderstandingModelDecision[];
|
||||
}> {
|
||||
const { entries, capability } = params;
|
||||
const attempts: MediaUnderstandingModelDecision[] = [];
|
||||
for (const entry of entries) {
|
||||
|
||||
@@ -474,10 +474,8 @@ async function collectChannelSecurityFindings(params: {
|
||||
|
||||
if (plugin.id === "discord") {
|
||||
const discordCfg =
|
||||
(account as { config?: Record<string, unknown> } | null)?.config ?? ({} as Record<
|
||||
string,
|
||||
unknown
|
||||
>);
|
||||
(account as { config?: Record<string, unknown> } | null)?.config ??
|
||||
({} as Record<string, unknown>);
|
||||
const nativeEnabled = resolveNativeCommandsEnabled({
|
||||
providerId: "discord",
|
||||
providerSetting: coerceNativeSetting(
|
||||
@@ -516,15 +514,20 @@ async function collectChannelSecurityFindings(params: {
|
||||
normalizeAllowFromList([...dmAllowFrom, ...storeAllowFrom]).length > 0;
|
||||
|
||||
const useAccessGroups = params.cfg.commands?.useAccessGroups !== false;
|
||||
if (!useAccessGroups && groupPolicy !== "disabled" && guildsConfigured && !hasAnyUserAllowlist) {
|
||||
if (
|
||||
!useAccessGroups &&
|
||||
groupPolicy !== "disabled" &&
|
||||
guildsConfigured &&
|
||||
!hasAnyUserAllowlist
|
||||
) {
|
||||
findings.push({
|
||||
checkId: "channels.discord.commands.native.unrestricted",
|
||||
severity: "critical",
|
||||
title: "Discord slash commands are unrestricted",
|
||||
detail:
|
||||
'commands.useAccessGroups=false disables sender allowlists for Discord slash commands unless a per-guild/channel users allowlist is configured; with no users allowlist, any user in allowed guild channels can invoke /… commands.',
|
||||
"commands.useAccessGroups=false disables sender allowlists for Discord slash commands unless a per-guild/channel users allowlist is configured; with no users allowlist, any user in allowed guild channels can invoke /… commands.",
|
||||
remediation:
|
||||
'Set commands.useAccessGroups=true (recommended), or configure channels.discord.guilds.<id>.users (or channels.discord.guilds.<id>.channels.<channel>.users).',
|
||||
"Set commands.useAccessGroups=true (recommended), or configure channels.discord.guilds.<id>.users (or channels.discord.guilds.<id>.channels.<channel>.users).",
|
||||
});
|
||||
} else if (
|
||||
useAccessGroups &&
|
||||
@@ -540,7 +543,7 @@ async function collectChannelSecurityFindings(params: {
|
||||
detail:
|
||||
"Discord slash commands are enabled, but neither an owner allowFrom list nor any per-guild/channel users allowlist is configured; /… commands will be rejected for everyone.",
|
||||
remediation:
|
||||
'Add your user id to channels.discord.dm.allowFrom (or approve yourself via pairing), or configure channels.discord.guilds.<id>.users.',
|
||||
"Add your user id to channels.discord.dm.allowFrom (or approve yourself via pairing), or configure channels.discord.guilds.<id>.users.",
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -567,7 +570,7 @@ async function collectChannelSecurityFindings(params: {
|
||||
const slashCommandEnabled =
|
||||
nativeEnabled ||
|
||||
nativeSkillsEnabled ||
|
||||
((slackCfg.slashCommand as { enabled?: unknown } | undefined)?.enabled === true);
|
||||
(slackCfg.slashCommand as { enabled?: unknown } | undefined)?.enabled === true;
|
||||
if (slashCommandEnabled) {
|
||||
const useAccessGroups = params.cfg.commands?.useAccessGroups !== false;
|
||||
if (!useAccessGroups) {
|
||||
@@ -580,7 +583,8 @@ async function collectChannelSecurityFindings(params: {
|
||||
remediation: "Set commands.useAccessGroups=true (recommended).",
|
||||
});
|
||||
} else {
|
||||
const dmAllowFromRaw = (account as { dm?: { allowFrom?: unknown } } | null)?.dm?.allowFrom;
|
||||
const dmAllowFromRaw = (account as { dm?: { allowFrom?: unknown } } | null)?.dm
|
||||
?.allowFrom;
|
||||
const dmAllowFrom = Array.isArray(dmAllowFromRaw) ? dmAllowFromRaw : [];
|
||||
const storeAllowFrom = await readChannelAllowFromStore("slack").catch(() => []);
|
||||
const ownerAllowFromConfigured =
|
||||
|
||||
@@ -110,8 +110,8 @@ export const registerTelegramNativeCommands = ({
|
||||
|
||||
if (allCommands.length > 0) {
|
||||
bot.api.setMyCommands(allCommands).catch((err) => {
|
||||
runtime.error?.(danger(`telegram setMyCommands failed: ${String(err)}`));
|
||||
});
|
||||
runtime.error?.(danger(`telegram setMyCommands failed: ${String(err)}`));
|
||||
});
|
||||
|
||||
if (typeof (bot as unknown as { command?: unknown }).command !== "function") {
|
||||
logVerbose("telegram: bot.command unavailable; skipping native handlers");
|
||||
|
||||
@@ -19,7 +19,7 @@ export function normalizeDeliveryContext(context?: DeliveryContext): DeliveryCon
|
||||
if (!context) return undefined;
|
||||
const channel =
|
||||
typeof context.channel === "string"
|
||||
? normalizeMessageChannel(context.channel) ?? context.channel.trim()
|
||||
? (normalizeMessageChannel(context.channel) ?? context.channel.trim())
|
||||
: undefined;
|
||||
const to = typeof context.to === "string" ? context.to.trim() : undefined;
|
||||
const accountId = normalizeAccountId(context.accountId);
|
||||
@@ -31,9 +31,7 @@ export function normalizeDeliveryContext(context?: DeliveryContext): DeliveryCon
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeSessionDeliveryFields(
|
||||
source?: DeliveryContextSessionSource,
|
||||
): {
|
||||
export function normalizeSessionDeliveryFields(source?: DeliveryContextSessionSource): {
|
||||
deliveryContext?: DeliveryContext;
|
||||
lastChannel?: string;
|
||||
lastTo?: string;
|
||||
|
||||
@@ -78,9 +78,15 @@ async function resolveWhatsAppCommandAuthorized(params: {
|
||||
}
|
||||
|
||||
const storeAllowFrom = await readChannelAllowFromStore("whatsapp").catch(() => []);
|
||||
const combinedAllowFrom = Array.from(new Set([...(configuredAllowFrom ?? []), ...storeAllowFrom]));
|
||||
const combinedAllowFrom = Array.from(
|
||||
new Set([...(configuredAllowFrom ?? []), ...storeAllowFrom]),
|
||||
);
|
||||
const allowFrom =
|
||||
combinedAllowFrom.length > 0 ? combinedAllowFrom : params.msg.selfE164 ? [params.msg.selfE164] : [];
|
||||
combinedAllowFrom.length > 0
|
||||
? combinedAllowFrom
|
||||
: params.msg.selfE164
|
||||
? [params.msg.selfE164]
|
||||
: [];
|
||||
if (allowFrom.some((v) => String(v).trim() === "*")) return true;
|
||||
return normalizeAllowFromE164(allowFrom).includes(senderE164);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user