Auto-reply: send timeout fallback and tests
This commit is contained in:
@@ -3,7 +3,8 @@
|
|||||||
## [Unreleased] 0.1.4
|
## [Unreleased] 0.1.4
|
||||||
|
|
||||||
### Pending
|
### Pending
|
||||||
- (add entries here)
|
- Auto-replies now send a WhatsApp fallback message when a command/Claude run hits the timeout, including up to 800 chars of partial stdout so the user still sees progress.
|
||||||
|
- Added tests covering the new timeout fallback behavior and partial-output truncation.
|
||||||
|
|
||||||
## 0.1.3 — 2025-11-25
|
## 0.1.3 — 2025-11-25
|
||||||
|
|
||||||
|
|||||||
@@ -386,6 +386,14 @@ export async function getReplyFromConfig(
|
|||||||
console.error(
|
console.error(
|
||||||
`Command auto-reply timed out after ${elapsed}ms (limit ${timeoutMs}ms)`,
|
`Command auto-reply timed out after ${elapsed}ms (limit ${timeoutMs}ms)`,
|
||||||
);
|
);
|
||||||
|
const baseMsg = `Command timed out after ${timeoutSeconds}s. Try a shorter prompt or split the request.`;
|
||||||
|
const partial = errorObj.stdout?.trim();
|
||||||
|
const partialSnippet =
|
||||||
|
partial && partial.length > 800 ? `${partial.slice(0, 800)}...` : partial;
|
||||||
|
const text = partialSnippet
|
||||||
|
? `${baseMsg}\n\nPartial output before timeout:\n${partialSnippet}`
|
||||||
|
: baseMsg;
|
||||||
|
return { text };
|
||||||
} else {
|
} else {
|
||||||
logError(
|
logError(
|
||||||
`Command auto-reply failed after ${elapsed}ms: ${String(err)}`,
|
`Command auto-reply failed after ${elapsed}ms: ${String(err)}`,
|
||||||
|
|||||||
@@ -300,6 +300,66 @@ describe("config and templating", () => {
|
|||||||
expect(result?.mediaUrl).toBeUndefined();
|
expect(result?.mediaUrl).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("returns timeout reply with partial stdout snippet", async () => {
|
||||||
|
const partial = "x".repeat(900);
|
||||||
|
const runSpy = vi.fn().mockRejectedValue({
|
||||||
|
killed: true,
|
||||||
|
signal: "SIGKILL",
|
||||||
|
stdout: partial,
|
||||||
|
stderr: "",
|
||||||
|
});
|
||||||
|
const cfg = {
|
||||||
|
inbound: {
|
||||||
|
reply: {
|
||||||
|
mode: "command" as const,
|
||||||
|
command: ["echo", "{{Body}}"],
|
||||||
|
timeoutSeconds: 42,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await index.getReplyFromConfig(
|
||||||
|
{ Body: "hi", From: "+1", To: "+2" },
|
||||||
|
undefined,
|
||||||
|
cfg,
|
||||||
|
runSpy,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result?.text).toContain("Command timed out after 42s");
|
||||||
|
expect(result?.text).toContain("Partial output before timeout");
|
||||||
|
expect(result?.text).toContain(`${partial.slice(0, 800)}...`);
|
||||||
|
expect(result?.text).not.toContain(partial);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns timeout reply without partial output when none is available", async () => {
|
||||||
|
const runSpy = vi.fn().mockRejectedValue({
|
||||||
|
killed: true,
|
||||||
|
signal: "SIGKILL",
|
||||||
|
stdout: "",
|
||||||
|
stderr: "",
|
||||||
|
});
|
||||||
|
const cfg = {
|
||||||
|
inbound: {
|
||||||
|
reply: {
|
||||||
|
mode: "command" as const,
|
||||||
|
command: ["echo", "{{Body}}"],
|
||||||
|
timeoutSeconds: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await index.getReplyFromConfig(
|
||||||
|
{ Body: "hi", From: "+1", To: "+2" },
|
||||||
|
undefined,
|
||||||
|
cfg,
|
||||||
|
runSpy,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result?.text).toBe(
|
||||||
|
"Command timed out after 5s. Try a shorter prompt or split the request.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
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",
|
||||||
|
|||||||
Reference in New Issue
Block a user