mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-09 22:33:37 +00:00
fix(web): render prompt.completed in stage Transcript
Prompt-shape stages (prompt, fan_in) only emit `prompt.completed` for their response, so the Transcript tab showed the input but never the output. Add the event to STAGE_ACTIVITY_EVENT_TYPES and to the eventsToActivity reducer, suppressing it when a prior agent.message already streamed the same content (agent stages). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
82019d356f
commit
4d33daf14e
3 changed files with 126 additions and 0 deletions
|
|
@ -64,6 +64,7 @@ const STAGE_EVENTS = new Set([
|
|||
// to run-scoped invalidations (stages list, graph, detail).
|
||||
export const STAGE_ACTIVITY_EVENT_TYPES = [
|
||||
"stage.prompt",
|
||||
"prompt.completed",
|
||||
"agent.message",
|
||||
"agent.tool.started",
|
||||
"agent.tool.completed",
|
||||
|
|
|
|||
|
|
@ -226,6 +226,116 @@ describe("eventsToActivity", () => {
|
|||
]);
|
||||
});
|
||||
|
||||
test("renders prompt.completed as an assistant turn for prompt-shape stages", () => {
|
||||
const events: EventEnvelope[] = [
|
||||
envelope(1, {
|
||||
event: "stage.prompt",
|
||||
stage_id: "summarize@1",
|
||||
node_id: "summarize",
|
||||
properties: { text: "summarize the diff" },
|
||||
}),
|
||||
envelope(2, {
|
||||
event: "prompt.completed",
|
||||
stage_id: "summarize@1",
|
||||
node_id: "summarize",
|
||||
properties: {
|
||||
response: "Refactored auth module",
|
||||
model: "claude-sonnet-4-6",
|
||||
provider: "anthropic",
|
||||
billing: { input_tokens: 120, output_tokens: 30 },
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
expect(eventsToActivity(events, "summarize@1")).toEqual([
|
||||
{
|
||||
kind: "system",
|
||||
ts: "2026-04-09T12:00:00Z",
|
||||
content: "summarize the diff",
|
||||
},
|
||||
{
|
||||
kind: "assistant",
|
||||
ts: "2026-04-09T12:00:00Z",
|
||||
content: "Refactored auth module",
|
||||
inputTokens: 120,
|
||||
outputTokens: 30,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("does not duplicate the assistant turn when prompt.completed follows agent.message", () => {
|
||||
const events: EventEnvelope[] = [
|
||||
envelope(1, {
|
||||
event: "stage.prompt",
|
||||
stage_id: "simplify@1",
|
||||
node_id: "simplify",
|
||||
properties: { text: "simplify" },
|
||||
}),
|
||||
envelope(2, {
|
||||
event: "agent.message",
|
||||
stage_id: "simplify@1",
|
||||
node_id: "simplify",
|
||||
properties: {
|
||||
text: "Done.",
|
||||
billing: { input_tokens: 10, output_tokens: 5 },
|
||||
},
|
||||
}),
|
||||
envelope(3, {
|
||||
event: "prompt.completed",
|
||||
stage_id: "simplify@1",
|
||||
node_id: "simplify",
|
||||
properties: {
|
||||
response: "Done.",
|
||||
model: "claude-sonnet-4-6",
|
||||
provider: "anthropic",
|
||||
billing: { input_tokens: 10, output_tokens: 5 },
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
const turns = eventsToActivity(events, "simplify@1");
|
||||
expect(turns).toEqual([
|
||||
{
|
||||
kind: "system",
|
||||
ts: "2026-04-09T12:00:00Z",
|
||||
content: "simplify",
|
||||
},
|
||||
{
|
||||
kind: "assistant",
|
||||
ts: "2026-04-09T12:00:00Z",
|
||||
content: "Done.",
|
||||
inputTokens: 10,
|
||||
outputTokens: 5,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("renders prompt.completed even with no preceding stage.prompt", () => {
|
||||
const events: EventEnvelope[] = [
|
||||
envelope(1, {
|
||||
event: "prompt.completed",
|
||||
stage_id: "summarize@1",
|
||||
node_id: "summarize",
|
||||
properties: {
|
||||
response: "All clear.",
|
||||
model: "claude-sonnet-4-6",
|
||||
provider: "anthropic",
|
||||
billing: { input_tokens: 0, output_tokens: 4 },
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
expect(eventsToActivity(events, "summarize@1")).toEqual([
|
||||
{
|
||||
kind: "assistant",
|
||||
ts: "2026-04-09T12:00:00Z",
|
||||
content: "All clear.",
|
||||
inputTokens: 0,
|
||||
outputTokens: 4,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("extractStageModel pulls model from agent.session.activated, ignoring other stages", () => {
|
||||
const events: EventEnvelope[] = [
|
||||
envelope(1, {
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ export function eventsToActivity(events: EventEnvelope[], stageId: string): Turn
|
|||
const turns: TurnType[] = [];
|
||||
const pendingTools = new Map<string, PendingTool>();
|
||||
let pendingCommand: PendingCommand | undefined;
|
||||
let sawAssistantMessage = false;
|
||||
|
||||
for (const e of events) {
|
||||
const eventName = e.event;
|
||||
|
|
@ -128,6 +129,7 @@ export function eventsToActivity(events: EventEnvelope[], stageId: string): Turn
|
|||
turns.push({ kind: "system", ts: e.ts, content: getString(props, "text") ?? e.text ?? "" });
|
||||
break;
|
||||
case "agent.message": {
|
||||
sawAssistantMessage = true;
|
||||
const msg = getString(props, "text") ?? e.text ?? "";
|
||||
if (msg) {
|
||||
const billing = (props.billing ?? {}) as UnknownRecord;
|
||||
|
|
@ -141,6 +143,19 @@ export function eventsToActivity(events: EventEnvelope[], stageId: string): Turn
|
|||
}
|
||||
break;
|
||||
}
|
||||
case "prompt.completed": {
|
||||
if (!sawAssistantMessage) {
|
||||
const billing = (props.billing ?? {}) as UnknownRecord;
|
||||
turns.push({
|
||||
kind: "assistant",
|
||||
ts: e.ts,
|
||||
content: getString(props, "response") ?? "",
|
||||
inputTokens: getNumber(billing, "input_tokens") ?? 0,
|
||||
outputTokens: getNumber(billing, "output_tokens") ?? 0,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "agent.steering.injected": {
|
||||
const text = getString(props, "text") ?? e.text ?? "";
|
||||
if (text) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue