Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions apps/web/src/session-logic.command-output.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { EventId, TurnId, type OrchestrationThreadActivity } from "@t3tools/contracts";
import { describe, expect, it } from "vite-plus/test";

import { deriveWorkLogEntries } from "./session-logic";

function makeCommandActivity(
id: string,
payload: Record<string, unknown>,
): OrchestrationThreadActivity {
return {
id: EventId.make(id),
createdAt: "2026-07-17T10:00:00.000Z",
kind: "tool.completed",
summary: "Ran command",
tone: "tool",
payload,
turnId: TurnId.make("turn-1"),
};
}

describe("deriveWorkLogEntries command output", () => {
it("uses Codex aggregated output instead of repeating the command", () => {
const [entry] = deriveWorkLogEntries([
makeCommandActivity("codex-command", {
itemType: "command_execution",
title: "Ran command",
detail: "printf hello",
data: {
item: {
type: "commandExecution",
command: "printf hello",
aggregatedOutput: "hello\n<exited with exit code 0>",
status: "completed",
},
},
}),
]);

expect(entry).toMatchObject({
command: "printf hello",
detail: "hello",
});
});

it("uses Claude ACP stdout instead of repeating the command", () => {
const [entry] = deriveWorkLogEntries([
makeCommandActivity("claude-command", {
itemType: "command_execution",
title: "Ran command",
detail: "printf hello",
data: {
kind: "execute",
command: "printf hello",
rawOutput: {
stdout: "hello from claude\n",
},
},
}),
]);

expect(entry).toMatchObject({
command: "printf hello",
detail: "hello from claude",
});
});

it("drops duplicated command detail when the command has no output", () => {
const [entry] = deriveWorkLogEntries([
makeCommandActivity("empty-command", {
itemType: "command_execution",
title: "Ran command",
detail: "true",
data: {
kind: "execute",
command: "true",
},
}),
]);

expect(entry?.command).toBe("true");
expect(entry?.detail).toBeUndefined();
});
});
88 changes: 86 additions & 2 deletions apps/web/src/session-logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,70 @@ function summarizeToolRawOutput(payload: Record<string, unknown> | null): string
return null;
}

function extractAcpTextContent(value: unknown): string | null {
if (!Array.isArray(value)) {
return null;
}

const chunks: string[] = [];
for (const entryValue of value) {
const entry = asRecord(entryValue);
if (entry?.type !== "content") {
continue;
}
const content = asRecord(entry.content);
if (content?.type !== "text") {
continue;
}
const text = asTrimmedString(content.text);
if (text) {
chunks.push(text);
}
}

return chunks.length > 0 ? chunks.join("\n") : null;
}

function extractToolOutput(payload: Record<string, unknown> | null): string | null {
const data = asRecord(payload?.data);
const item = asRecord(data?.item);
const itemResult = asRecord(item?.result);
const rawOutput = asRecord(data?.rawOutput);

const outputStreams: string[] = [];
const stdout = asTrimmedString(rawOutput?.stdout);
const stderr = asTrimmedString(rawOutput?.stderr);
if (stdout) {
outputStreams.push(stdout);
}
if (stderr) {
outputStreams.push(stderr);
}

const candidates: unknown[] = [
item?.aggregatedOutput,
itemResult?.content,
data?.rawOutput,
rawOutput?.content,
outputStreams.length > 0 ? outputStreams.join("\n") : null,
rawOutput?.output,
extractAcpTextContent(data?.content),
];

for (const candidate of candidates) {
const text = asTrimmedString(candidate);
if (!text) {
continue;
}
const output = stripTrailingExitCode(text).output;
if (output) {
return output;
}
}

return null;
}

function isCommandToolDetail(payload: Record<string, unknown> | null, heading: string): boolean {
const data = asRecord(payload?.data);
const kind = asTrimmedString(data?.kind)?.toLowerCase();
Expand All @@ -1169,12 +1233,32 @@ function extractToolDetail(
const detail = rawDetail ? stripTrailingExitCode(rawDetail).output : null;
const normalizedHeading = normalizePreviewForComparison(heading);
const normalizedDetail = normalizePreviewForComparison(detail);
const commandTool = isCommandToolDetail(payload, heading);
const command = commandTool ? extractToolCommand(payload).command : null;
const normalizedCommand = normalizePreviewForComparison(command);

if (detail && normalizedHeading !== normalizedDetail) {
if (
detail &&
normalizedHeading !== normalizedDetail &&
(!commandTool || normalizedCommand !== normalizedDetail)
) {
return detail;
}

if (isCommandToolDetail(payload, heading)) {
if (commandTool) {
if (!command) {
return null;
}

const output = extractToolOutput(payload);
const normalizedOutput = normalizePreviewForComparison(output);
if (
output &&
normalizedOutput !== normalizedHeading &&
normalizedOutput !== normalizedCommand
) {
return output;
}
return null;
}

Expand Down
Loading