fix: gate tool-error fallback replies (#1175) (thanks @vrknetha)
This commit is contained in:
parent
f039fdf8e9
commit
ace906539f
@ -17,6 +17,7 @@ Docs: https://docs.clawd.bot
|
||||
- macOS: avoid touching launchd in Remote over SSH so quitting the app no longer disables the remote gateway. (#1105)
|
||||
- Memory: index atomically so failed reindex preserves the previous memory database. (#1151) — thanks @gumadeiras.
|
||||
- Memory: avoid sqlite-vec unique constraint failures when reindexing duplicate chunk ids. (#1151) — thanks @gumadeiras.
|
||||
- Agents: surface tool failures when no assistant output is emitted. (#1175) — thanks @vrknetha.
|
||||
|
||||
## 2026.1.18-3
|
||||
|
||||
|
||||
@ -432,6 +432,7 @@ export async function runEmbeddedPiAgent(
|
||||
toolMetas: attempt.toolMetas,
|
||||
lastAssistant: attempt.lastAssistant,
|
||||
lastToolError: attempt.lastToolError,
|
||||
didSendViaMessagingTool: attempt.didSendViaMessagingTool,
|
||||
config: params.config,
|
||||
sessionKey: params.sessionKey ?? params.sessionId,
|
||||
verboseLevel: params.verboseLevel,
|
||||
|
||||
@ -147,4 +147,40 @@ describe("buildEmbeddedRunPayloads", () => {
|
||||
expect(payloads).toHaveLength(1);
|
||||
expect(payloads[0]?.text).toBe("All good");
|
||||
});
|
||||
|
||||
it("adds tool error fallback when assistant output is NO_REPLY", () => {
|
||||
const payloads = buildEmbeddedRunPayloads({
|
||||
assistantTexts: ["NO_REPLY"],
|
||||
toolMetas: [],
|
||||
lastAssistant: { stopReason: "end_turn" } as AssistantMessage,
|
||||
lastToolError: { toolName: "browser", error: "tab not found" },
|
||||
sessionKey: "session:telegram",
|
||||
inlineToolResultsAllowed: false,
|
||||
verboseLevel: "off",
|
||||
reasoningLevel: "off",
|
||||
toolResultFormat: "plain",
|
||||
});
|
||||
|
||||
expect(payloads).toHaveLength(1);
|
||||
expect(payloads[0]?.isError).toBe(true);
|
||||
expect(payloads[0]?.text).toContain("browser");
|
||||
expect(payloads[0]?.text).toContain("tab not found");
|
||||
});
|
||||
|
||||
it("skips tool error fallback when messaging tool already sent", () => {
|
||||
const payloads = buildEmbeddedRunPayloads({
|
||||
assistantTexts: [],
|
||||
toolMetas: [],
|
||||
lastAssistant: undefined,
|
||||
lastToolError: { toolName: "browser", error: "tab not found" },
|
||||
didSendViaMessagingTool: true,
|
||||
sessionKey: "session:telegram",
|
||||
inlineToolResultsAllowed: false,
|
||||
verboseLevel: "off",
|
||||
reasoningLevel: "off",
|
||||
toolResultFormat: "plain",
|
||||
});
|
||||
|
||||
expect(payloads).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
@ -24,6 +24,7 @@ export function buildEmbeddedRunPayloads(params: {
|
||||
toolMetas: ToolMetaEntry[];
|
||||
lastAssistant: AssistantMessage | undefined;
|
||||
lastToolError?: { toolName: string; meta?: string; error?: string };
|
||||
didSendViaMessagingTool?: boolean;
|
||||
config?: ClawdbotConfig;
|
||||
sessionKey: string;
|
||||
verboseLevel?: VerboseLevel;
|
||||
@ -156,34 +157,46 @@ export function buildEmbeddedRunPayloads(params: {
|
||||
});
|
||||
}
|
||||
|
||||
if (replyItems.length === 0 && params.lastToolError) {
|
||||
const buildPayloads = (items: typeof replyItems) => {
|
||||
const hasAudioAsVoiceTag = items.some((item) => item.audioAsVoice);
|
||||
return items
|
||||
.map((item) => ({
|
||||
text: item.text?.trim() ? item.text.trim() : undefined,
|
||||
mediaUrls: item.media?.length ? item.media : undefined,
|
||||
mediaUrl: item.media?.[0],
|
||||
isError: item.isError,
|
||||
replyToId: item.replyToId,
|
||||
replyToTag: item.replyToTag,
|
||||
replyToCurrent: item.replyToCurrent,
|
||||
audioAsVoice: item.audioAsVoice || Boolean(hasAudioAsVoiceTag && item.media?.length),
|
||||
}))
|
||||
.filter((p) => {
|
||||
if (!p.text && !p.mediaUrl && (!p.mediaUrls || p.mediaUrls.length === 0)) return false;
|
||||
if (p.text && isSilentReplyText(p.text, SILENT_REPLY_TOKEN)) return false;
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
let payloads = buildPayloads(replyItems);
|
||||
|
||||
if (
|
||||
payloads.length === 0 &&
|
||||
params.lastToolError &&
|
||||
params.didSendViaMessagingTool !== true
|
||||
) {
|
||||
const toolSummary = formatToolAggregate(
|
||||
params.lastToolError.toolName,
|
||||
params.lastToolError.meta ? [params.lastToolError.meta] : undefined,
|
||||
{ markdown: useMarkdown },
|
||||
);
|
||||
const errorSuffix = params.lastToolError.error ? `: ${params.lastToolError.error}` : "";
|
||||
replyItems.push({
|
||||
text: `⚠️ ${toolSummary} failed${errorSuffix}`,
|
||||
isError: true,
|
||||
});
|
||||
payloads = buildPayloads([
|
||||
{
|
||||
text: `⚠️ ${toolSummary} failed${errorSuffix}`,
|
||||
isError: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
const hasAudioAsVoiceTag = replyItems.some((item) => item.audioAsVoice);
|
||||
return replyItems
|
||||
.map((item) => ({
|
||||
text: item.text?.trim() ? item.text.trim() : undefined,
|
||||
mediaUrls: item.media?.length ? item.media : undefined,
|
||||
mediaUrl: item.media?.[0],
|
||||
isError: item.isError,
|
||||
replyToId: item.replyToId,
|
||||
replyToTag: item.replyToTag,
|
||||
replyToCurrent: item.replyToCurrent,
|
||||
audioAsVoice: item.audioAsVoice || Boolean(hasAudioAsVoiceTag && item.media?.length),
|
||||
}))
|
||||
.filter((p) => {
|
||||
if (!p.text && !p.mediaUrl && (!p.mediaUrls || p.mediaUrls.length === 0)) return false;
|
||||
if (p.text && isSilentReplyText(p.text, SILENT_REPLY_TOKEN)) return false;
|
||||
return true;
|
||||
});
|
||||
return payloads;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user