fix: normalize exec tool alias naming
This commit is contained in:
@@ -73,6 +73,7 @@ Docs: https://docs.clawd.bot
|
|||||||
- macOS: drain subprocess pipes before waiting to avoid deadlocks. (#1081) — thanks @thesash.
|
- macOS: drain subprocess pipes before waiting to avoid deadlocks. (#1081) — thanks @thesash.
|
||||||
- Verbose: wrap tool summaries/output in markdown only for markdown-capable channels.
|
- Verbose: wrap tool summaries/output in markdown only for markdown-capable channels.
|
||||||
- Tools: include provider/session context in elevated exec denial errors.
|
- Tools: include provider/session context in elevated exec denial errors.
|
||||||
|
- Tools: normalize exec tool alias naming in tool error logs.
|
||||||
- Telegram: accept tg/group/telegram prefixes + topic targets for inline button validation. (#1072) — thanks @danielz1z.
|
- Telegram: accept tg/group/telegram prefixes + topic targets for inline button validation. (#1072) — thanks @danielz1z.
|
||||||
- Telegram: split long captions into follow-up messages.
|
- Telegram: split long captions into follow-up messages.
|
||||||
- Config: block startup on invalid config, preserve best-effort doctor config, and keep rolling config backups. (#1083) — thanks @mukhtharcm.
|
- Config: block startup on invalid config, preserve best-effort doctor config, and keep rolling config backups. (#1083) — thanks @mukhtharcm.
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
sanitizeToolResult,
|
sanitizeToolResult,
|
||||||
} from "./pi-embedded-subscribe.tools.js";
|
} from "./pi-embedded-subscribe.tools.js";
|
||||||
import { inferToolMetaFromArgs } from "./pi-embedded-utils.js";
|
import { inferToolMetaFromArgs } from "./pi-embedded-utils.js";
|
||||||
|
import { normalizeToolName } from "./tool-policy.js";
|
||||||
|
|
||||||
function extendExecMeta(toolName: string, args: unknown, meta?: string): string | undefined {
|
function extendExecMeta(toolName: string, args: unknown, meta?: string): string | undefined {
|
||||||
const normalized = toolName.trim().toLowerCase();
|
const normalized = toolName.trim().toLowerCase();
|
||||||
@@ -35,7 +36,8 @@ export async function handleToolExecutionStart(
|
|||||||
void ctx.params.onBlockReplyFlush();
|
void ctx.params.onBlockReplyFlush();
|
||||||
}
|
}
|
||||||
|
|
||||||
const toolName = String(evt.toolName);
|
const rawToolName = String(evt.toolName);
|
||||||
|
const toolName = normalizeToolName(rawToolName);
|
||||||
const toolCallId = String(evt.toolCallId);
|
const toolCallId = String(evt.toolCallId);
|
||||||
const args = evt.args;
|
const args = evt.args;
|
||||||
|
|
||||||
@@ -109,7 +111,7 @@ export function handleToolExecutionUpdate(
|
|||||||
partialResult?: unknown;
|
partialResult?: unknown;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const toolName = String(evt.toolName);
|
const toolName = normalizeToolName(String(evt.toolName));
|
||||||
const toolCallId = String(evt.toolCallId);
|
const toolCallId = String(evt.toolCallId);
|
||||||
const partial = evt.partialResult;
|
const partial = evt.partialResult;
|
||||||
const sanitized = sanitizeToolResult(partial);
|
const sanitized = sanitizeToolResult(partial);
|
||||||
@@ -142,7 +144,7 @@ export function handleToolExecutionEnd(
|
|||||||
result?: unknown;
|
result?: unknown;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const toolName = String(evt.toolName);
|
const toolName = normalizeToolName(String(evt.toolName));
|
||||||
const toolCallId = String(evt.toolCallId);
|
const toolCallId = String(evt.toolCallId);
|
||||||
const isError = Boolean(evt.isError);
|
const isError = Boolean(evt.isError);
|
||||||
const result = evt.result;
|
const result = evt.result;
|
||||||
|
|||||||
@@ -25,4 +25,25 @@ describe("pi tool definition adapter", () => {
|
|||||||
expect(result.details).toMatchObject({ error: "nope" });
|
expect(result.details).toMatchObject({ error: "nope" });
|
||||||
expect(JSON.stringify(result.details)).not.toContain("\n at ");
|
expect(JSON.stringify(result.details)).not.toContain("\n at ");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("normalizes exec tool aliases in error results", async () => {
|
||||||
|
const tool = {
|
||||||
|
name: "bash",
|
||||||
|
label: "Bash",
|
||||||
|
description: "throws",
|
||||||
|
parameters: {},
|
||||||
|
execute: async () => {
|
||||||
|
throw new Error("nope");
|
||||||
|
},
|
||||||
|
} satisfies AgentTool<unknown, unknown>;
|
||||||
|
|
||||||
|
const defs = toToolDefinitions([tool]);
|
||||||
|
const result = await defs[0].execute("call2", {}, undefined, undefined);
|
||||||
|
|
||||||
|
expect(result.details).toMatchObject({
|
||||||
|
status: "error",
|
||||||
|
tool: "exec",
|
||||||
|
error: "nope",
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type {
|
|||||||
} from "@mariozechner/pi-agent-core";
|
} from "@mariozechner/pi-agent-core";
|
||||||
import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
|
import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
|
||||||
import { logDebug, logError } from "../logger.js";
|
import { logDebug, logError } from "../logger.js";
|
||||||
|
import { normalizeToolName } from "./tool-policy.js";
|
||||||
import { jsonResult } from "./tools/common.js";
|
import { jsonResult } from "./tools/common.js";
|
||||||
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: TypeBox schema type from pi-agent-core uses a different module instance.
|
// biome-ignore lint/suspicious/noExplicitAny: TypeBox schema type from pi-agent-core uses a different module instance.
|
||||||
@@ -21,70 +22,10 @@ function describeToolExecutionError(err: unknown): {
|
|||||||
return { message: String(err) };
|
return { message: String(err) };
|
||||||
}
|
}
|
||||||
|
|
||||||
function asScalar(value: unknown): string | undefined {
|
|
||||||
if (typeof value === "string") {
|
|
||||||
const trimmed = value.trim();
|
|
||||||
return trimmed ? trimmed : undefined;
|
|
||||||
}
|
|
||||||
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
||||||
if (typeof value === "bigint") return String(value);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function summarizeList(value: unknown): string | undefined {
|
|
||||||
if (!Array.isArray(value)) return undefined;
|
|
||||||
const items = value.map(asScalar).filter((entry): entry is string => Boolean(entry));
|
|
||||||
if (items.length === 0) return undefined;
|
|
||||||
const sample = items.slice(0, 3).join(", ");
|
|
||||||
const suffix = items.length > 3 ? ` (+${items.length - 3})` : "";
|
|
||||||
return `${sample}${suffix}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function looksLikeMemberTarget(value: string): boolean {
|
|
||||||
return /^user:/i.test(value) || value.startsWith("@") || /^<@!?/.test(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function describeMessageToolContext(params: Record<string, unknown>): string | undefined {
|
|
||||||
const action = asScalar(params.action);
|
|
||||||
const channel = asScalar(params.channel);
|
|
||||||
const accountId = asScalar(params.accountId);
|
|
||||||
const guildId = asScalar(params.guildId);
|
|
||||||
const channelId = asScalar(params.channelId);
|
|
||||||
const threadId = asScalar(params.threadId);
|
|
||||||
const messageId = asScalar(params.messageId);
|
|
||||||
const userId = asScalar(params.userId) ?? asScalar(params.authorId) ?? asScalar(params.participant);
|
|
||||||
const target =
|
|
||||||
asScalar(params.target) ??
|
|
||||||
asScalar(params.to) ??
|
|
||||||
summarizeList(params.targets) ??
|
|
||||||
summarizeList(params.target);
|
|
||||||
|
|
||||||
const member =
|
|
||||||
userId ?? (target && looksLikeMemberTarget(target) ? target : undefined) ?? undefined;
|
|
||||||
const pairs: string[] = [];
|
|
||||||
if (action) pairs.push(`action=${action}`);
|
|
||||||
if (channel) pairs.push(`channel=${channel}`);
|
|
||||||
if (accountId) pairs.push(`accountId=${accountId}`);
|
|
||||||
if (member) {
|
|
||||||
pairs.push(`member=${member}`);
|
|
||||||
} else if (target) {
|
|
||||||
pairs.push(`target=${target}`);
|
|
||||||
}
|
|
||||||
if (guildId) pairs.push(`guildId=${guildId}`);
|
|
||||||
if (channelId) pairs.push(`channelId=${channelId}`);
|
|
||||||
if (threadId) pairs.push(`threadId=${threadId}`);
|
|
||||||
if (messageId) pairs.push(`messageId=${messageId}`);
|
|
||||||
return pairs.length > 0 ? pairs.join(" ") : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function describeToolContext(toolName: string, params: Record<string, unknown>): string | undefined {
|
|
||||||
if (toolName === "message") return describeMessageToolContext(params);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toToolDefinitions(tools: AnyAgentTool[]): ToolDefinition[] {
|
export function toToolDefinitions(tools: AnyAgentTool[]): ToolDefinition[] {
|
||||||
return tools.map((tool) => {
|
return tools.map((tool) => {
|
||||||
const name = tool.name || "tool";
|
const name = tool.name || "tool";
|
||||||
|
const normalizedName = normalizeToolName(name);
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
label: tool.label ?? name,
|
label: tool.label ?? name,
|
||||||
@@ -111,14 +52,12 @@ export function toToolDefinitions(tools: AnyAgentTool[]): ToolDefinition[] {
|
|||||||
if (name === "AbortError") throw err;
|
if (name === "AbortError") throw err;
|
||||||
const described = describeToolExecutionError(err);
|
const described = describeToolExecutionError(err);
|
||||||
if (described.stack && described.stack !== described.message) {
|
if (described.stack && described.stack !== described.message) {
|
||||||
logDebug(`tools: ${tool.name} failed stack:\n${described.stack}`);
|
logDebug(`tools: ${normalizedName} failed stack:\n${described.stack}`);
|
||||||
}
|
}
|
||||||
const context = describeToolContext(tool.name, params);
|
logError(`[tools] ${normalizedName} failed: ${described.message}`);
|
||||||
const suffix = context ? ` (${context})` : "";
|
|
||||||
logError(`tools: ${tool.name} failed: ${described.message}${suffix}`);
|
|
||||||
return jsonResult({
|
return jsonResult({
|
||||||
status: "error",
|
status: "error",
|
||||||
tool: tool.name,
|
tool: normalizedName,
|
||||||
error: described.message,
|
error: described.message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user