style: run oxfmt
This commit is contained in:
@@ -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: [
|
||||||
|
|||||||
@@ -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"] });
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -70,4 +70,3 @@ describe("resolveCommandAuthorizedFromAuthorizers", () => {
|
|||||||
).toBe(true);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -21,4 +21,3 @@ export function resolveCommandAuthorizedFromAuthorizers(params: {
|
|||||||
}
|
}
|
||||||
return authorizers.some((entry) => entry.configured && entry.allowed);
|
return authorizers.some((entry) => entry.configured && entry.allowed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -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(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -319,38 +319,38 @@ export async function preflightDiscordMessage(
|
|||||||
surface: "discord",
|
surface: "discord",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isDirectMessage) {
|
if (!isDirectMessage) {
|
||||||
const ownerAllowList = normalizeDiscordAllowList(params.allowFrom, ["discord:", "user:"]);
|
const ownerAllowList = normalizeDiscordAllowList(params.allowFrom, ["discord:", "user:"]);
|
||||||
const ownerOk = ownerAllowList
|
const ownerOk = ownerAllowList
|
||||||
? allowListMatches(ownerAllowList, {
|
? allowListMatches(ownerAllowList, {
|
||||||
id: author.id,
|
id: author.id,
|
||||||
name: author.username,
|
name: author.username,
|
||||||
tag: formatDiscordUserTag(author),
|
tag: formatDiscordUserTag(author),
|
||||||
})
|
})
|
||||||
: false;
|
: false;
|
||||||
const channelUsers = channelConfig?.users ?? guildInfo?.users;
|
const channelUsers = channelConfig?.users ?? guildInfo?.users;
|
||||||
const usersOk =
|
const usersOk =
|
||||||
Array.isArray(channelUsers) && channelUsers.length > 0
|
Array.isArray(channelUsers) && channelUsers.length > 0
|
||||||
? resolveDiscordUserAllowed({
|
? resolveDiscordUserAllowed({
|
||||||
allowList: channelUsers,
|
allowList: channelUsers,
|
||||||
userId: author.id,
|
userId: author.id,
|
||||||
userName: author.username,
|
userName: author.username,
|
||||||
userTag: formatDiscordUserTag(author),
|
userTag: formatDiscordUserTag(author),
|
||||||
})
|
})
|
||||||
: false;
|
: false;
|
||||||
const useAccessGroups = params.cfg.commands?.useAccessGroups !== false;
|
const useAccessGroups = params.cfg.commands?.useAccessGroups !== false;
|
||||||
commandAuthorized = resolveCommandAuthorizedFromAuthorizers({
|
commandAuthorized = resolveCommandAuthorizedFromAuthorizers({
|
||||||
useAccessGroups,
|
useAccessGroups,
|
||||||
authorizers: [
|
authorizers: [
|
||||||
{ configured: ownerAllowList != null, allowed: ownerOk },
|
{ configured: ownerAllowList != null, allowed: ownerOk },
|
||||||
{ configured: Array.isArray(channelUsers) && channelUsers.length > 0, allowed: usersOk },
|
{ configured: Array.isArray(channelUsers) && channelUsers.length > 0, allowed: usersOk },
|
||||||
],
|
],
|
||||||
modeWhenAccessGroupsOff: "configured",
|
modeWhenAccessGroupsOff: "configured",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (allowTextCommands && hasControlCommand(baseText, params.cfg) && !commandAuthorized) {
|
if (allowTextCommands && hasControlCommand(baseText, params.cfg) && !commandAuthorized) {
|
||||||
logVerbose(`Blocked discord control command from unauthorized sender ${author.id}`);
|
logVerbose(`Blocked discord control command from unauthorized sender ${author.id}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -230,19 +230,19 @@ describe("gateway server agent", () => {
|
|||||||
await server.close();
|
await server.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("agent uses webchat for internal runs when last provider is webchat", async () => {
|
test("agent uses webchat for internal runs when last provider is webchat", async () => {
|
||||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-gw-"));
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-gw-"));
|
||||||
testState.sessionStorePath = path.join(dir, "sessions.json");
|
testState.sessionStorePath = path.join(dir, "sessions.json");
|
||||||
await writeSessionStore({
|
await writeSessionStore({
|
||||||
entries: {
|
entries: {
|
||||||
main: {
|
main: {
|
||||||
sessionId: "sess-main-webchat-internal",
|
sessionId: "sess-main-webchat-internal",
|
||||||
updatedAt: Date.now(),
|
updatedAt: Date.now(),
|
||||||
lastChannel: "webchat",
|
lastChannel: "webchat",
|
||||||
lastTo: "+1555",
|
lastTo: "+1555",
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const { server, ws } = await startServerWithClient();
|
const { server, ws } = await startServerWithClient();
|
||||||
await connectOk(ws);
|
await connectOk(ws);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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"] }],
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 =
|
||||||
|
|||||||
@@ -110,8 +110,8 @@ export const registerTelegramNativeCommands = ({
|
|||||||
|
|
||||||
if (allCommands.length > 0) {
|
if (allCommands.length > 0) {
|
||||||
bot.api.setMyCommands(allCommands).catch((err) => {
|
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") {
|
if (typeof (bot as unknown as { command?: unknown }).command !== "function") {
|
||||||
logVerbose("telegram: bot.command unavailable; skipping native handlers");
|
logVerbose("telegram: bot.command unavailable; skipping native handlers");
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user