Agents: harden message tool sends
This commit is contained in:
committed by
Peter Steinberger
parent
55da6ca449
commit
3da3e201de
@@ -96,6 +96,17 @@ function sanitizeToolResult(result: unknown): unknown {
|
|||||||
return { ...record, content: sanitized };
|
return { ...record, content: sanitized };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isToolResultError(result: unknown): boolean {
|
||||||
|
if (!result || typeof result !== "object") return false;
|
||||||
|
const record = result as { details?: unknown };
|
||||||
|
const details = record.details;
|
||||||
|
if (!details || typeof details !== "object") return false;
|
||||||
|
const status = (details as { status?: unknown }).status;
|
||||||
|
if (typeof status !== "string") return false;
|
||||||
|
const normalized = status.trim().toLowerCase();
|
||||||
|
return normalized === "error" || normalized === "timeout";
|
||||||
|
}
|
||||||
|
|
||||||
function stripThinkingSegments(text: string): string {
|
function stripThinkingSegments(text: string): string {
|
||||||
if (!text || !THINKING_TAG_RE.test(text)) return text;
|
if (!text || !THINKING_TAG_RE.test(text)) return text;
|
||||||
THINKING_TAG_RE.lastIndex = 0;
|
THINKING_TAG_RE.lastIndex = 0;
|
||||||
@@ -613,6 +624,7 @@ export function subscribeEmbeddedPiSession(params: {
|
|||||||
(evt as AgentEvent & { isError: boolean }).isError,
|
(evt as AgentEvent & { isError: boolean }).isError,
|
||||||
);
|
);
|
||||||
const result = (evt as AgentEvent & { result?: unknown }).result;
|
const result = (evt as AgentEvent & { result?: unknown }).result;
|
||||||
|
const isToolError = isError || isToolResultError(result);
|
||||||
const sanitizedResult = sanitizeToolResult(result);
|
const sanitizedResult = sanitizeToolResult(result);
|
||||||
const meta = toolMetaById.get(toolCallId);
|
const meta = toolMetaById.get(toolCallId);
|
||||||
toolMetas.push({ toolName, meta });
|
toolMetas.push({ toolName, meta });
|
||||||
@@ -624,7 +636,7 @@ export function subscribeEmbeddedPiSession(params: {
|
|||||||
const pendingTarget = pendingMessagingTargets.get(toolCallId);
|
const pendingTarget = pendingMessagingTargets.get(toolCallId);
|
||||||
if (pendingText) {
|
if (pendingText) {
|
||||||
pendingMessagingTexts.delete(toolCallId);
|
pendingMessagingTexts.delete(toolCallId);
|
||||||
if (!isError) {
|
if (!isToolError) {
|
||||||
messagingToolSentTexts.push(pendingText);
|
messagingToolSentTexts.push(pendingText);
|
||||||
messagingToolSentTextsNormalized.push(
|
messagingToolSentTextsNormalized.push(
|
||||||
normalizeTextForComparison(pendingText),
|
normalizeTextForComparison(pendingText),
|
||||||
@@ -637,7 +649,7 @@ export function subscribeEmbeddedPiSession(params: {
|
|||||||
}
|
}
|
||||||
if (pendingTarget) {
|
if (pendingTarget) {
|
||||||
pendingMessagingTargets.delete(toolCallId);
|
pendingMessagingTargets.delete(toolCallId);
|
||||||
if (!isError) {
|
if (!isToolError) {
|
||||||
messagingToolSentTargets.push(pendingTarget);
|
messagingToolSentTargets.push(pendingTarget);
|
||||||
trimMessagingToolSent();
|
trimMessagingToolSent();
|
||||||
}
|
}
|
||||||
@@ -651,7 +663,7 @@ export function subscribeEmbeddedPiSession(params: {
|
|||||||
name: toolName,
|
name: toolName,
|
||||||
toolCallId,
|
toolCallId,
|
||||||
meta,
|
meta,
|
||||||
isError,
|
isError: isToolError,
|
||||||
result: sanitizedResult,
|
result: sanitizedResult,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -662,7 +674,7 @@ export function subscribeEmbeddedPiSession(params: {
|
|||||||
name: toolName,
|
name: toolName,
|
||||||
toolCallId,
|
toolCallId,
|
||||||
meta,
|
meta,
|
||||||
isError,
|
isError: isToolError,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -313,6 +313,7 @@ export function buildAgentSystemPrompt(params: {
|
|||||||
"",
|
"",
|
||||||
"### message tool",
|
"### message tool",
|
||||||
"- Use `message` for proactive sends + provider actions (polls, reactions, etc.).",
|
"- Use `message` for proactive sends + provider actions (polls, reactions, etc.).",
|
||||||
|
"- For `action=send`, include `to` and `message`.",
|
||||||
"- If multiple providers are configured, pass `provider` (whatsapp|telegram|discord|slack|signal|imessage|msteams).",
|
"- If multiple providers are configured, pass `provider` (whatsapp|telegram|discord|slack|signal|imessage|msteams).",
|
||||||
telegramInlineButtonsEnabled
|
telegramInlineButtonsEnabled
|
||||||
? "- Telegram: inline buttons supported. Use `action=send` with `buttons=[[{text,callback_data}]]` (callback_data routes back as a user message)."
|
? "- Telegram: inline buttons supported. Use `action=send` with `buttons=[[{text,callback_data}]]` (callback_data routes back as a user message)."
|
||||||
|
|||||||
@@ -27,45 +27,43 @@ import { handleSlackAction } from "./slack-actions.js";
|
|||||||
import { handleTelegramAction } from "./telegram-actions.js";
|
import { handleTelegramAction } from "./telegram-actions.js";
|
||||||
import { handleWhatsAppAction } from "./whatsapp-actions.js";
|
import { handleWhatsAppAction } from "./whatsapp-actions.js";
|
||||||
|
|
||||||
const MessageActionSchema = Type.Union([
|
const AllMessageActions = [
|
||||||
Type.Literal("send"),
|
"send",
|
||||||
Type.Literal("poll"),
|
"poll",
|
||||||
Type.Literal("react"),
|
"react",
|
||||||
Type.Literal("reactions"),
|
"reactions",
|
||||||
Type.Literal("read"),
|
"read",
|
||||||
Type.Literal("edit"),
|
"edit",
|
||||||
Type.Literal("delete"),
|
"delete",
|
||||||
Type.Literal("pin"),
|
"pin",
|
||||||
Type.Literal("unpin"),
|
"unpin",
|
||||||
Type.Literal("list-pins"),
|
"list-pins",
|
||||||
Type.Literal("permissions"),
|
"permissions",
|
||||||
Type.Literal("thread-create"),
|
"thread-create",
|
||||||
Type.Literal("thread-list"),
|
"thread-list",
|
||||||
Type.Literal("thread-reply"),
|
"thread-reply",
|
||||||
Type.Literal("search"),
|
"search",
|
||||||
Type.Literal("sticker"),
|
"sticker",
|
||||||
Type.Literal("member-info"),
|
"member-info",
|
||||||
Type.Literal("role-info"),
|
"role-info",
|
||||||
Type.Literal("emoji-list"),
|
"emoji-list",
|
||||||
Type.Literal("emoji-upload"),
|
"emoji-upload",
|
||||||
Type.Literal("sticker-upload"),
|
"sticker-upload",
|
||||||
Type.Literal("role-add"),
|
"role-add",
|
||||||
Type.Literal("role-remove"),
|
"role-remove",
|
||||||
Type.Literal("channel-info"),
|
"channel-info",
|
||||||
Type.Literal("channel-list"),
|
"channel-list",
|
||||||
Type.Literal("voice-status"),
|
"voice-status",
|
||||||
Type.Literal("event-list"),
|
"event-list",
|
||||||
Type.Literal("event-create"),
|
"event-create",
|
||||||
Type.Literal("timeout"),
|
"timeout",
|
||||||
Type.Literal("kick"),
|
"kick",
|
||||||
Type.Literal("ban"),
|
"ban",
|
||||||
]);
|
];
|
||||||
|
|
||||||
const MessageToolSchema = Type.Object({
|
|
||||||
action: MessageActionSchema,
|
const MessageToolCommonSchema = {
|
||||||
provider: Type.Optional(Type.String()),
|
provider: Type.Optional(Type.String()),
|
||||||
to: Type.Optional(Type.String()),
|
|
||||||
message: Type.Optional(Type.String()),
|
|
||||||
media: Type.Optional(Type.String()),
|
media: Type.Optional(Type.String()),
|
||||||
buttons: Type.Optional(
|
buttons: Type.Optional(
|
||||||
Type.Array(
|
Type.Array(
|
||||||
@@ -129,6 +127,46 @@ const MessageToolSchema = Type.Object({
|
|||||||
gatewayUrl: Type.Optional(Type.String()),
|
gatewayUrl: Type.Optional(Type.String()),
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
gatewayToken: Type.Optional(Type.String()),
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
timeoutMs: Type.Optional(Type.Number()),
|
||||||
|
};
|
||||||
|
|
||||||
|
function buildMessageToolSchemaFromActions(
|
||||||
|
actions: string[],
|
||||||
|
options: { includeButtons: boolean },
|
||||||
|
) {
|
||||||
|
const props: Record<string, unknown> = { ...MessageToolCommonSchema };
|
||||||
|
if (!options.includeButtons) delete props.buttons;
|
||||||
|
|
||||||
|
const schemas: Array<ReturnType<typeof Type.Object>> = [];
|
||||||
|
if (actions.includes("send")) {
|
||||||
|
schemas.push(
|
||||||
|
Type.Object({
|
||||||
|
action: Type.Literal("send"),
|
||||||
|
to: Type.String(),
|
||||||
|
message: Type.String(),
|
||||||
|
...props,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nonSendActions = actions.filter((action) => action !== "send");
|
||||||
|
if (nonSendActions.length > 0) {
|
||||||
|
schemas.push(
|
||||||
|
Type.Object({
|
||||||
|
action: Type.Union(
|
||||||
|
nonSendActions.map((action) => Type.Literal(action)),
|
||||||
|
),
|
||||||
|
to: Type.Optional(Type.String()),
|
||||||
|
message: Type.Optional(Type.String()),
|
||||||
|
...props,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return schemas.length === 1 ? schemas[0] : Type.Union(schemas);
|
||||||
|
}
|
||||||
|
|
||||||
|
const MessageToolSchema = buildMessageToolSchemaFromActions(AllMessageActions, {
|
||||||
|
includeButtons: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
type MessageToolOptions = {
|
type MessageToolOptions = {
|
||||||
@@ -164,7 +202,7 @@ function hasTelegramInlineButtons(cfg: ClawdbotConfig): boolean {
|
|||||||
return caps.has("inlinebuttons");
|
return caps.has("inlinebuttons");
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildMessageActionSchema(cfg: ClawdbotConfig) {
|
function buildMessageActionList(cfg: ClawdbotConfig) {
|
||||||
const actions = new Set<string>(["send"]);
|
const actions = new Set<string>(["send"]);
|
||||||
|
|
||||||
const discordAccounts = listEnabledDiscordAccounts(cfg).filter(
|
const discordAccounts = listEnabledDiscordAccounts(cfg).filter(
|
||||||
@@ -281,25 +319,16 @@ function buildMessageActionSchema(cfg: ClawdbotConfig) {
|
|||||||
actions.add("ban");
|
actions.add("ban");
|
||||||
}
|
}
|
||||||
|
|
||||||
return Type.Union(Array.from(actions).map((action) => Type.Literal(action)));
|
return Array.from(actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildMessageToolSchema(cfg: ClawdbotConfig) {
|
function buildMessageToolSchema(cfg: ClawdbotConfig) {
|
||||||
const base = MessageToolSchema as unknown as Record<string, unknown>;
|
const actions = buildMessageActionList(cfg);
|
||||||
const baseProps = (base.properties ?? {}) as Record<string, unknown>;
|
|
||||||
const props: Record<string, unknown> = {
|
|
||||||
...baseProps,
|
|
||||||
action: buildMessageActionSchema(cfg),
|
|
||||||
};
|
|
||||||
|
|
||||||
const telegramEnabled = listEnabledTelegramAccounts(cfg).some(
|
const telegramEnabled = listEnabledTelegramAccounts(cfg).some(
|
||||||
(account) => account.tokenSource !== "none",
|
(account) => account.tokenSource !== "none",
|
||||||
);
|
);
|
||||||
if (!telegramEnabled || !hasTelegramInlineButtons(cfg)) {
|
const includeButtons = telegramEnabled && hasTelegramInlineButtons(cfg);
|
||||||
delete props.buttons;
|
return buildMessageToolSchemaFromActions(actions, { includeButtons });
|
||||||
}
|
|
||||||
|
|
||||||
return { ...base, properties: props };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveAgentAccountId(value?: string): string | undefined {
|
function resolveAgentAccountId(value?: string): string | undefined {
|
||||||
|
|||||||
Reference in New Issue
Block a user