style: oxfmt format
This commit is contained in:
@@ -97,7 +97,8 @@ const execSchema = Type.Object({
|
||||
),
|
||||
pty: Type.Optional(
|
||||
Type.Boolean({
|
||||
description: "Run in a pseudo-terminal (PTY) when available (TTY-required CLIs, coding agents)",
|
||||
description:
|
||||
"Run in a pseudo-terminal (PTY) when available (TTY-required CLIs, coding agents)",
|
||||
}),
|
||||
),
|
||||
elevated: Type.Optional(
|
||||
@@ -272,22 +273,22 @@ export function createExecTool(
|
||||
|
||||
if (sandbox) {
|
||||
child = spawn(
|
||||
"docker",
|
||||
buildDockerExecArgs({
|
||||
containerName: sandbox.containerName,
|
||||
command: params.command,
|
||||
workdir: containerWorkdir ?? sandbox.containerWorkdir,
|
||||
env,
|
||||
tty: params.pty === true,
|
||||
}),
|
||||
{
|
||||
cwd: workdir,
|
||||
env: process.env,
|
||||
detached: process.platform !== "win32",
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
windowsHide: true,
|
||||
},
|
||||
) as ChildProcessWithoutNullStreams;
|
||||
"docker",
|
||||
buildDockerExecArgs({
|
||||
containerName: sandbox.containerName,
|
||||
command: params.command,
|
||||
workdir: containerWorkdir ?? sandbox.containerWorkdir,
|
||||
env,
|
||||
tty: params.pty === true,
|
||||
}),
|
||||
{
|
||||
cwd: workdir,
|
||||
env: process.env,
|
||||
detached: process.platform !== "win32",
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
windowsHide: true,
|
||||
},
|
||||
) as ChildProcessWithoutNullStreams;
|
||||
stdin = child.stdin;
|
||||
} else if (usePty) {
|
||||
const ptyModule = (await import("@lydell/node-pty")) as unknown as {
|
||||
@@ -326,11 +327,11 @@ export function createExecTool(
|
||||
};
|
||||
} else {
|
||||
child = spawn(shell, [...shellArgs, params.command], {
|
||||
cwd: workdir,
|
||||
env,
|
||||
detached: process.platform !== "win32",
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
windowsHide: true,
|
||||
cwd: workdir,
|
||||
env,
|
||||
detached: process.platform !== "win32",
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
windowsHide: true,
|
||||
}) as ChildProcessWithoutNullStreams;
|
||||
stdin = child.stdin;
|
||||
}
|
||||
|
||||
@@ -68,9 +68,7 @@ export function buildEmbeddedRunPayloads(params: {
|
||||
if (errorText) replyItems.push({ text: errorText, isError: true });
|
||||
|
||||
const inlineToolResults =
|
||||
params.inlineToolResultsAllowed &&
|
||||
params.verboseLevel !== "off" &&
|
||||
params.toolMetas.length > 0;
|
||||
params.inlineToolResultsAllowed && params.verboseLevel !== "off" && params.toolMetas.length > 0;
|
||||
if (inlineToolResults) {
|
||||
for (const { toolName, meta } of params.toolMetas) {
|
||||
const agg = formatToolAggregate(toolName, meta ? [meta] : []);
|
||||
|
||||
@@ -9,9 +9,7 @@ import {
|
||||
resolveStorePath,
|
||||
} from "../config/sessions.js";
|
||||
import { normalizeMainKey } from "../routing/session-key.js";
|
||||
import {
|
||||
resolveQueueSettings,
|
||||
} from "../auto-reply/reply/queue.js";
|
||||
import { resolveQueueSettings } from "../auto-reply/reply/queue.js";
|
||||
import { callGateway } from "../gateway/call.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import {
|
||||
|
||||
@@ -64,8 +64,8 @@ describe("subagent registry persistence", () => {
|
||||
expect(parsed.runs && Object.keys(parsed.runs)).toContain("run-1");
|
||||
const run = parsed.runs?.["run-1"] as
|
||||
| {
|
||||
requesterOrigin?: { channel?: string; accountId?: string };
|
||||
}
|
||||
requesterOrigin?: { channel?: string; accountId?: string };
|
||||
}
|
||||
| undefined;
|
||||
expect(run).toBeDefined();
|
||||
if (run) {
|
||||
|
||||
@@ -54,9 +54,7 @@ export function loadSubagentRegistryFromDisk(): Map<string, SubagentRunRecord> {
|
||||
? typed.announceCompletedAt
|
||||
: undefined;
|
||||
const cleanupCompletedAt =
|
||||
typeof typed.cleanupCompletedAt === "number"
|
||||
? typed.cleanupCompletedAt
|
||||
: legacyCompletedAt;
|
||||
typeof typed.cleanupCompletedAt === "number" ? typed.cleanupCompletedAt : legacyCompletedAt;
|
||||
const cleanupHandled =
|
||||
typeof typed.cleanupHandled === "boolean"
|
||||
? typed.cleanupHandled
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { callGateway } from "../gateway/call.js";
|
||||
import { onAgentEvent } from "../infra/agent-events.js";
|
||||
import {
|
||||
type DeliveryContext,
|
||||
normalizeDeliveryContext,
|
||||
} from "../utils/delivery-context.js";
|
||||
import { type DeliveryContext, normalizeDeliveryContext } from "../utils/delivery-context.js";
|
||||
import { runSubagentAnnounceFlow, type SubagentRunOutcome } from "./subagent-announce.js";
|
||||
import {
|
||||
loadSubagentRegistryFromDisk,
|
||||
|
||||
@@ -26,9 +26,7 @@ const AllMessageActions = CHANNEL_MESSAGE_ACTION_NAMES;
|
||||
function buildRoutingSchema() {
|
||||
return {
|
||||
channel: Type.Optional(Type.String()),
|
||||
target: Type.Optional(
|
||||
channelTargetSchema({ description: "Target channel/user id or name." }),
|
||||
),
|
||||
target: Type.Optional(channelTargetSchema({ description: "Target channel/user id or name." })),
|
||||
targets: Type.Optional(channelTargetsSchema()),
|
||||
accountId: Type.Optional(Type.String()),
|
||||
dryRun: Type.Optional(Type.Boolean()),
|
||||
@@ -188,7 +186,6 @@ function buildMessageToolSchemaProps(options: { includeButtons: boolean }) {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function buildMessageToolSchemaFromActions(
|
||||
actions: readonly string[],
|
||||
options: { includeButtons: boolean },
|
||||
|
||||
@@ -409,7 +409,7 @@ export async function resolveReplyDirectives(params: {
|
||||
return {
|
||||
kind: "continue",
|
||||
result: {
|
||||
commandSource: commandText,
|
||||
commandSource: commandText,
|
||||
command,
|
||||
allowTextCommands,
|
||||
skillCommands,
|
||||
|
||||
@@ -42,9 +42,9 @@ export function finalizeInboundContext<T extends Record<string, unknown>>(
|
||||
const bodyForCommandsSource = opts.forceBodyForCommands
|
||||
? (normalized.CommandBody ?? normalized.RawBody ?? normalized.Body)
|
||||
: (normalized.BodyForCommands ??
|
||||
normalized.CommandBody ??
|
||||
normalized.RawBody ??
|
||||
normalized.Body);
|
||||
normalized.CommandBody ??
|
||||
normalized.RawBody ??
|
||||
normalized.Body);
|
||||
normalized.BodyForCommands = normalizeInboundTextNewlines(bodyForCommandsSource);
|
||||
|
||||
const explicitLabel = normalized.ConversationLabel?.trim();
|
||||
|
||||
@@ -44,8 +44,6 @@ describe("formatInboundBodyWithSenderMeta", () => {
|
||||
|
||||
it("does not append when the body already includes a sender prefix", () => {
|
||||
const ctx: MsgContext = { ChatType: "group", SenderName: "Alice", SenderId: "A1" };
|
||||
expect(formatInboundBodyWithSenderMeta({ ctx, body: "Alice (A1): hi" })).toBe(
|
||||
"Alice (A1): hi",
|
||||
);
|
||||
expect(formatInboundBodyWithSenderMeta({ ctx, body: "Alice (A1): hi" })).toBe("Alice (A1): hi");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,4 +16,3 @@ describe("normalizeInboundTextNewlines", () => {
|
||||
expect(normalizeInboundTextNewlines("a\\nb")).toBe("a\nb");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -129,7 +129,8 @@ export async function initSessionState(params: {
|
||||
|
||||
const groupResolution = resolveGroupSessionKey(sessionCtxForState) ?? undefined;
|
||||
const normalizedChatType = normalizeChatType(ctx.ChatType);
|
||||
const isGroup = normalizedChatType != null && normalizedChatType !== "direct" ? true : Boolean(groupResolution);
|
||||
const isGroup =
|
||||
normalizedChatType != null && normalizedChatType !== "direct" ? true : Boolean(groupResolution);
|
||||
// Prefer CommandBody/RawBody (clean message) for command detection; fall back
|
||||
// to Body which may contain structural context (history, sender labels).
|
||||
const commandSource = ctx.BodyForCommands ?? ctx.CommandBody ?? ctx.RawBody ?? ctx.Body ?? "";
|
||||
|
||||
@@ -20,13 +20,20 @@ describe("resolveConversationLabel", () => {
|
||||
});
|
||||
|
||||
it("does not append ids for #rooms/channels", () => {
|
||||
const ctx: MsgContext = { ChatType: "channel", GroupSubject: "#general", From: "slack:channel:C123" };
|
||||
const ctx: MsgContext = {
|
||||
ChatType: "channel",
|
||||
GroupSubject: "#general",
|
||||
From: "slack:channel:C123",
|
||||
};
|
||||
expect(resolveConversationLabel(ctx)).toBe("#general");
|
||||
});
|
||||
|
||||
it("appends ids for WhatsApp-like group ids when a subject exists", () => {
|
||||
const ctx: MsgContext = { ChatType: "group", GroupSubject: "Family", From: "whatsapp:group:123@g.us" };
|
||||
const ctx: MsgContext = {
|
||||
ChatType: "group",
|
||||
GroupSubject: "Family",
|
||||
From: "whatsapp:group:123@g.us",
|
||||
};
|
||||
expect(resolveConversationLabel(ctx)).toBe("Family id:123@g.us");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -43,4 +43,3 @@ export function resolveConversationLabel(ctx: MsgContext): string | undefined {
|
||||
if (base.startsWith("#") || base.startsWith("@")) return base;
|
||||
return `${base} id:${id}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -53,10 +53,7 @@ export const whatsappOutbound: ChannelOutboundAdapter = {
|
||||
}
|
||||
return {
|
||||
ok: false,
|
||||
error: missingTargetError(
|
||||
"WhatsApp",
|
||||
"<E.164|group JID> or channels.whatsapp.allowFrom[0]",
|
||||
),
|
||||
error: missingTargetError("WhatsApp", "<E.164|group JID> or channels.whatsapp.allowFrom[0]"),
|
||||
};
|
||||
},
|
||||
sendText: async ({ to, text, accountId, deps, gifPlayback }) => {
|
||||
|
||||
@@ -202,10 +202,7 @@ export const signalPlugin: ChannelPlugin<ResolvedSignalAccount> = {
|
||||
if (!trimmed) {
|
||||
return {
|
||||
ok: false,
|
||||
error: missingTargetError(
|
||||
"Signal",
|
||||
"<E.164|group:ID|signal:group:ID|signal:+E.164>",
|
||||
),
|
||||
error: missingTargetError("Signal", "<E.164|group:ID|signal:group:ID|signal:+E.164>"),
|
||||
};
|
||||
}
|
||||
return { ok: true, to: trimmed };
|
||||
|
||||
@@ -342,7 +342,10 @@ export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> = {
|
||||
}
|
||||
return {
|
||||
ok: false,
|
||||
error: missingTargetError("WhatsApp", "<E.164|group JID> or channels.whatsapp.allowFrom[0]"),
|
||||
error: missingTargetError(
|
||||
"WhatsApp",
|
||||
"<E.164|group JID> or channels.whatsapp.allowFrom[0]",
|
||||
),
|
||||
};
|
||||
},
|
||||
sendText: async ({ to, text, accountId, deps, gifPlayback }) => {
|
||||
|
||||
@@ -11,7 +11,9 @@ describe("validateSenderIdentity", () => {
|
||||
|
||||
it("requires some sender identity for non-direct chats", () => {
|
||||
const ctx: MsgContext = { ChatType: "group" };
|
||||
expect(validateSenderIdentity(ctx)).toContain("missing sender identity (SenderId/SenderName/SenderUsername/SenderE164)");
|
||||
expect(validateSenderIdentity(ctx)).toContain(
|
||||
"missing sender identity (SenderId/SenderName/SenderUsername/SenderE164)",
|
||||
);
|
||||
});
|
||||
|
||||
it("validates SenderE164 and SenderUsername shape", () => {
|
||||
@@ -27,4 +29,3 @@ describe("validateSenderIdentity", () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -25,8 +25,10 @@ export function validateSenderIdentity(ctx: MsgContext): string[] {
|
||||
}
|
||||
|
||||
if (senderUsername) {
|
||||
if (senderUsername.includes("@")) issues.push(`SenderUsername should not include "@": ${senderUsername}`);
|
||||
if (/\s/.test(senderUsername)) issues.push(`SenderUsername should not include whitespace: ${senderUsername}`);
|
||||
if (senderUsername.includes("@"))
|
||||
issues.push(`SenderUsername should not include "@": ${senderUsername}`);
|
||||
if (/\s/.test(senderUsername))
|
||||
issues.push(`SenderUsername should not include whitespace: ${senderUsername}`);
|
||||
}
|
||||
|
||||
if (ctx.SenderId != null && !senderId) {
|
||||
@@ -35,4 +37,3 @@ export function validateSenderIdentity(ctx: MsgContext): string[] {
|
||||
|
||||
return issues;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,4 +20,3 @@ export function createOutboundSendDeps(deps: CliDeps): OutboundSendDeps {
|
||||
sendIMessage: deps.sendMessageIMessage,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -40,9 +40,7 @@ export function registerMessageDiscordAdminCommands(message: Command, helpers: M
|
||||
const channel = message.command("channel").description("Channel actions");
|
||||
helpers
|
||||
.withMessageBase(
|
||||
helpers.withRequiredMessageTarget(
|
||||
channel.command("info").description("Fetch channel info"),
|
||||
),
|
||||
helpers.withRequiredMessageTarget(channel.command("info").description("Fetch channel info")),
|
||||
)
|
||||
.action(async (opts) => {
|
||||
await helpers.runMessageAction("channel-info", opts);
|
||||
|
||||
@@ -21,7 +21,9 @@ export function registerMessagePinCommands(message: Command, helpers: MessageCli
|
||||
}),
|
||||
helpers
|
||||
.withMessageBase(
|
||||
helpers.withRequiredMessageTarget(message.command("pins").description("List pinned messages")),
|
||||
helpers.withRequiredMessageTarget(
|
||||
message.command("pins").description("List pinned messages"),
|
||||
),
|
||||
)
|
||||
.option("--limit <n>", "Result limit")
|
||||
.action(async (opts) => {
|
||||
|
||||
@@ -7,7 +7,9 @@ export function registerMessageReadEditDeleteCommands(
|
||||
) {
|
||||
helpers
|
||||
.withMessageBase(
|
||||
helpers.withRequiredMessageTarget(message.command("read").description("Read recent messages")),
|
||||
helpers.withRequiredMessageTarget(
|
||||
message.command("read").description("Read recent messages"),
|
||||
),
|
||||
)
|
||||
.option("--limit <n>", "Result limit")
|
||||
.option("--before <id>", "Read/search before id")
|
||||
|
||||
@@ -34,9 +34,7 @@ function resolveDeliveryAccountId(params: {
|
||||
const sessionOrigin = deliveryContextFromSession(params.sessionEntry);
|
||||
return (
|
||||
normalizeAccountId(params.opts.accountId) ??
|
||||
(params.targetMode === "implicit"
|
||||
? normalizeAccountId(sessionOrigin?.accountId)
|
||||
: undefined)
|
||||
(params.targetMode === "implicit" ? normalizeAccountId(sessionOrigin?.accountId) : undefined)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -332,9 +332,7 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_2: LegacyConfigMigration[] = [
|
||||
const tools = ensureRecord(raw, "tools");
|
||||
const media = ensureRecord(tools, "media");
|
||||
const mediaAudio = ensureRecord(media, "audio");
|
||||
const models = Array.isArray(mediaAudio.models)
|
||||
? (mediaAudio.models as unknown[])
|
||||
: [];
|
||||
const models = Array.isArray(mediaAudio.models) ? (mediaAudio.models as unknown[]) : [];
|
||||
if (models.length === 0) {
|
||||
mediaAudio.enabled = true;
|
||||
mediaAudio.models = [mapped];
|
||||
@@ -355,9 +353,7 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_2: LegacyConfigMigration[] = [
|
||||
const tools = ensureRecord(raw, "tools");
|
||||
const media = ensureRecord(tools, "media");
|
||||
const mediaAudio = ensureRecord(media, "audio");
|
||||
const models = Array.isArray(mediaAudio.models)
|
||||
? (mediaAudio.models as unknown[])
|
||||
: [];
|
||||
const models = Array.isArray(mediaAudio.models) ? (mediaAudio.models as unknown[]) : [];
|
||||
if (models.length === 0) {
|
||||
mediaAudio.enabled = true;
|
||||
mediaAudio.models = [mapped];
|
||||
|
||||
@@ -274,7 +274,9 @@ export const MediaUnderstandingAttachmentsSchema = z
|
||||
.object({
|
||||
mode: z.union([z.literal("first"), z.literal("all")]).optional(),
|
||||
maxAttachments: z.number().int().positive().optional(),
|
||||
prefer: z.union([z.literal("first"), z.literal("last"), z.literal("path"), z.literal("url")]).optional(),
|
||||
prefer: z
|
||||
.union([z.literal("first"), z.literal("last"), z.literal("path"), z.literal("url")])
|
||||
.optional(),
|
||||
})
|
||||
.optional();
|
||||
|
||||
|
||||
@@ -8,7 +8,10 @@ import {
|
||||
} from "../../config/sessions.js";
|
||||
import { resolveMessageChannelSelection } from "../../infra/outbound/channel-selection.js";
|
||||
import type { OutboundChannel } from "../../infra/outbound/targets.js";
|
||||
import { resolveOutboundTarget, resolveSessionDeliveryTarget } from "../../infra/outbound/targets.js";
|
||||
import {
|
||||
resolveOutboundTarget,
|
||||
resolveSessionDeliveryTarget,
|
||||
} from "../../infra/outbound/targets.js";
|
||||
|
||||
export async function resolveDeliveryTarget(
|
||||
cfg: ClawdbotConfig,
|
||||
|
||||
@@ -87,4 +87,3 @@ describe("discord processDiscordMessage inbound contract", () => {
|
||||
expectInboundContextContract(capturedCtx!);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -8,10 +8,7 @@ import {
|
||||
extractShortModelName,
|
||||
type ResponsePrefixContext,
|
||||
} from "../../auto-reply/reply/response-prefix-template.js";
|
||||
import {
|
||||
formatInboundEnvelope,
|
||||
formatThreadStarterEnvelope,
|
||||
} from "../../auto-reply/envelope.js";
|
||||
import { formatInboundEnvelope, formatThreadStarterEnvelope } from "../../auto-reply/envelope.js";
|
||||
import { dispatchReplyFromConfig } from "../../auto-reply/reply/dispatch-from-config.js";
|
||||
import {
|
||||
buildPendingHistoryContextFromMap,
|
||||
@@ -126,7 +123,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
|
||||
const senderLabel =
|
||||
senderDisplay && senderTag && senderDisplay !== senderTag
|
||||
? `${senderDisplay} (${senderTag})`
|
||||
: senderDisplay ?? senderTag ?? author.id;
|
||||
: (senderDisplay ?? senderTag ?? author.id);
|
||||
const groupRoom = isGuildMessage && displayChannelSlug ? `#${displayChannelSlug}` : undefined;
|
||||
const groupSubject = isDirectMessage ? undefined : groupRoom;
|
||||
const channelDescription = channelInfo?.topic?.trim();
|
||||
|
||||
@@ -60,7 +60,7 @@ describe("runMessageAction context isolation", () => {
|
||||
action: "send",
|
||||
params: {
|
||||
channel: "slack",
|
||||
target: "#C12345678",
|
||||
target: "#C12345678",
|
||||
},
|
||||
toolContext: { currentChannelId: "C12345678" },
|
||||
dryRun: true,
|
||||
|
||||
@@ -13,7 +13,10 @@ import type {
|
||||
} from "../../channels/plugins/types.js";
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import type { GatewayClientMode, GatewayClientName } from "../../utils/message-channel.js";
|
||||
import { listConfiguredMessageChannels, resolveMessageChannelSelection } from "./channel-selection.js";
|
||||
import {
|
||||
listConfiguredMessageChannels,
|
||||
resolveMessageChannelSelection,
|
||||
} from "./channel-selection.js";
|
||||
import { applyTargetToParams } from "./channel-target.js";
|
||||
import type { OutboundSendDeps } from "./deliver.js";
|
||||
import type { MessagePollResult, MessageSendResult } from "./message.js";
|
||||
@@ -483,10 +486,7 @@ async function handlePollAction(ctx: ResolvedActionContext): Promise<MessageActi
|
||||
|
||||
async function handlePluginAction(ctx: ResolvedActionContext): Promise<MessageActionRunResult> {
|
||||
const { cfg, params, channel, accountId, dryRun, gateway, input } = ctx;
|
||||
const action = input.action as Exclude<
|
||||
ChannelMessageActionName,
|
||||
"send" | "poll" | "broadcast"
|
||||
>;
|
||||
const action = input.action as Exclude<ChannelMessageActionName, "send" | "poll" | "broadcast">;
|
||||
if (dryRun) {
|
||||
return {
|
||||
kind: "action",
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||
import { dispatchChannelMessageAction } from "../../channels/plugins/message-actions.js";
|
||||
import type {
|
||||
ChannelId,
|
||||
ChannelThreadingToolContext,
|
||||
} from "../../channels/plugins/types.js";
|
||||
import type { ChannelId, ChannelThreadingToolContext } from "../../channels/plugins/types.js";
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import type { GatewayClientMode, GatewayClientName } from "../../utils/message-channel.js";
|
||||
import type { OutboundSendDeps } from "./deliver.js";
|
||||
|
||||
@@ -340,9 +340,7 @@ describe("applyMediaUnderstanding", () => {
|
||||
expect(result.appliedAudio).toBe(true);
|
||||
expect(ctx.Transcript).toBe("Audio 1:\nnote-a.ogg\n\nAudio 2:\nnote-b.ogg");
|
||||
expect(ctx.Body).toBe(
|
||||
["[Audio 1/2]\nTranscript:\nnote-a.ogg", "[Audio 2/2]\nTranscript:\nnote-b.ogg"].join(
|
||||
"\n\n",
|
||||
),
|
||||
["[Audio 1/2]\nTranscript:\nnote-a.ogg", "[Audio 2/2]\nTranscript:\nnote-b.ogg"].join("\n\n"),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,11 +9,7 @@ import type {
|
||||
MediaUnderstandingConfig,
|
||||
MediaUnderstandingModelConfig,
|
||||
} from "../config/types.tools.js";
|
||||
import {
|
||||
MediaAttachmentCache,
|
||||
normalizeAttachments,
|
||||
selectAttachments,
|
||||
} from "./attachments.js";
|
||||
import { MediaAttachmentCache, normalizeAttachments, selectAttachments } from "./attachments.js";
|
||||
import {
|
||||
CLI_OUTPUT_MAX_BUFFER,
|
||||
DEFAULT_AUDIO_MODELS,
|
||||
@@ -85,7 +81,9 @@ async function runProviderEntry(params: {
|
||||
const maxBytes = resolveMaxBytes({ capability, entry, cfg, config: params.config });
|
||||
const maxChars = resolveMaxChars({ capability, entry, cfg, config: params.config });
|
||||
const timeoutMs = resolveTimeoutMs(
|
||||
entry.timeoutSeconds ?? params.config?.timeoutSeconds ?? cfg.tools?.media?.[capability]?.timeoutSeconds,
|
||||
entry.timeoutSeconds ??
|
||||
params.config?.timeoutSeconds ??
|
||||
cfg.tools?.media?.[capability]?.timeoutSeconds,
|
||||
DEFAULT_TIMEOUT_SECONDS[capability],
|
||||
);
|
||||
const prompt = resolvePrompt(
|
||||
@@ -250,7 +248,9 @@ async function runCliEntry(params: {
|
||||
const maxBytes = resolveMaxBytes({ capability, entry, cfg, config: params.config });
|
||||
const maxChars = resolveMaxChars({ capability, entry, cfg, config: params.config });
|
||||
const timeoutMs = resolveTimeoutMs(
|
||||
entry.timeoutSeconds ?? params.config?.timeoutSeconds ?? cfg.tools?.media?.[capability]?.timeoutSeconds,
|
||||
entry.timeoutSeconds ??
|
||||
params.config?.timeoutSeconds ??
|
||||
cfg.tools?.media?.[capability]?.timeoutSeconds,
|
||||
DEFAULT_TIMEOUT_SECONDS[capability],
|
||||
);
|
||||
const prompt = resolvePrompt(
|
||||
|
||||
@@ -317,10 +317,7 @@ export class MediaAttachmentCache {
|
||||
timeoutMs: params.timeoutMs,
|
||||
});
|
||||
const extension = path.extname(bufferResult.fileName || "") || "";
|
||||
const tmpPath = path.join(
|
||||
os.tmpdir(),
|
||||
`clawdbot-media-${crypto.randomUUID()}${extension}`,
|
||||
);
|
||||
const tmpPath = path.join(os.tmpdir(), `clawdbot-media-${crypto.randomUUID()}${extension}`);
|
||||
await fs.writeFile(tmpPath, bufferResult.buffer);
|
||||
entry.tempPath = tmpPath;
|
||||
entry.tempCleanup = async () => {
|
||||
@@ -348,8 +345,9 @@ export class MediaAttachmentCache {
|
||||
}
|
||||
return existing;
|
||||
}
|
||||
const attachment =
|
||||
this.attachments.find((item) => item.index === attachmentIndex) ?? { index: attachmentIndex };
|
||||
const attachment = this.attachments.find((item) => item.index === attachmentIndex) ?? {
|
||||
index: attachmentIndex,
|
||||
};
|
||||
const entry: AttachmentCacheEntry = {
|
||||
attachment,
|
||||
resolvedPath: this.resolveLocalPath(attachment),
|
||||
|
||||
@@ -10,8 +10,6 @@ export class MediaUnderstandingSkipError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export function isMediaUnderstandingSkipError(
|
||||
err: unknown,
|
||||
): err is MediaUnderstandingSkipError {
|
||||
export function isMediaUnderstandingSkipError(err: unknown): err is MediaUnderstandingSkipError {
|
||||
return err instanceof MediaUnderstandingSkipError;
|
||||
}
|
||||
|
||||
@@ -88,7 +88,5 @@ export function formatMediaUnderstandingBody(params: {
|
||||
|
||||
export function formatAudioTranscripts(outputs: MediaUnderstandingOutput[]): string {
|
||||
if (outputs.length === 1) return outputs[0].text;
|
||||
return outputs
|
||||
.map((output, index) => `Audio ${index + 1}:\n${output.text}`)
|
||||
.join("\n\n");
|
||||
return outputs.map((output, index) => `Audio ${index + 1}:\n${output.text}`).join("\n\n");
|
||||
}
|
||||
|
||||
@@ -383,13 +383,19 @@ export async function collectPluginsTrustFindings(params: {
|
||||
if (!allowConfigured) {
|
||||
const hasString = (value: unknown) => typeof value === "string" && value.trim().length > 0;
|
||||
const hasAccountStringKey = (account: unknown, key: string) =>
|
||||
Boolean(account && typeof account === "object" && hasString((account as Record<string, unknown>)[key]));
|
||||
Boolean(
|
||||
account &&
|
||||
typeof account === "object" &&
|
||||
hasString((account as Record<string, unknown>)[key]),
|
||||
);
|
||||
|
||||
const discordConfigured =
|
||||
hasString(params.cfg.channels?.discord?.token) ||
|
||||
Boolean(
|
||||
params.cfg.channels?.discord?.accounts &&
|
||||
Object.values(params.cfg.channels.discord.accounts).some((a) => hasAccountStringKey(a, "token")),
|
||||
Object.values(params.cfg.channels.discord.accounts).some((a) =>
|
||||
hasAccountStringKey(a, "token"),
|
||||
),
|
||||
) ||
|
||||
hasString(process.env.DISCORD_BOT_TOKEN);
|
||||
|
||||
@@ -398,9 +404,9 @@ export async function collectPluginsTrustFindings(params: {
|
||||
hasString(params.cfg.channels?.telegram?.tokenFile) ||
|
||||
Boolean(
|
||||
params.cfg.channels?.telegram?.accounts &&
|
||||
Object.values(params.cfg.channels.telegram.accounts).some(
|
||||
(a) => hasAccountStringKey(a, "botToken") || hasAccountStringKey(a, "tokenFile"),
|
||||
),
|
||||
Object.values(params.cfg.channels.telegram.accounts).some(
|
||||
(a) => hasAccountStringKey(a, "botToken") || hasAccountStringKey(a, "tokenFile"),
|
||||
),
|
||||
) ||
|
||||
hasString(process.env.TELEGRAM_BOT_TOKEN);
|
||||
|
||||
@@ -409,9 +415,9 @@ export async function collectPluginsTrustFindings(params: {
|
||||
hasString(params.cfg.channels?.slack?.appToken) ||
|
||||
Boolean(
|
||||
params.cfg.channels?.slack?.accounts &&
|
||||
Object.values(params.cfg.channels.slack.accounts).some(
|
||||
(a) => hasAccountStringKey(a, "botToken") || hasAccountStringKey(a, "appToken"),
|
||||
),
|
||||
Object.values(params.cfg.channels.slack.accounts).some(
|
||||
(a) => hasAccountStringKey(a, "botToken") || hasAccountStringKey(a, "appToken"),
|
||||
),
|
||||
) ||
|
||||
hasString(process.env.SLACK_BOT_TOKEN) ||
|
||||
hasString(process.env.SLACK_APP_TOKEN);
|
||||
|
||||
@@ -505,10 +505,8 @@ async function collectChannelSecurityFindings(params: {
|
||||
if (!allowTextCommands) continue;
|
||||
|
||||
const telegramCfg =
|
||||
(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 groupPolicy = (telegramCfg.groupPolicy as string | undefined) ?? "allowlist";
|
||||
const groups = telegramCfg.groups as Record<string, unknown> | undefined;
|
||||
const groupsConfigured = Boolean(groups) && Object.keys(groups ?? {}).length > 0;
|
||||
@@ -518,24 +516,26 @@ async function collectChannelSecurityFindings(params: {
|
||||
|
||||
const storeAllowFrom = await readChannelAllowFromStore("telegram").catch(() => []);
|
||||
const storeHasWildcard = storeAllowFrom.some((v) => String(v).trim() === "*");
|
||||
const groupAllowFrom = Array.isArray(telegramCfg.groupAllowFrom) ? telegramCfg.groupAllowFrom : [];
|
||||
const groupAllowFrom = Array.isArray(telegramCfg.groupAllowFrom)
|
||||
? telegramCfg.groupAllowFrom
|
||||
: [];
|
||||
const groupAllowFromHasWildcard = groupAllowFrom.some((v) => String(v).trim() === "*");
|
||||
const anyGroupOverride = Boolean(
|
||||
groups &&
|
||||
Object.values(groups).some((value) => {
|
||||
if (!value || typeof value !== "object") return false;
|
||||
const group = value as Record<string, unknown>;
|
||||
const allowFrom = Array.isArray(group.allowFrom) ? group.allowFrom : [];
|
||||
if (allowFrom.length > 0) return true;
|
||||
const topics = group.topics;
|
||||
if (!topics || typeof topics !== "object") return false;
|
||||
return Object.values(topics as Record<string, unknown>).some((topicValue) => {
|
||||
if (!topicValue || typeof topicValue !== "object") return false;
|
||||
const topic = topicValue as Record<string, unknown>;
|
||||
const topicAllow = Array.isArray(topic.allowFrom) ? topic.allowFrom : [];
|
||||
return topicAllow.length > 0;
|
||||
});
|
||||
}),
|
||||
Object.values(groups).some((value) => {
|
||||
if (!value || typeof value !== "object") return false;
|
||||
const group = value as Record<string, unknown>;
|
||||
const allowFrom = Array.isArray(group.allowFrom) ? group.allowFrom : [];
|
||||
if (allowFrom.length > 0) return true;
|
||||
const topics = group.topics;
|
||||
if (!topics || typeof topics !== "object") return false;
|
||||
return Object.values(topics as Record<string, unknown>).some((topicValue) => {
|
||||
if (!topicValue || typeof topicValue !== "object") return false;
|
||||
const topic = topicValue as Record<string, unknown>;
|
||||
const topicAllow = Array.isArray(topic.allowFrom) ? topic.allowFrom : [];
|
||||
return topicAllow.length > 0;
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
const hasAnySenderAllowlist =
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
|
||||
const dispatchMock = vi.fn();
|
||||
const readAllowFromMock = vi.fn();
|
||||
|
||||
@@ -9,7 +8,6 @@ vi.mock("../pairing/pairing-store.js", () => ({
|
||||
upsertChannelPairingRequest: vi.fn(),
|
||||
}));
|
||||
|
||||
|
||||
describe("signal event handler sender prefix", () => {
|
||||
beforeEach(() => {
|
||||
dispatchMock.mockReset().mockImplementation(async ({ dispatcher, ctx }) => {
|
||||
|
||||
@@ -49,7 +49,7 @@ describe("slack prepareSlackMessage inbound contract", () => {
|
||||
mediaMaxBytes: 1024,
|
||||
removeAckAfterReply: false,
|
||||
});
|
||||
slackCtx.resolveUserName = async () => ({ name: "Alice" } as any);
|
||||
slackCtx.resolveUserName = async () => ({ name: "Alice" }) as any;
|
||||
|
||||
const account: ResolvedSlackAccount = {
|
||||
accountId: "default",
|
||||
@@ -78,4 +78,3 @@ describe("slack prepareSlackMessage inbound contract", () => {
|
||||
expectInboundContextContract(prepared!.ctxPayload as any);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -240,7 +240,12 @@ export async function prepareSlackMessage(params: {
|
||||
: false;
|
||||
const commandAuthorized = ownerAuthorized || channelCommandAuthorized;
|
||||
|
||||
if (allowTextCommands && isRoomish && hasControlCommand(message.text ?? "", cfg) && !commandAuthorized) {
|
||||
if (
|
||||
allowTextCommands &&
|
||||
isRoomish &&
|
||||
hasControlCommand(message.text ?? "", cfg) &&
|
||||
!commandAuthorized
|
||||
) {
|
||||
logVerbose(`Blocked slack control command from unauthorized sender ${senderId}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -251,7 +251,9 @@ export const registerTelegramNativeCommands = ({
|
||||
const groupSystemPrompt =
|
||||
systemPromptParts.length > 0 ? systemPromptParts.join("\n\n") : undefined;
|
||||
const conversationLabel = isGroup
|
||||
? (msg.chat.title ? `${msg.chat.title} id:${chatId}` : `group:${chatId}`)
|
||||
? msg.chat.title
|
||||
? `${msg.chat.title} id:${chatId}`
|
||||
: `group:${chatId}`
|
||||
: (buildSenderName(msg) ?? String(senderId || chatId));
|
||||
const ctxPayload = finalizeInboundContext({
|
||||
Body: prompt,
|
||||
|
||||
@@ -38,13 +38,11 @@ describe("delivery context helpers", () => {
|
||||
});
|
||||
|
||||
it("builds stable keys only when channel and to are present", () => {
|
||||
expect(deliveryContextKey({ channel: "whatsapp", to: "+1555" })).toBe(
|
||||
"whatsapp|+1555|",
|
||||
);
|
||||
expect(deliveryContextKey({ channel: "whatsapp", to: "+1555" })).toBe("whatsapp|+1555|");
|
||||
expect(deliveryContextKey({ channel: "whatsapp" })).toBeUndefined();
|
||||
expect(
|
||||
deliveryContextKey({ channel: "whatsapp", to: "+1555", accountId: "acct-1" }),
|
||||
).toBe("whatsapp|+1555|acct-1");
|
||||
expect(deliveryContextKey({ channel: "whatsapp", to: "+1555", accountId: "acct-1" })).toBe(
|
||||
"whatsapp|+1555|acct-1",
|
||||
);
|
||||
});
|
||||
|
||||
it("derives delivery context from a session entry", () => {
|
||||
|
||||
@@ -31,7 +31,11 @@ describe("web processMessage inbound contract", () => {
|
||||
groupSubject: "Test Group",
|
||||
groupParticipants: [],
|
||||
} as any,
|
||||
route: { agentId: "main", accountId: "default", sessionKey: "agent:main:whatsapp:group:123" } as any,
|
||||
route: {
|
||||
agentId: "main",
|
||||
accountId: "default",
|
||||
sessionKey: "agent:main:whatsapp:group:123",
|
||||
} as any,
|
||||
groupHistoryKey: "123@g.us",
|
||||
groupHistories: new Map(),
|
||||
groupMemberNames: new Map(),
|
||||
|
||||
@@ -18,4 +18,3 @@ export function expectInboundContextContract(ctx: MsgContext) {
|
||||
expect(label).toBeTruthy();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -160,4 +160,3 @@ describe("inbound context contract (providers + extensions)", () => {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user