fix: broaden prompt-echo guard and add heartbeat directive test
This commit is contained in:
@@ -148,6 +148,47 @@ describe("runCommandReply (pi)", () => {
|
||||
expect(payloads?.[0]?.text).not.toContain("hello");
|
||||
});
|
||||
|
||||
it("does not echo the prompt even when the fallback text matches after stripping prefixes", async () => {
|
||||
const rpcMock = mockPiRpc({
|
||||
stdout: [
|
||||
'{"type":"agent_start"}',
|
||||
'{"type":"turn_start"}',
|
||||
'{"type":"message_start","message":{"role":"user","content":[{"type":"text","text":"[Dec 5 22:52] https://example.com"}]}}',
|
||||
'{"type":"message_end","message":{"role":"user","content":[{"type":"text","text":"[Dec 5 22:52] https://example.com"}]}}',
|
||||
// No assistant content
|
||||
'{"type":"agent_end"}',
|
||||
].join("\n"),
|
||||
stderr: "",
|
||||
code: 0,
|
||||
});
|
||||
|
||||
const { payloads } = await runCommandReply({
|
||||
reply: {
|
||||
mode: "command",
|
||||
command: ["pi", "{{Body}}"],
|
||||
agent: { kind: "pi" },
|
||||
},
|
||||
templatingCtx: {
|
||||
...noopTemplateCtx,
|
||||
Body: "[Dec 5 22:52] https://example.com",
|
||||
BodyStripped: "[Dec 5 22:52] https://example.com",
|
||||
},
|
||||
sendSystemOnce: false,
|
||||
isNewSession: true,
|
||||
isFirstTurnInSession: true,
|
||||
systemSent: false,
|
||||
timeoutMs: 1000,
|
||||
timeoutSeconds: 1,
|
||||
commandRunner: vi.fn(),
|
||||
enqueue: enqueueImmediate,
|
||||
});
|
||||
|
||||
expect(rpcMock).toHaveBeenCalledOnce();
|
||||
expect(payloads?.length).toBe(1);
|
||||
expect(payloads?.[0]?.text).toMatch(/no output/i);
|
||||
expect(payloads?.[0]?.text).not.toContain("example.com");
|
||||
});
|
||||
|
||||
it("adds session args and --continue when resuming", async () => {
|
||||
const rpcMock = mockPiRpc({
|
||||
stdout:
|
||||
|
||||
@@ -22,6 +22,14 @@ import {
|
||||
} from "./tool-meta.js";
|
||||
import type { ReplyPayload } from "./types.js";
|
||||
|
||||
function stripStructuralPrefixes(text: string): string {
|
||||
return text
|
||||
.replace(/\[[^\]]+\]\s*/g, "")
|
||||
.replace(/^[ \t]*[A-Za-z0-9+()\-_. ]+:\s*/gm, "")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function stripRpcNoise(raw: string): string {
|
||||
// Drop rpc streaming scaffolding (toolcall deltas, audio buffer events) before parsing.
|
||||
const lines = raw.split(/\n+/);
|
||||
@@ -771,18 +779,23 @@ export async function runCommandReply(
|
||||
}
|
||||
|
||||
// If parser gave nothing, fall back to best-effort assistant text (prefers RPC deltas).
|
||||
const fallbackText =
|
||||
rpcAssistantText ??
|
||||
extractRpcAssistantText(trimmed) ??
|
||||
extractAssistantTextLoosely(trimmed) ??
|
||||
trimmed;
|
||||
const promptEcho =
|
||||
fallbackText &&
|
||||
(fallbackText === (templatingCtx.Body ?? "") ||
|
||||
fallbackText === (templatingCtx.BodyStripped ?? ""));
|
||||
const safeFallbackText = promptEcho ? undefined : fallbackText;
|
||||
const fallbackText =
|
||||
rpcAssistantText ??
|
||||
extractRpcAssistantText(trimmed) ??
|
||||
extractAssistantTextLoosely(trimmed) ??
|
||||
trimmed;
|
||||
const normalize = (s?: string) =>
|
||||
stripStructuralPrefixes((s ?? "").trim()).toLowerCase();
|
||||
const bodyNorm = normalize(templatingCtx.Body ?? templatingCtx.BodyStripped);
|
||||
const fallbackNorm = normalize(fallbackText);
|
||||
const promptEcho =
|
||||
fallbackText &&
|
||||
(fallbackText === (templatingCtx.Body ?? "") ||
|
||||
fallbackText === (templatingCtx.BodyStripped ?? "") ||
|
||||
(bodyNorm.length > 0 && bodyNorm === fallbackNorm));
|
||||
const safeFallbackText = promptEcho ? undefined : fallbackText;
|
||||
|
||||
if (replyItems.length === 0 && safeFallbackText && !hasParsedContent) {
|
||||
if (replyItems.length === 0 && safeFallbackText && !hasParsedContent) {
|
||||
const { text: cleanedText, mediaUrls: mediaFound } =
|
||||
splitMediaFromOutput(safeFallbackText);
|
||||
if (cleanedText || mediaFound?.length) {
|
||||
|
||||
Reference in New Issue
Block a user