style: run oxfmt

This commit is contained in:
Peter Steinberger
2026-01-17 07:59:53 +00:00
parent 5986175268
commit 7cebe7a506
34 changed files with 127 additions and 148 deletions

View File

@@ -33,9 +33,7 @@ const processSchema = Type.Object({
keys: Type.Optional( keys: Type.Optional(
Type.Array(Type.String(), { description: "Key tokens to send for send-keys" }), Type.Array(Type.String(), { description: "Key tokens to send for send-keys" }),
), ),
hex: Type.Optional( hex: Type.Optional(Type.Array(Type.String(), { description: "Hex bytes to send for send-keys" })),
Type.Array(Type.String(), { description: "Hex bytes to send for send-keys" }),
),
literal: Type.Optional(Type.String({ description: "Literal string 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" })), text: Type.Optional(Type.String({ description: "Text to paste for paste" })),
bracketed: Type.Optional(Type.Boolean({ description: "Wrap paste in bracketed mode" })), 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) .sort((a, b) => b.startedAt - a.startedAt)
.map((s) => { .map((s) => {
const label = s.name ? truncateMiddle(s.name, 80) : truncateMiddle(s.command, 120); const label = s.name ? truncateMiddle(s.name, 80) : truncateMiddle(s.command, 120);
return `${s.sessionId} ${pad( return `${s.sessionId} ${pad(s.status, 9)} ${formatDuration(s.runtimeMs)} :: ${label}`;
s.status,
9,
)} ${formatDuration(s.runtimeMs)} :: ${label}`;
}); });
return { return {
content: [ content: [

View File

@@ -1,6 +1,11 @@
import { expect, test } from "vitest"; 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", () => { test("encodeKeySequence maps common keys and modifiers", () => {
const enter = encodeKeySequence({ keys: ["Enter"] }); const enter = encodeKeySequence({ keys: ["Enter"] });

View File

@@ -150,12 +150,12 @@ export function createSessionsListTool(opts?: {
: undefined; : undefined;
const deliveryChannel = const deliveryChannel =
typeof deliveryContext?.channel === "string" ? deliveryContext.channel : undefined; typeof deliveryContext?.channel === "string" ? deliveryContext.channel : undefined;
const deliveryTo = const deliveryTo = typeof deliveryContext?.to === "string" ? deliveryContext.to : undefined;
typeof deliveryContext?.to === "string" ? deliveryContext.to : undefined;
const deliveryAccountId = const deliveryAccountId =
typeof deliveryContext?.accountId === "string" ? deliveryContext.accountId : undefined; typeof deliveryContext?.accountId === "string" ? deliveryContext.accountId : undefined;
const lastChannel = const lastChannel =
deliveryChannel ?? (typeof entry.lastChannel === "string" ? entry.lastChannel : undefined); deliveryChannel ??
(typeof entry.lastChannel === "string" ? entry.lastChannel : undefined);
const lastAccountId = const lastAccountId =
deliveryAccountId ?? deliveryAccountId ??
(typeof entry.lastAccountId === "string" ? entry.lastAccountId : undefined); (typeof entry.lastAccountId === "string" ? entry.lastAccountId : undefined);

View File

@@ -297,9 +297,7 @@ export async function runPreparedReply(
abortKey: command.abortKey, abortKey: command.abortKey,
messageId: sessionCtx.MessageSid, messageId: sessionCtx.MessageSid,
}); });
const isGroupSession = const isGroupSession = sessionEntry?.chatType === "group" || sessionEntry?.chatType === "channel";
sessionEntry?.chatType === "group" ||
sessionEntry?.chatType === "channel";
const isMainSession = !isGroupSession && sessionKey === normalizeMainKey(sessionCfg?.mainKey); const isMainSession = !isGroupSession && sessionKey === normalizeMainKey(sessionCfg?.mainKey);
prefixedBodyBase = await prependSystemEvents({ prefixedBodyBase = await prependSystemEvents({
cfg, cfg,

View File

@@ -1,7 +1,4 @@
import { import { applyQueueDropPolicy, shouldSkipQueueItem } from "../../../utils/queue-helpers.js";
applyQueueDropPolicy,
shouldSkipQueueItem,
} from "../../../utils/queue-helpers.js";
import { FOLLOWUP_QUEUES, getFollowupQueue } from "./state.js"; import { FOLLOWUP_QUEUES, getFollowupQueue } from "./state.js";
import type { FollowupRun, QueueDedupeMode, QueueSettings } from "./types.js"; import type { FollowupRun, QueueDedupeMode, QueueSettings } from "./types.js";

View File

@@ -193,11 +193,9 @@ export async function initSessionState(params: {
const baseEntry = !isNewSession && freshEntry ? entry : undefined; const baseEntry = !isNewSession && freshEntry ? entry : undefined;
// Track the originating channel/to for announce routing (subagent announce-back). // Track the originating channel/to for announce routing (subagent announce-back).
const lastChannelRaw = const lastChannelRaw = (ctx.OriginatingChannel as string | undefined) || baseEntry?.lastChannel;
(ctx.OriginatingChannel as string | undefined) || baseEntry?.lastChannel;
const lastToRaw = (ctx.OriginatingTo as string | undefined) || ctx.To || baseEntry?.lastTo; const lastToRaw = (ctx.OriginatingTo as string | undefined) || ctx.To || baseEntry?.lastTo;
const lastAccountIdRaw = const lastAccountIdRaw = (ctx.AccountId as string | undefined) || baseEntry?.lastAccountId;
(ctx.AccountId as string | undefined) || baseEntry?.lastAccountId;
const deliveryFields = normalizeSessionDeliveryFields({ const deliveryFields = normalizeSessionDeliveryFields({
deliveryContext: { deliveryContext: {
channel: lastChannelRaw, channel: lastChannelRaw,

View File

@@ -70,4 +70,3 @@ describe("resolveCommandAuthorizedFromAuthorizers", () => {
).toBe(true); ).toBe(true);
}); });
}); });

View File

@@ -21,4 +21,3 @@ export function resolveCommandAuthorizedFromAuthorizers(params: {
} }
return authorizers.some((entry) => entry.configured && entry.allowed); return authorizers.some((entry) => entry.configured && entry.allowed);
} }

View File

@@ -26,10 +26,7 @@ import {
} from "./config-helpers.js"; } from "./config-helpers.js";
import { resolveDiscordGroupRequireMention } from "./group-mentions.js"; import { resolveDiscordGroupRequireMention } from "./group-mentions.js";
import { formatPairingApproveHint } from "./helpers.js"; import { formatPairingApproveHint } from "./helpers.js";
import { import { looksLikeDiscordTargetId, normalizeDiscordMessagingTarget } from "./normalize-target.js";
looksLikeDiscordTargetId,
normalizeDiscordMessagingTarget,
} from "./normalize-target.js";
import { discordOnboardingAdapter } from "./onboarding/discord.js"; import { discordOnboardingAdapter } from "./onboarding/discord.js";
import { PAIRING_APPROVED_MESSAGE } from "./pairing-message.js"; import { PAIRING_APPROVED_MESSAGE } from "./pairing-message.js";
import { import {

View File

@@ -18,10 +18,7 @@ import {
} from "./config-helpers.js"; } from "./config-helpers.js";
import { formatPairingApproveHint } from "./helpers.js"; import { formatPairingApproveHint } from "./helpers.js";
import { resolveChannelMediaMaxBytes } from "./media-limits.js"; import { resolveChannelMediaMaxBytes } from "./media-limits.js";
import { import { looksLikeSignalTargetId, normalizeSignalMessagingTarget } from "./normalize-target.js";
looksLikeSignalTargetId,
normalizeSignalMessagingTarget,
} from "./normalize-target.js";
import { signalOnboardingAdapter } from "./onboarding/signal.js"; import { signalOnboardingAdapter } from "./onboarding/signal.js";
import { PAIRING_APPROVED_MESSAGE } from "./pairing-message.js"; import { PAIRING_APPROVED_MESSAGE } from "./pairing-message.js";
import { import {

View File

@@ -26,10 +26,7 @@ import {
} from "./config-helpers.js"; } from "./config-helpers.js";
import { resolveTelegramGroupRequireMention } from "./group-mentions.js"; import { resolveTelegramGroupRequireMention } from "./group-mentions.js";
import { formatPairingApproveHint } from "./helpers.js"; import { formatPairingApproveHint } from "./helpers.js";
import { import { looksLikeTelegramTargetId, normalizeTelegramMessagingTarget } from "./normalize-target.js";
looksLikeTelegramTargetId,
normalizeTelegramMessagingTarget,
} from "./normalize-target.js";
import { telegramOnboardingAdapter } from "./onboarding/telegram.js"; import { telegramOnboardingAdapter } from "./onboarding/telegram.js";
import { PAIRING_APPROVED_MESSAGE } from "./pairing-message.js"; import { PAIRING_APPROVED_MESSAGE } from "./pairing-message.js";
import { import {

View File

@@ -26,10 +26,7 @@ import { buildChannelConfigSchema } from "./config-schema.js";
import { createWhatsAppLoginTool } from "./agent-tools/whatsapp-login.js"; import { createWhatsAppLoginTool } from "./agent-tools/whatsapp-login.js";
import { resolveWhatsAppGroupRequireMention } from "./group-mentions.js"; import { resolveWhatsAppGroupRequireMention } from "./group-mentions.js";
import { formatPairingApproveHint } from "./helpers.js"; import { formatPairingApproveHint } from "./helpers.js";
import { import { looksLikeWhatsAppTargetId, normalizeWhatsAppMessagingTarget } from "./normalize-target.js";
looksLikeWhatsAppTargetId,
normalizeWhatsAppMessagingTarget,
} from "./normalize-target.js";
import { whatsappOnboardingAdapter } from "./onboarding/whatsapp.js"; import { whatsappOnboardingAdapter } from "./onboarding/whatsapp.js";
import { import {
applyAccountNameToChannelSection, applyAccountNameToChannelSection,

View File

@@ -129,7 +129,9 @@ export function normalizeAnyChannelId(raw?: string | null): ChannelId | null {
if (!registry) return null; if (!registry) return null;
const hit = registry.channels.find((entry) => { 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; if (id && id === key) return true;
return (entry.plugin.meta.aliases ?? []).some((alias) => alias.trim().toLowerCase() === key); return (entry.plugin.meta.aliases ?? []).some((alias) => alias.trim().toLowerCase() === key);
}); });

View File

@@ -51,7 +51,9 @@ export async function deliverAgentCommandResult(params: {
isInternalMessageChannel(deliveryChannel) || Boolean(deliveryPlugin); isInternalMessageChannel(deliveryChannel) || Boolean(deliveryPlugin);
const targetMode = const targetMode =
opts.deliveryTargetMode ?? deliveryPlan.deliveryTargetMode ?? (opts.to ? "explicit" : "implicit"); opts.deliveryTargetMode ??
deliveryPlan.deliveryTargetMode ??
(opts.to ? "explicit" : "implicit");
const resolvedAccountId = deliveryPlan.resolvedAccountId; const resolvedAccountId = deliveryPlan.resolvedAccountId;
const resolved = const resolved =
deliver && isDeliveryChannelKnown && deliveryChannel deliver && isDeliveryChannelKnown && deliveryChannel

View File

@@ -243,10 +243,7 @@ export function formatMessageCliText(result: MessageActionRunResult): string[] {
const results = result.payload.results ?? []; const results = result.payload.results ?? [];
const rows = results.map((entry) => ({ const rows = results.map((entry) => ({
Channel: resolveChannelLabel(entry.channel), Channel: resolveChannelLabel(entry.channel),
Target: shortenText( Target: shortenText(formatTargetDisplay({ channel: entry.channel, target: entry.to }), 36),
formatTargetDisplay({ channel: entry.channel, target: entry.to }),
36,
),
Status: entry.ok ? "ok" : "error", Status: entry.ok ? "ok" : "error",
Error: entry.ok ? "" : shortenText(entry.error ?? "unknown error", 48), Error: entry.ok ? "" : shortenText(entry.error ?? "unknown error", 48),
})); }));

View File

@@ -251,11 +251,7 @@ export const MediaUnderstandingScopeSchema = z
.object({ .object({
channel: z.string().optional(), channel: z.string().optional(),
chatType: z chatType: z
.union([ .union([z.literal("direct"), z.literal("group"), z.literal("channel")])
z.literal("direct"),
z.literal("group"),
z.literal("channel"),
])
.optional(), .optional(),
keyPrefix: z.string().optional(), keyPrefix: z.string().optional(),
}) })

View File

@@ -39,11 +39,7 @@ export const SessionSchema = z
.object({ .object({
channel: z.string().optional(), channel: z.string().optional(),
chatType: z chatType: z
.union([ .union([z.literal("direct"), z.literal("group"), z.literal("channel")])
z.literal("direct"),
z.literal("group"),
z.literal("channel"),
])
.optional(), .optional(),
keyPrefix: z.string().optional(), keyPrefix: z.string().optional(),
}) })

View File

@@ -97,9 +97,8 @@ describe("hooks install (e2e)", () => {
expect(installResult.ok).toBe(true); expect(installResult.ok).toBe(true);
if (!installResult.ok) return; if (!installResult.ok) return;
const { clearInternalHooks, createInternalHookEvent, triggerInternalHook } = await import( const { clearInternalHooks, createInternalHookEvent, triggerInternalHook } =
"./internal-hooks.js" await import("./internal-hooks.js");
);
const { loadInternalHooks } = await import("./loader.js"); const { loadInternalHooks } = await import("./loader.js");
clearInternalHooks(); clearInternalHooks();

View File

@@ -62,7 +62,7 @@ describe("installHooksFromArchive", () => {
"---", "---",
"name: zip-hook", "name: zip-hook",
"description: Zip hook", "description: Zip hook",
"metadata: {\"clawdbot\":{\"events\":[\"command:new\"]}}", 'metadata: {"clawdbot":{"events":["command:new"]}}',
"---", "---",
"", "",
"# Zip Hook", "# Zip Hook",
@@ -82,9 +82,7 @@ describe("installHooksFromArchive", () => {
expect(result.hookPackId).toBe("zip-hooks"); expect(result.hookPackId).toBe("zip-hooks");
expect(result.hooks).toContain("zip-hook"); expect(result.hooks).toContain("zip-hook");
expect(result.targetDir).toBe(path.join(stateDir, "hooks", "zip-hooks")); expect(result.targetDir).toBe(path.join(stateDir, "hooks", "zip-hooks"));
expect( expect(fs.existsSync(path.join(result.targetDir, "hooks", "zip-hook", "HOOK.md"))).toBe(true);
fs.existsSync(path.join(result.targetDir, "hooks", "zip-hook", "HOOK.md")),
).toBe(true);
}); });
it("installs hook packs from tar archives", async () => { it("installs hook packs from tar archives", async () => {
@@ -109,7 +107,7 @@ describe("installHooksFromArchive", () => {
"---", "---",
"name: tar-hook", "name: tar-hook",
"description: Tar hook", "description: Tar hook",
"metadata: {\"clawdbot\":{\"events\":[\"command:new\"]}}", 'metadata: {"clawdbot":{"events":["command:new"]}}',
"---", "---",
"", "",
"# Tar Hook", "# Tar Hook",
@@ -148,7 +146,7 @@ describe("installHooksFromPath", () => {
"---", "---",
"name: my-hook", "name: my-hook",
"description: My hook", "description: My hook",
"metadata: {\"clawdbot\":{\"events\":[\"command:new\"]}}", 'metadata: {"clawdbot":{"events":["command:new"]}}',
"---", "---",
"", "",
"# My Hook", "# My Hook",

View File

@@ -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 }); await fs.mkdir(hooksDir, { recursive: true });
const targetDir = resolveHookInstallDir(hookPackId, hooksDir); 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 }); await fs.mkdir(hooksDir, { recursive: true });
const targetDir = resolveHookInstallDir(hookName, hooksDir); const targetDir = resolveHookInstallDir(hookName, hooksDir);

View File

@@ -79,9 +79,7 @@ export async function loadInternalHooks(
// Register for all events listed in metadata // Register for all events listed in metadata
const events = entry.clawdbot?.events ?? []; const events = entry.clawdbot?.events ?? [];
if (events.length === 0) { if (events.length === 0) {
console.warn( console.warn(`Hook warning: Hook '${entry.hook.name}' has no events defined in metadata`);
`Hook warning: Hook '${entry.hook.name}' has no events defined in metadata`,
);
continue; continue;
} }
@@ -126,9 +124,7 @@ export async function loadInternalHooks(
const handler = mod[exportName]; const handler = mod[exportName];
if (typeof handler !== "function") { if (typeof handler !== "function") {
console.error( console.error(`Hook error: Handler '${exportName}' from ${modulePath} is not a function`);
`Hook error: Handler '${exportName}' from ${modulePath} is not a function`,
);
continue; continue;
} }

View File

@@ -48,7 +48,11 @@ function resolvePackageHooks(manifest: HookPackageManifest): string[] {
return raw.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean); 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"); const hookMdPath = path.join(params.hookDir, "HOOK.md");
if (!fs.existsSync(hookMdPath)) return null; if (!fs.existsSync(hookMdPath)) return null;

View File

@@ -61,10 +61,7 @@ export async function withTimeout<T>(
} }
} }
async function extractZip(params: { async function extractZip(params: { archivePath: string; destDir: string }): Promise<void> {
archivePath: string;
destDir: string;
}): Promise<void> {
const buffer = await fs.readFile(params.archivePath); const buffer = await fs.readFile(params.archivePath);
const zip = await JSZip.loadAsync(buffer); const zip = await JSZip.loadAsync(buffer);
const entries = Object.values(zip.files); const entries = Object.values(zip.files);

View File

@@ -9,7 +9,11 @@ import {
normalizeMessageChannel, normalizeMessageChannel,
type GatewayMessageChannel, type GatewayMessageChannel,
} from "../../utils/message-channel.js"; } 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 { ClawdbotConfig } from "../../config/config.js";
import type { OutboundTargetResolution } from "./targets.js"; import type { OutboundTargetResolution } from "./targets.js";

View File

@@ -121,9 +121,7 @@ describe("applyMediaUnderstanding", () => {
expect(result.appliedAudio).toBe(true); expect(result.appliedAudio).toBe(true);
expect(ctx.Transcript).toBe("transcribed text"); expect(ctx.Transcript).toBe("transcribed text");
expect(ctx.Body).toBe( expect(ctx.Body).toBe("[Audio]\nUser text:\n/capture status\nTranscript:\ntranscribed text");
"[Audio]\nUser text:\n/capture status\nTranscript:\ntranscribed text",
);
expect(ctx.CommandBody).toBe("/capture status"); expect(ctx.CommandBody).toBe("/capture status");
expect(ctx.RawBody).toBe("/capture status"); expect(ctx.RawBody).toBe("/capture status");
expect(ctx.BodyForCommands).toBe("/capture status"); expect(ctx.BodyForCommands).toBe("/capture status");

View File

@@ -78,10 +78,7 @@ export async function applyMediaUnderstanding(params: {
} }
if (decisions.length > 0) { if (decisions.length > 0) {
ctx.MediaUnderstandingDecisions = [ ctx.MediaUnderstandingDecisions = [...(ctx.MediaUnderstandingDecisions ?? []), ...decisions];
...(ctx.MediaUnderstandingDecisions ?? []),
...decisions,
];
} }
if (outputs.length > 0) { if (outputs.length > 0) {

View File

@@ -1,10 +1,7 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import type { ClawdbotConfig } from "../config/config.js"; import type { ClawdbotConfig } from "../config/config.js";
import { import { resolveEntriesWithActiveFallback, resolveModelEntries } from "./resolve.js";
resolveEntriesWithActiveFallback,
resolveModelEntries,
} from "./resolve.js";
const providerRegistry = new Map([ const providerRegistry = new Map([
["openai", { capabilities: ["image"] }], ["openai", { capabilities: ["image"] }],

View File

@@ -100,7 +100,9 @@ function buildModelDecision(params: {
function formatDecisionSummary(decision: MediaUnderstandingDecision): string { function formatDecisionSummary(decision: MediaUnderstandingDecision): string {
const total = decision.attachments.length; 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 chosen = decision.attachments.find((entry) => entry.chosen)?.chosen;
const provider = chosen?.provider?.trim(); const provider = chosen?.provider?.trim();
const model = chosen?.model?.trim(); const model = chosen?.model?.trim();
@@ -355,7 +357,10 @@ async function runAttachmentEntries(params: {
cache: MediaAttachmentCache; cache: MediaAttachmentCache;
entries: MediaUnderstandingModelConfig[]; entries: MediaUnderstandingModelConfig[];
config?: MediaUnderstandingConfig; config?: MediaUnderstandingConfig;
}): Promise<{ output: MediaUnderstandingOutput | null; attempts: MediaUnderstandingModelDecision[] }> { }): Promise<{
output: MediaUnderstandingOutput | null;
attempts: MediaUnderstandingModelDecision[];
}> {
const { entries, capability } = params; const { entries, capability } = params;
const attempts: MediaUnderstandingModelDecision[] = []; const attempts: MediaUnderstandingModelDecision[] = [];
for (const entry of entries) { for (const entry of entries) {

View File

@@ -474,10 +474,8 @@ async function collectChannelSecurityFindings(params: {
if (plugin.id === "discord") { if (plugin.id === "discord") {
const discordCfg = const discordCfg =
(account as { config?: Record<string, unknown> } | null)?.config ?? ({} as Record< (account as { config?: Record<string, unknown> } | null)?.config ??
string, ({} as Record<string, unknown>);
unknown
>);
const nativeEnabled = resolveNativeCommandsEnabled({ const nativeEnabled = resolveNativeCommandsEnabled({
providerId: "discord", providerId: "discord",
providerSetting: coerceNativeSetting( providerSetting: coerceNativeSetting(
@@ -516,15 +514,20 @@ async function collectChannelSecurityFindings(params: {
normalizeAllowFromList([...dmAllowFrom, ...storeAllowFrom]).length > 0; normalizeAllowFromList([...dmAllowFrom, ...storeAllowFrom]).length > 0;
const useAccessGroups = params.cfg.commands?.useAccessGroups !== false; const useAccessGroups = params.cfg.commands?.useAccessGroups !== false;
if (!useAccessGroups && groupPolicy !== "disabled" && guildsConfigured && !hasAnyUserAllowlist) { if (
!useAccessGroups &&
groupPolicy !== "disabled" &&
guildsConfigured &&
!hasAnyUserAllowlist
) {
findings.push({ findings.push({
checkId: "channels.discord.commands.native.unrestricted", checkId: "channels.discord.commands.native.unrestricted",
severity: "critical", severity: "critical",
title: "Discord slash commands are unrestricted", title: "Discord slash commands are unrestricted",
detail: 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: 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 ( } else if (
useAccessGroups && useAccessGroups &&
@@ -540,7 +543,7 @@ async function collectChannelSecurityFindings(params: {
detail: 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.", "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: 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 = const slashCommandEnabled =
nativeEnabled || nativeEnabled ||
nativeSkillsEnabled || nativeSkillsEnabled ||
((slackCfg.slashCommand as { enabled?: unknown } | undefined)?.enabled === true); (slackCfg.slashCommand as { enabled?: unknown } | undefined)?.enabled === true;
if (slashCommandEnabled) { if (slashCommandEnabled) {
const useAccessGroups = params.cfg.commands?.useAccessGroups !== false; const useAccessGroups = params.cfg.commands?.useAccessGroups !== false;
if (!useAccessGroups) { if (!useAccessGroups) {
@@ -580,7 +583,8 @@ async function collectChannelSecurityFindings(params: {
remediation: "Set commands.useAccessGroups=true (recommended).", remediation: "Set commands.useAccessGroups=true (recommended).",
}); });
} else { } 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 dmAllowFrom = Array.isArray(dmAllowFromRaw) ? dmAllowFromRaw : [];
const storeAllowFrom = await readChannelAllowFromStore("slack").catch(() => []); const storeAllowFrom = await readChannelAllowFromStore("slack").catch(() => []);
const ownerAllowFromConfigured = const ownerAllowFromConfigured =

View File

@@ -19,7 +19,7 @@ export function normalizeDeliveryContext(context?: DeliveryContext): DeliveryCon
if (!context) return undefined; if (!context) return undefined;
const channel = const channel =
typeof context.channel === "string" typeof context.channel === "string"
? normalizeMessageChannel(context.channel) ?? context.channel.trim() ? (normalizeMessageChannel(context.channel) ?? context.channel.trim())
: undefined; : undefined;
const to = typeof context.to === "string" ? context.to.trim() : undefined; const to = typeof context.to === "string" ? context.to.trim() : undefined;
const accountId = normalizeAccountId(context.accountId); const accountId = normalizeAccountId(context.accountId);
@@ -31,9 +31,7 @@ export function normalizeDeliveryContext(context?: DeliveryContext): DeliveryCon
}; };
} }
export function normalizeSessionDeliveryFields( export function normalizeSessionDeliveryFields(source?: DeliveryContextSessionSource): {
source?: DeliveryContextSessionSource,
): {
deliveryContext?: DeliveryContext; deliveryContext?: DeliveryContext;
lastChannel?: string; lastChannel?: string;
lastTo?: string; lastTo?: string;

View File

@@ -78,9 +78,15 @@ async function resolveWhatsAppCommandAuthorized(params: {
} }
const storeAllowFrom = await readChannelAllowFromStore("whatsapp").catch(() => []); const storeAllowFrom = await readChannelAllowFromStore("whatsapp").catch(() => []);
const combinedAllowFrom = Array.from(new Set([...(configuredAllowFrom ?? []), ...storeAllowFrom])); const combinedAllowFrom = Array.from(
new Set([...(configuredAllowFrom ?? []), ...storeAllowFrom]),
);
const allowFrom = 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; if (allowFrom.some((v) => String(v).trim() === "*")) return true;
return normalizeAllowFromE164(allowFrom).includes(senderE164); return normalizeAllowFromE164(allowFrom).includes(senderE164);
} }