Skip to content
Merged
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
5 changes: 4 additions & 1 deletion docs/architecture/agent-message-ux-catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ The current column exercises these visible cases in one trace:
- subagent progress and nested output
- Todo progress
- changed-file diff summary
- a Codex turn-start checkpoint whose expanded recovery control does not repeat
the accordion title
- single-line system notices, rate-limit guidance, plugin installation, and a
multi-line provider warning whose expanded body contains detail without
repeating the row title
Expand Down Expand Up @@ -118,7 +120,8 @@ construction even though they are not separate `NormalizedProviderEvent` types.
such as `Context window at 62%` must not repeat the same sentence inside a
nested body. For a multi-line generic system notice, the first non-empty line
is the title and only the remaining lines belong in the expanded body;
compact checkpoints keep their specialized recovery surface.
compact and turn-start checkpoints keep their specialized recovery surface,
without repeating the title inside it.
- Tool results update the existing tool row by id. A result must never create a
second row that repeats the header or input.
- Stave-owned MCP tools use the Stave icon and an action-oriented title. Only
Expand Down
6 changes: 4 additions & 2 deletions src/components/session/chat-panel-message-parts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ export function MessagePartRenderer(args: {
const compactBoundaryTrigger =
part.compactBoundary?.trigger ?? compactedMatch?.[1];
const compactBoundaryGitRef = part.compactBoundary?.gitRef;
const isTurnStartCheckpoint = compactBoundaryTrigger === "turn_start";
const handleRestoreCompactBoundary = () => {
if (!compactBoundaryGitRef || isRestoringCompactBoundary) {
return;
Expand All @@ -241,10 +242,11 @@ export function MessagePartRenderer(args: {
setIsRestoringCompactBoundary(false);
});
};
if (compactedMatch) {
if (part.compactBoundary != null || compactedMatch) {
return (
<ContextCompactedCheckpoint
trigger={compactBoundaryTrigger}
label={isTurnStartCheckpoint ? "Workspace checkpoint" : undefined}
trigger={isTurnStartCheckpoint ? undefined : compactBoundaryTrigger}
onRestore={handleRestoreCompactBoundary}
restorePending={isRestoringCompactBoundary}
restoreDisabled={!compactBoundaryGitRef}
Expand Down
5 changes: 5 additions & 0 deletions src/dev/agent-preview/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ function commonParts(args: { reasoningStreaming: boolean }): MessagePart[] {
type: "system_event",
content: "Context window at 62% — compaction not required.",
},
{
type: "system_event",
content: "Checkpoint captured before Codex turn.",
compactBoundary: { trigger: "turn_start", gitRef: "preview-checkpoint" },
},
{
type: "system_event",
content: "Approaching rate limit (72% used). Consider pacing requests.",
Expand Down
22 changes: 22 additions & 0 deletions tests/assistant-message-body.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,28 @@ describe("AssistantMessageBody", () => {
});
});

test("does not repeat a Codex turn checkpoint inside its accordion", async () => {
const { AssistantMessageBody } = await loadAssistantMessageBodies();
const checkpointNotice = "Checkpoint captured before Codex turn.";
const html = renderToStaticMarkup(createElement(AssistantMessageBody, {
message: createAssistantMessage({
isStreaming: true,
parts: [{
type: "system_event",
content: checkpointNotice,
compactBoundary: { trigger: "turn_start", gitRef: "abc123" },
}],
}),
taskId: "task-1",
messageId: "message-1",
streamingEnabled: true,
}));

expect(html.split(checkpointNotice).length - 1).toBe(1);
expect(html).toContain("Workspace checkpoint");
expect(html).toContain("Restore");
});

test("uses Stave-specific copy and icon without rewriting external MCP names", async () => {
const { AssistantMessageBody } = await loadAssistantMessageBodies();
const html = renderToStaticMarkup(createElement(AssistantMessageBody, {
Expand Down
Loading