fix: show print mode liveness

This commit is contained in:
Mikael Hugo 2026-05-14 20:59:19 +02:00
parent 487237a32c
commit f88b48b0aa
2 changed files with 94 additions and 0 deletions

View file

@ -1,6 +1,7 @@
import assert from "node:assert/strict";
import { test } from "vitest";
import {
createPrintModeLivenessReporter,
PrintModeTimeoutError,
promptWithPrintTimeout,
resolvePrintModeTimeoutMs,
@ -79,3 +80,61 @@ test("runPrintMode_when_extension_startup_hangs_still_runs_prompt", async () =>
console.log = originalLog;
}
});
test("createPrintModeLivenessReporter_in_text_mode_reports_progress_to_stderr_once", () => {
const writes: string[] = [];
const originalWrite = process.stderr.write;
process.stderr.write = ((chunk: string | Uint8Array) => {
writes.push(String(chunk));
return true;
}) as typeof process.stderr.write;
try {
const report = createPrintModeLivenessReporter("text");
report({ type: "message_start" });
report({
type: "message_update",
assistantMessageEvent: { type: "thinking_start" },
});
report({
type: "message_update",
assistantMessageEvent: { type: "thinking_delta" },
});
report({
type: "message_update",
assistantMessageEvent: { type: "text_start" },
});
report({
type: "message_update",
assistantMessageEvent: { type: "text_delta" },
});
assert.deepEqual(writes, [
"[forge] model responding...\n",
"[forge] thinking...\n",
"[forge] writing response...\n",
]);
} finally {
process.stderr.write = originalWrite;
}
});
test("createPrintModeLivenessReporter_in_json_mode_keeps_stderr_quiet", () => {
const writes: string[] = [];
const originalWrite = process.stderr.write;
process.stderr.write = ((chunk: string | Uint8Array) => {
writes.push(String(chunk));
return true;
}) as typeof process.stderr.write;
try {
const report = createPrintModeLivenessReporter("json");
report({ type: "message_start" });
report({
type: "message_update",
assistantMessageEvent: { type: "thinking_start" },
});
assert.deepEqual(writes, []);
} finally {
process.stderr.write = originalWrite;
}
});

View file

@ -126,6 +126,7 @@ export async function runPrintMode(
// Print mode intentionally skips extension session_start binding. One-shot
// automation needs bounded prompt output; startup hooks are interactive/RPC
// lifecycle work and have previously blocked `sf -p` before the prompt ran.
const liveness = createPrintModeLivenessReporter(mode);
// Always subscribe to enable session persistence via _handleAgentEvent
const unsubscribe = session.subscribe((event) => {
@ -133,6 +134,7 @@ export async function runPrintMode(
if (mode === "json") {
console.log(JSON.stringify(event));
}
liveness(event);
});
let exitCode = 0;
@ -232,3 +234,36 @@ export async function runPrintMode(
process.exit(exitCode);
}
}
export function createPrintModeLivenessReporter(
mode: "text" | "json",
): (event: { type: string; assistantMessageEvent?: { type: string } }) => void {
if (mode !== "text") return () => {};
let sawAssistant = false;
let sawThinking = false;
let sawText = false;
return (event) => {
if (event.type === "message_start" && !sawAssistant) {
sawAssistant = true;
process.stderr.write("[forge] model responding...\n");
return;
}
if (event.type !== "message_update") return;
const streamType = event.assistantMessageEvent?.type;
if (
(streamType === "thinking_start" || streamType === "thinking_delta") &&
!sawThinking
) {
sawThinking = true;
process.stderr.write("[forge] thinking...\n");
return;
}
if (
(streamType === "text_start" || streamType === "text_delta") &&
!sawText
) {
sawText = true;
process.stderr.write("[forge] writing response...\n");
}
};
}