fix: refine tool summaries and scope discord tool

This commit is contained in:
Peter Steinberger
2026-01-03 12:33:42 +01:00
parent 7165c8a7e5
commit 772ada4308
13 changed files with 83 additions and 11 deletions

View File

@@ -310,6 +310,7 @@ function resolvePromptSkills(
export async function runEmbeddedPiAgent(params: {
sessionId: string;
sessionKey?: string;
surface?: string;
sessionFile: string;
workspaceDir: string;
config?: ClawdisConfig;
@@ -414,6 +415,7 @@ export async function runEmbeddedPiAgent(params: {
const promptSkills = resolvePromptSkills(skillsSnapshot, skillEntries);
const tools = createClawdisCodingTools({
bash: params.config?.agent?.bash,
surface: params.surface,
});
const machineName = await getMachineDisplayName();
const runtimeInfo = {

View File

@@ -83,6 +83,14 @@ describe("createClawdisCodingTools", () => {
expect(tools.some((tool) => tool.name === "process")).toBe(true);
});
it("scopes discord tool to discord surface", () => {
const other = createClawdisCodingTools({ surface: "whatsapp" });
expect(other.some((tool) => tool.name === "discord")).toBe(false);
const discord = createClawdisCodingTools({ surface: "discord" });
expect(discord.some((tool) => tool.name === "discord")).toBe(true);
});
it("keeps read tool image metadata intact", async () => {
const tools = createClawdisCodingTools();
const readTool = tools.find((tool) => tool.name === "read");

View File

@@ -294,8 +294,20 @@ function createClawdisReadTool(base: AnyAgentTool): AnyAgentTool {
};
}
function normalizeSurface(surface?: string): string | undefined {
const trimmed = surface?.trim().toLowerCase();
return trimmed ? trimmed : undefined;
}
function shouldIncludeDiscordTool(surface?: string): boolean {
const normalized = normalizeSurface(surface);
if (!normalized) return false;
return normalized === "discord" || normalized.startsWith("discord:");
}
export function createClawdisCodingTools(options?: {
bash?: BashToolDefaults & ProcessToolDefaults;
surface?: string;
}): AnyAgentTool[] {
const bashToolName = "bash";
const base = (codingTools as unknown as AnyAgentTool[]).flatMap((tool) => {
@@ -314,5 +326,9 @@ export function createClawdisCodingTools(options?: {
createWhatsAppLoginTool(),
...createClawdisTools(),
];
return tools.map(normalizeToolParameters);
const allowDiscord = shouldIncludeDiscordTool(options?.surface);
const filtered = allowDiscord
? tools
: tools.filter((tool) => tool.name !== "discord");
return filtered.map(normalizeToolParameters);
}

View File

@@ -105,6 +105,7 @@ type FollowupRun = {
run: {
sessionId: string;
sessionKey?: string;
surface?: string;
sessionFile: string;
workspaceDir: string;
config: ClawdisConfig;
@@ -1871,6 +1872,7 @@ export async function getReplyFromConfig(
run: {
sessionId: sessionIdFinal,
sessionKey,
surface: sessionCtx.Surface?.trim().toLowerCase() || undefined,
sessionFile,
workspaceDir,
config: cfg,
@@ -1942,6 +1944,7 @@ export async function getReplyFromConfig(
runResult = await runEmbeddedPiAgent({
sessionId: queued.run.sessionId,
sessionKey: queued.run.sessionKey,
surface: queued.run.surface,
sessionFile: queued.run.sessionFile,
workspaceDir: queued.run.workspaceDir,
config: queued.run.config,
@@ -2061,6 +2064,7 @@ export async function getReplyFromConfig(
runResult = await runEmbeddedPiAgent({
sessionId: sessionIdFinal,
sessionKey,
surface: sessionCtx.Surface?.trim().toLowerCase() || undefined,
sessionFile,
workspaceDir,
config: cfg,

View File

@@ -35,7 +35,7 @@ describe("tool meta formatting", () => {
"note",
"a→b",
]);
expect(out).toMatch(/^\[🛠️ fs]/);
expect(out).toMatch(/^🛠️ fs/);
expect(out).toContain("~/dir/{a.txt, b.txt}");
expect(out).toContain("note");
expect(out).toContain("a→b");
@@ -43,8 +43,8 @@ describe("tool meta formatting", () => {
it("formats prefixes with default labels", () => {
vi.stubEnv("HOME", "/Users/test");
expect(formatToolPrefix(undefined, undefined)).toBe("[🛠️ tool]");
expect(formatToolPrefix("x", "/Users/test/a.txt")).toBe("[🛠️ x ~/a.txt]");
expect(formatToolPrefix(undefined, undefined)).toBe("🛠️ tool");
expect(formatToolPrefix("x", "/Users/test/a.txt")).toBe("🛠️ x: ~/a.txt");
});
});

View File

@@ -1,6 +1,28 @@
export const TOOL_RESULT_DEBOUNCE_MS = 500;
export const TOOL_RESULT_FLUSH_COUNT = 5;
const TOOL_EMOJI_BY_NAME: Record<string, string> = {
bash: "💻",
process: "🧰",
read: "📖",
write: "✍️",
edit: "📝",
attach: "📎",
clawdis_browser: "🌐",
clawdis_canvas: "🖼️",
clawdis_nodes: "📱",
clawdis_cron: "⏰",
clawdis_gateway: "🔌",
whatsapp_login: "🟢",
discord: "💬",
};
function resolveToolEmoji(toolName?: string): string {
const key = toolName?.trim().toLowerCase();
if (key && TOOL_EMOJI_BY_NAME[key]) return TOOL_EMOJI_BY_NAME[key];
return "🛠️";
}
export function shortenPath(p: string): string {
const home = process.env.HOME;
if (home && (p === home || p.startsWith(`${home}/`)))
@@ -23,7 +45,7 @@ export function formatToolAggregate(
): string {
const filtered = (metas ?? []).filter(Boolean).map(shortenMeta);
const label = toolName?.trim() || "tool";
const prefix = `[🛠️ ${label}]`;
const prefix = `${resolveToolEmoji(label)} ${label}`;
if (!filtered.length) return prefix;
const rawSegments: string[] = [];
@@ -53,13 +75,14 @@ export function formatToolAggregate(
});
const allSegments = [...rawSegments, ...segments];
return `${prefix} ${allSegments.join("; ")}`;
return `${prefix}: ${allSegments.join("; ")}`;
}
export function formatToolPrefix(toolName?: string, meta?: string) {
const label = toolName?.trim() || "tool";
const emoji = resolveToolEmoji(label);
const extra = meta?.trim() ? shortenMeta(meta) : undefined;
return extra ? `[🛠️ ${label} ${extra}]` : `[🛠️ ${label}]`;
return extra ? `${emoji} ${label}: ${extra}` : `${emoji} ${label}`;
}
export function createToolDebouncer(

View File

@@ -329,9 +329,17 @@ export async function agentCommand(
let result: Awaited<ReturnType<typeof runEmbeddedPiAgent>>;
try {
const surface =
opts.surface?.trim().toLowerCase() ||
(() => {
const raw = opts.provider?.trim().toLowerCase();
if (!raw) return undefined;
return raw === "imsg" ? "imessage" : raw;
})();
result = await runEmbeddedPiAgent({
sessionId,
sessionKey,
surface,
sessionFile,
workspaceDir,
config: cfg,

View File

@@ -255,9 +255,16 @@ export async function runCronIsolatedAgentTurn(params: {
registerAgentRunContext(cronSession.sessionEntry.sessionId, {
sessionKey: params.sessionKey,
});
const surface =
resolvedDelivery.channel &&
resolvedDelivery.channel !== "last" &&
resolvedDelivery.channel !== "none"
? resolvedDelivery.channel
: undefined;
runResult = await runEmbeddedPiAgent({
sessionId: cronSession.sessionEntry.sessionId,
sessionKey: params.sessionKey,
surface,
sessionFile,
workspaceDir,
config: params.cfg,

View File

@@ -1591,8 +1591,8 @@ describe("web auto-reply", () => {
_ctx,
opts?: { onToolResult?: (r: { text: string }) => Promise<void> },
) => {
await opts?.onToolResult?.({ text: "[🛠️ tool1]" });
await opts?.onToolResult?.({ text: "[🛠️ tool2]" });
await opts?.onToolResult?.({ text: "🛠️ tool1" });
await opts?.onToolResult?.({ text: "🛠️ tool2" });
return { text: "final" };
},
);
@@ -1611,7 +1611,7 @@ describe("web auto-reply", () => {
});
const replies = reply.mock.calls.map((call) => call[0]);
expect(replies).toEqual(["🦞 [🛠️ tool1]", "🦞 [🛠️ tool2]", "🦞 final"]);
expect(replies).toEqual(["🦞 🛠️ tool1", "🦞 🛠️ tool2", "🦞 final"]);
resetLoadConfigMock();
});
});