Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const TOOL_CALL_VISIBILITY_OPTIONS: ReadonlyArray<{
{ value: "showAll", label: "Show all" },
{ value: "hideSuccessful", label: "Hide successful" },
{ value: "hideAll", label: "Hide all" },
{ value: "messagesOnly", label: "Messages only" },
{ value: "zen", label: "Zen" },
];

Expand All @@ -40,8 +41,9 @@ export function ToolCallsAppearanceSection() {
))}
</SettingsSection>
<Text className="px-2 text-sm text-foreground-muted">
Hide the commands, edits, and searches agents run. Hide successful keeps failures. Zen shows
only final answers. Approvals, questions, and errors always show.
Hide the commands, edits, and searches agents run. Hide successful keeps failures. Messages
only also hides thinking and subagents. Zen shows only final answers. Approvals, questions,
and errors always show.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
</Text>
</View>
);
Expand Down
2 changes: 2 additions & 0 deletions apps/mobile/src/features/settings/settings-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const SETTINGS_SEARCH_TERMS = {
"work log",
"zen",
"final answers only",
"messages only",
"hide thinking",
],
SettingsNavigation: ["tabs", "tab order", "reorder", "hide tabs"],
SettingsKeyboard: ["return key", "send message", "new line", "hardware keyboard"],
Expand Down
21 changes: 20 additions & 1 deletion apps/mobile/src/lib/threadActivity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1281,7 +1281,7 @@ describe("buildThreadFeed", () => {
const running = { runId, status: "running" as const, startedAt: at(1), completedAt: null };
const rows = (
items: ReadonlyArray<OrchestrationV2TurnItem>,
toolCallVisibility: "hideSuccessful" | "hideAll" | "zen",
toolCallVisibility: "hideSuccessful" | "hideAll" | "messagesOnly" | "zen",
latestRun: ThreadFeedLatestRun | null = null,
) =>
deriveThreadFeedPresentation(
Expand Down Expand Up @@ -1340,6 +1340,25 @@ describe("buildThreadFeed", () => {
"wake",
"thinking:",
]);

const thought = {
...base("thought", at(3), 9),
type: "reasoning" as const,
text: "Checking the captures",
streaming: false,
};
expect(rows([user, step, thought, passed, failed, final], "messagesOnly")).toEqual([
"user",
"fold",
"step",
"final",
]);
expect(rows([user, thought, passed, final], "messagesOnly")).toEqual(["user", "final"]);
expect(rows([user, step, thought, passed], "messagesOnly", running)).toEqual([
"user",
"step",
"thinking:",
]);
});
});

Expand Down
6 changes: 3 additions & 3 deletions apps/mobile/src/lib/threadActivity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1372,7 +1372,7 @@ const visibleActivityGroups = new WeakMap<
{ readonly visibility: ToolCallVisibility; readonly group: ThreadFeedActivityGroup | null }
>();

function zenKeepsActivity(activity: ThreadFeedActivity): boolean {
function shownWithoutWorkLog(activity: ThreadFeedActivity): boolean {
const item = activity.projectedItem.item;
return (
activity.prominent ||
Expand All @@ -1391,8 +1391,8 @@ function activityGroupForVisibility(
const cached = visibleActivityGroups.get(entry);
if (cached?.visibility === visibility) return cached.group;
const activities = entry.activities.filter((activity) =>
visibility === "zen"
? zenKeepsActivity(activity)
visibility === "zen" || visibility === "messagesOnly"
? shownWithoutWorkLog(activity)
: !workEntryHiddenByToolCallVisibility(activity.workEntry, visibility),
);
const group =
Expand Down
87 changes: 86 additions & 1 deletion apps/web/src/components/chat/MessagesTimeline.logic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2980,14 +2980,16 @@ describe("v2 run and attempt history", () => {
const ended = { ...running, turnEnded: true };
const rows = (
timelineEntries: ReadonlyArray<TimelineEntry>,
toolCallVisibility: "hideSuccessful" | "hideAll" | "zen",
toolCallVisibility: "hideSuccessful" | "hideAll" | "messagesOnly" | "zen",
latestRun?: typeof ended | typeof running,
pendingApprovalRequestIds?: ReadonlySet<RuntimeRequestId>,
) =>
deriveMessagesTimelineRows({
timelineEntries,
expandedRunIds: new Set([runId]),
isWorking: latestRun !== undefined,
...(latestRun ? { latestRun } : {}),
...(pendingApprovalRequestIds ? { pendingApprovalRequestIds } : {}),
turnDiffSummaries: [],
supportsConversationRollback: false,
toolCallVisibility,
Expand Down Expand Up @@ -3050,6 +3052,89 @@ describe("v2 run and attempt history", () => {
"working-indicator-row",
"live-activity-row",
]);

const work = (
id: string,
itemType: "reasoning" | "todo_list" | "error" | "approval_request" | "user_input_request",
second: number,
extra: Partial<Extract<TimelineEntry, { kind: "work" }>["entry"]> = {},
): Extract<TimelineEntry, { kind: "work" }> => ({
id,
kind: "work",
createdAt: at(second),
entry: {
id,
createdAt: at(second),
runId,
label: id,
tone: "info",
itemType,
toolLifecycleStatus: "completed",
...extra,
},
});
const thought = work("thought", "reasoning", 6, { detail: "Checking the captures" });
const tasks = work("tasks", "todo_list", 6);
const child: TimelineEntry = {
id: "child",
kind: "event",
createdAt: at(7),
projectedItem: {
item: { id: "child", type: "subagent", runId },
} as OrchestrationV2ProjectedTurnItem,
};
const steps = [thought, tasks, child, passed, failed];
expect(rows([user, step, ...steps, final], "messagesOnly")).toEqual([
"user",
"turn-fold:turn-1",
"step",
"final",
]);
expect(rows([user, ...steps, final], "messagesOnly")).toEqual(["user", "final"]);
expect(
rows(
[
user,
step,
{ ...thought, entry: { ...thought.entry, toolLifecycleStatus: "inProgress" } },
],
"messagesOnly",
running,
),
).toEqual(["user", "working-indicator-row", "step", "live-activity-row"]);
const requestId = RuntimeRequestId.make("approval-request");
const approval = work("approval", "approval_request", 10, {
toolLifecycleStatus: "inProgress",
projectedItem: {
item: { id: "approval", type: "approval_request", requestId, runId },
} as OrchestrationV2ProjectedTurnItem,
});
const answered = work("answered", "user_input_request", 11, {
questionAnswer: {
requestId: RuntimeRequestId.make("input-request"),
questionTextById: { scope: "Which scope?" },
answers: { scope: "Web" },
attachmentsByQuestionId: {},
},
});
const errored = work("errored", "error", 12, { tone: "error", toolLifecycleStatus: "failed" });
expect(
rows(
[user, step, thought, approval, answered, errored],
"messagesOnly",
running,
new Set([requestId]),
),
).toEqual(["user", "working-indicator-row", "step", "approval", "answered", "errored"]);
// Find and citations open the fold that holds an in-between message.
const foldRunIds = (toolCallVisibility: "messagesOnly" | "zen") =>
timelineTurnFoldRunIdsByEntryId({
timelineEntries: [user, step, ...steps, final],
isWorking: false,
toolCallVisibility,
});
expect(foldRunIds("messagesOnly").get("step")).toBe(runId);
expect(foldRunIds("zen").get("step")).toBeUndefined();
});
it("collapses only output from a superseded V2 attempt within the active logical run", () => {
const runId = "run-steered" as never;
Expand Down
9 changes: 7 additions & 2 deletions apps/web/src/components/chat/MessagesTimeline.logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1378,7 +1378,7 @@ function settleSupersededReasoning(entries: ReadonlyArray<TimelineEntry>) {
});
}

function workShownAsFinalAnswerOnly(
function shownWithoutWorkLog(
entry: TimelineEntry,
pendingApprovalRequestIds: ReadonlySet<RuntimeRequestId> | undefined,
): boolean {
Expand Down Expand Up @@ -1460,7 +1460,7 @@ function timelineRowEntries(
const steerAnswerIds = deriveInFlightSteerAnswerIds(entries);
shown = entries.filter((entry, index) => {
if (entry.kind !== "message") {
return workShownAsFinalAnswerOnly(entry, input.pendingApprovalRequestIds);
return shownWithoutWorkLog(entry, input.pendingApprovalRequestIds);
}
const { message } = entry;
return (
Expand All @@ -1472,6 +1472,11 @@ function timelineRowEntries(
(answeringRunId === null || message.runId !== answeringRunId))
);
});
} else if (visibility === "messagesOnly") {
shown = entries.filter(
(entry) =>
entry.kind === "message" || shownWithoutWorkLog(entry, input.pendingApprovalRequestIds),
);
} else if (visibility !== "showAll") {
shown = entries.filter(
(entry) =>
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/components/settings/SettingsPanels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1223,6 +1223,7 @@ const TOOL_CALL_VISIBILITY_LABELS: Record<ToolCallVisibility, string> = {
showAll: "Show all",
hideSuccessful: "Hide successful",
hideAll: "Hide all",
messagesOnly: "Messages only",
zen: "Zen",
};

Expand All @@ -1234,7 +1235,7 @@ function ToolCallVisibilityRow() {
return (
<SettingsRow
{...searchableSetting("tool-call-visibility")}
description="Hide the commands, edits, and searches agents run. Hide successful keeps failures. Zen shows only final answers. Approvals, questions, and errors always show."
description="Hide commands, edits, and searches. Hide successful keeps failures. Messages only also hides thinking, tasks, and subagents. Zen shows only final answers. Approvals, questions, and errors always show."
resetAction={
settings.toolCallVisibility !== DEFAULT_UNIFIED_SETTINGS.toolCallVisibility ? (
<SettingResetButton
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/settings/settingsSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ export const SETTINGS_SEARCH_ITEMS = [
title: "Tool calls in chat",
to: "/settings/chat",
searchTerms: [
"hide tool calls commands edits searches work log activity quiet chat zen final answers only",
"hide tool calls commands edits searches work log activity quiet chat zen final answers only messages thinking reasoning thoughts tasks subagents",
],
},
{
Expand Down
11 changes: 6 additions & 5 deletions docs/user/activity-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ were affected.
(mobile) hides commands, file edits, searches, and MCP tool calls
from the conversation. **Hide successful** shows a call while it runs and keeps
it only if it fails. **Hide all** hides every call and shows **Thinking** while the agent works.
**Zen** also hides the agent's in-between messages, so each turn shows your message
and the agent's last reply, which appears when the turn ends. If you steer while the agent is
answering, the answer it finished before reading your steer stays too. On web and desktop, Find
shows the in-between messages while it is open. Approvals, questions, and errors always show. The
setting applies to this browser or device only.
**Messages only** also hides the agent's thinking, task lists, and subagents, so the conversation
shows only what you and the agent say. **Zen** also hides the agent's in-between messages, so each
turn shows your message and the agent's last reply, which appears when the turn ends. If you steer
while the agent is answering, the answer it finished before reading your steer stays too. On web
and desktop, Find shows the in-between messages while it is open. Approvals, questions, and errors
always show. The setting applies to this browser or device only.

## Screen reader announcements

Expand Down
8 changes: 7 additions & 1 deletion packages/contracts/src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,13 @@ export type ChatWidth = typeof ChatWidth.Type;
export const EffortDisplay = Schema.Literals(["label", "bars"]);
export type EffortDisplay = typeof EffortDisplay.Type;

export const ToolCallVisibility = Schema.Literals(["showAll", "hideSuccessful", "hideAll", "zen"]);
export const ToolCallVisibility = Schema.Literals([
"showAll",
"hideSuccessful",
"hideAll",
"messagesOnly",
"zen",
]);
export type ToolCallVisibility = typeof ToolCallVisibility.Type;

/** What the open thread reads to a screen reader: turn status, the agent's in-between messages too, or every step. */
Expand Down
Loading