auto-reply: handle empty stdout gracefully
parent
5b17aba4fc
commit
716f31f17a
|
|
@ -290,23 +290,14 @@ const mediaNote =
|
||||||
);
|
);
|
||||||
const rawStdout = stdout.trim();
|
const rawStdout = stdout.trim();
|
||||||
let mediaFromCommand: string | undefined;
|
let mediaFromCommand: string | undefined;
|
||||||
const { text: trimmedText, mediaUrl: mediaDirect } =
|
let trimmed = rawStdout;
|
||||||
splitMediaFromOutput(rawStdout);
|
|
||||||
mediaFromCommand = mediaDirect;
|
|
||||||
if (isVerbose()) {
|
|
||||||
logVerbose(
|
|
||||||
mediaFromCommand
|
|
||||||
? `MEDIA token extracted from stdout: ${mediaFromCommand}`
|
|
||||||
: "No MEDIA token extracted from stdout",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let trimmed = trimmedText;
|
|
||||||
if (stderr?.trim()) {
|
if (stderr?.trim()) {
|
||||||
logVerbose(`Command auto-reply stderr: ${stderr.trim()}`);
|
logVerbose(`Command auto-reply stderr: ${stderr.trim()}`);
|
||||||
}
|
}
|
||||||
|
let parsed: ClaudeJsonParseResult | undefined;
|
||||||
if (trimmed && (reply.claudeOutputFormat === "json" || isClaudeInvocation)) {
|
if (trimmed && (reply.claudeOutputFormat === "json" || isClaudeInvocation)) {
|
||||||
// Claude JSON mode: extract the human text for both logging and reply while keeping metadata.
|
// Claude JSON mode: extract the human text for both logging and reply while keeping metadata.
|
||||||
const parsed = parseClaudeJson(trimmed);
|
parsed = parseClaudeJson(trimmed);
|
||||||
if (parsed?.parsed && isVerbose()) {
|
if (parsed?.parsed && isVerbose()) {
|
||||||
const summary = summarizeClaudeMetadata(parsed.parsed);
|
const summary = summarizeClaudeMetadata(parsed.parsed);
|
||||||
if (summary) logVerbose(`Claude JSON meta: ${summary}`);
|
if (summary) logVerbose(`Claude JSON meta: ${summary}`);
|
||||||
|
|
@ -319,21 +310,25 @@ const mediaNote =
|
||||||
`Claude JSON parsed -> ${parsed.text.slice(0, 120)}${parsed.text.length > 120 ? "…" : ""}`,
|
`Claude JSON parsed -> ${parsed.text.slice(0, 120)}${parsed.text.length > 120 ? "…" : ""}`,
|
||||||
);
|
);
|
||||||
trimmed = parsed.text.trim();
|
trimmed = parsed.text.trim();
|
||||||
if (!mediaFromCommand) {
|
|
||||||
const { mediaUrl: mediaFromParsed } = splitMediaFromOutput(
|
|
||||||
parsed.text,
|
|
||||||
);
|
|
||||||
if (mediaFromParsed) {
|
|
||||||
mediaFromCommand = mediaFromParsed;
|
|
||||||
logVerbose(
|
|
||||||
`MEDIA token extracted after JSON parse: ${mediaFromParsed}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
logVerbose("Claude JSON parse failed; returning raw stdout");
|
logVerbose("Claude JSON parse failed; returning raw stdout");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Run media extraction once on the final human text (post-JSON parse if available).
|
||||||
|
const { text: cleanedText, mediaUrl: mediaFound } =
|
||||||
|
splitMediaFromOutput(trimmed);
|
||||||
|
trimmed = cleanedText;
|
||||||
|
if (mediaFound) {
|
||||||
|
mediaFromCommand = mediaFound;
|
||||||
|
if (isVerbose()) logVerbose(`MEDIA token extracted: ${mediaFound}`);
|
||||||
|
} else if (isVerbose()) {
|
||||||
|
logVerbose("No MEDIA token extracted from final text");
|
||||||
|
}
|
||||||
|
if (!trimmed && !mediaFromCommand) {
|
||||||
|
const meta = parsed ? summarizeClaudeMetadata(parsed.parsed) : undefined;
|
||||||
|
trimmed = `(command produced no output${meta ? `; ${meta}` : ""})`;
|
||||||
|
logVerbose("No text/media produced; injecting fallback notice to user");
|
||||||
|
}
|
||||||
logVerbose(
|
logVerbose(
|
||||||
`Command auto-reply stdout (trimmed): ${trimmed || "<empty>"}`,
|
`Command auto-reply stdout (trimmed): ${trimmed || "<empty>"}`,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -274,6 +274,32 @@ describe("config and templating", () => {
|
||||||
expect(result?.mediaUrl).toBeUndefined();
|
expect(result?.mediaUrl).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("injects fallback text when command returns nothing", async () => {
|
||||||
|
const runSpy = vi.spyOn(index, "runCommandWithTimeout").mockResolvedValue({
|
||||||
|
stdout: "",
|
||||||
|
stderr: "",
|
||||||
|
code: 0,
|
||||||
|
signal: null,
|
||||||
|
killed: false,
|
||||||
|
});
|
||||||
|
const cfg = {
|
||||||
|
inbound: {
|
||||||
|
reply: {
|
||||||
|
mode: "command" as const,
|
||||||
|
command: ["echo", "{{Body}}"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const result = await index.getReplyFromConfig(
|
||||||
|
{ Body: "hi", From: "+1", To: "+2" },
|
||||||
|
undefined,
|
||||||
|
cfg,
|
||||||
|
runSpy,
|
||||||
|
);
|
||||||
|
expect(result?.text).toContain("command produced no output");
|
||||||
|
expect(result?.mediaUrl).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
it("splitMediaFromOutput strips media token and preserves text", () => {
|
it("splitMediaFromOutput strips media token and preserves text", () => {
|
||||||
const { text, mediaUrl } = splitMediaFromOutput(
|
const { text, mediaUrl } = splitMediaFromOutput(
|
||||||
"line1\nMEDIA:https://x/y.png\nline2",
|
"line1\nMEDIA:https://x/y.png\nline2",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue