diff --git a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts
index 8d3fb5d752..48bce1414e 100644
--- a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts
+++ b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts
@@ -440,6 +440,7 @@ function runtimeEventToActivities(
kind: "tool.updated",
summary: event.payload.title ?? "Tool updated",
payload: {
+ ...(event.itemId ? { itemId: event.itemId } : {}),
itemType: event.payload.itemType,
...(event.payload.status ? { status: event.payload.status } : {}),
...(event.payload.detail ? { detail: truncateDetail(event.payload.detail) } : {}),
@@ -463,8 +464,9 @@ function runtimeEventToActivities(
kind: "tool.completed",
summary: event.payload.title ?? "Tool",
payload: {
+ ...(event.itemId ? { itemId: event.itemId } : {}),
itemType: event.payload.itemType,
- ...(event.payload.detail ? { detail: truncateDetail(event.payload.detail) } : {}),
+ ...(event.payload.detail ? { detail: event.payload.detail } : {}),
},
turnId: toTurnId(event.turnId) ?? null,
...maybeSequence,
@@ -484,8 +486,9 @@ function runtimeEventToActivities(
kind: "tool.started",
summary: `${event.payload.title ?? "Tool"} started`,
payload: {
+ ...(event.itemId ? { itemId: event.itemId } : {}),
itemType: event.payload.itemType,
- ...(event.payload.detail ? { detail: truncateDetail(event.payload.detail) } : {}),
+ ...(event.payload.detail ? { detail: event.payload.detail } : {}),
},
turnId: toTurnId(event.turnId) ?? null,
...maybeSequence,
diff --git a/apps/server/src/provider/Layers/CodexAdapter.ts b/apps/server/src/provider/Layers/CodexAdapter.ts
index 8b9f3b59e7..c936fe86a2 100644
--- a/apps/server/src/provider/Layers/CodexAdapter.ts
+++ b/apps/server/src/provider/Layers/CodexAdapter.ts
@@ -213,7 +213,10 @@ function toCanonicalItemType(raw: unknown): CanonicalItemType {
return "unknown";
}
-function itemTitle(itemType: CanonicalItemType): string | undefined {
+function itemTitle(
+ itemType: CanonicalItemType,
+ started = false,
+): string | undefined {
switch (itemType) {
case "assistant_message":
return "Assistant message";
@@ -224,7 +227,7 @@ function itemTitle(itemType: CanonicalItemType): string | undefined {
case "plan":
return "Plan";
case "command_execution":
- return "Ran command";
+ return started ? "Running command" : "Ran command";
case "file_change":
return "File change";
case "mcp_tool_call":
@@ -562,6 +565,7 @@ function mapItemLifecycle(
: lifecycle === "item.completed"
? "completed"
: undefined;
+ const title = itemTitle(itemType, lifecycle === "item.started");
return {
...runtimeEventBase(event, canonicalThreadId),
@@ -569,7 +573,7 @@ function mapItemLifecycle(
payload: {
itemType,
...(status ? { status } : {}),
- ...(itemTitle(itemType) ? { title: itemTitle(itemType) } : {}),
+ ...(title ? { title } : {}),
...(detail ? { detail } : {}),
...(event.payload !== undefined ? { data: event.payload } : {}),
},
diff --git a/apps/web/src/components/chat/MessagesTimeline.tsx b/apps/web/src/components/chat/MessagesTimeline.tsx
index e823569c13..daaa3baa70 100644
--- a/apps/web/src/components/chat/MessagesTimeline.tsx
+++ b/apps/web/src/components/chat/MessagesTimeline.tsx
@@ -346,7 +346,10 @@ export const MessagesTimeline = memo(function MessagesTimeline({
)}
{visibleEntries.map((workEntry) => (
-
+
))}
diff --git a/apps/web/src/session-logic.ts b/apps/web/src/session-logic.ts
index fc33827014..b745f3ec57 100644
--- a/apps/web/src/session-logic.ts
+++ b/apps/web/src/session-logic.ts
@@ -42,7 +42,9 @@ export interface WorkLogEntry {
tone: "thinking" | "tool" | "info" | "error";
toolTitle?: string;
itemType?: ToolLifecycleItemType;
+ itemId?: string;
requestKind?: PendingApproval["requestKind"];
+ collapseKey?: string;
}
interface DerivedWorkLogEntry extends WorkLogEntry {
@@ -461,14 +463,23 @@ export function deriveWorkLogEntries(
const ordered = [...activities].toSorted(compareActivitiesByOrder);
const entries = ordered
.filter((activity) => (latestTurnId ? activity.turnId === latestTurnId : true))
- .filter((activity) => activity.kind !== "tool.started")
+ .filter((activity) => {
+ if (activity.kind !== "tool.started") {
+ return true;
+ }
+ const payload =
+ activity.payload && typeof activity.payload === "object"
+ ? (activity.payload as Record)
+ : null;
+ return payload?.itemType === "command_execution";
+ })
.filter((activity) => activity.kind !== "task.started" && activity.kind !== "task.completed")
.filter((activity) => activity.kind !== "context-window.updated")
.filter((activity) => activity.summary !== "Checkpoint captured")
.filter((activity) => !isPlanBoundaryToolActivity(activity))
.map(toDerivedWorkLogEntry);
return collapseDerivedWorkLogEntries(entries).map(
- ({ activityKind: _activityKind, collapseKey: _collapseKey, ...entry }) => entry,
+ ({ activityKind: _activityKind, ...entry }) => entry,
);
}
@@ -500,6 +511,7 @@ function toDerivedWorkLogEntry(activity: OrchestrationThreadActivity): DerivedWo
activityKind: activity.kind,
};
const itemType = extractWorkLogItemType(payload);
+ const itemId = extractWorkLogItemId(payload);
const requestKind = extractWorkLogRequestKind(payload);
if (payload && typeof payload.detail === "string" && payload.detail.length > 0) {
const detail = stripTrailingExitCode(payload.detail).output;
@@ -519,6 +531,9 @@ function toDerivedWorkLogEntry(activity: OrchestrationThreadActivity): DerivedWo
if (itemType) {
entry.itemType = itemType;
}
+ if (itemId) {
+ entry.itemId = itemId;
+ }
if (requestKind) {
entry.requestKind = requestKind;
}
@@ -533,13 +548,28 @@ function collapseDerivedWorkLogEntries(
entries: ReadonlyArray,
): DerivedWorkLogEntry[] {
const collapsed: DerivedWorkLogEntry[] = [];
+ const openLifecycleRowIndexByCollapseKey = new Map();
for (const entry of entries) {
- const previous = collapsed.at(-1);
- if (previous && shouldCollapseToolLifecycleEntries(previous, entry)) {
- collapsed[collapsed.length - 1] = mergeDerivedWorkLogEntries(previous, entry);
- continue;
+ const collapseKey = entry.collapseKey;
+ if (collapseKey) {
+ const openIndex = openLifecycleRowIndexByCollapseKey.get(collapseKey);
+ if (openIndex !== undefined) {
+ const previous = collapsed[openIndex];
+ if (previous && shouldCollapseToolLifecycleEntries(previous, entry)) {
+ collapsed[openIndex] = mergeDerivedWorkLogEntries(previous, entry);
+ if (entry.activityKind === "tool.completed") {
+ openLifecycleRowIndexByCollapseKey.delete(collapseKey);
+ }
+ continue;
+ }
+ }
}
+
collapsed.push(entry);
+ if (collapseKey && (entry.activityKind === "tool.started" || entry.activityKind === "tool.updated")) {
+ openLifecycleRowIndexByCollapseKey.set(collapseKey, collapsed.length - 1);
+ continue;
+ }
}
return collapsed;
}
@@ -548,7 +578,11 @@ function shouldCollapseToolLifecycleEntries(
previous: DerivedWorkLogEntry,
next: DerivedWorkLogEntry,
): boolean {
- if (previous.activityKind !== "tool.updated" && previous.activityKind !== "tool.completed") {
+ if (
+ previous.activityKind !== "tool.started" &&
+ previous.activityKind !== "tool.updated" &&
+ previous.activityKind !== "tool.completed"
+ ) {
return false;
}
if (next.activityKind !== "tool.updated" && next.activityKind !== "tool.completed") {
@@ -596,16 +630,24 @@ function mergeChangedFiles(
}
function deriveToolLifecycleCollapseKey(entry: DerivedWorkLogEntry): string | undefined {
- if (entry.activityKind !== "tool.updated" && entry.activityKind !== "tool.completed") {
+ if (
+ entry.activityKind !== "tool.started" &&
+ entry.activityKind !== "tool.updated" &&
+ entry.activityKind !== "tool.completed"
+ ) {
return undefined;
}
+ const itemId = entry.itemId?.trim() ?? "";
+ if (itemId.length > 0) {
+ return itemId;
+ }
const normalizedLabel = normalizeCompactToolLabel(entry.toolTitle ?? entry.label);
- const detail = entry.detail?.trim() ?? "";
+ const commandOrDetail = (entry.command ?? entry.detail)?.trim() ?? "";
const itemType = entry.itemType ?? "";
- if (normalizedLabel.length === 0 && detail.length === 0 && itemType.length === 0) {
+ if (normalizedLabel.length === 0 && commandOrDetail.length === 0 && itemType.length === 0) {
return undefined;
}
- return [itemType, normalizedLabel, detail].join("\u001f");
+ return [itemType, normalizedLabel, commandOrDetail].join("\u001f");
}
function normalizeCompactToolLabel(value: string): string {
@@ -698,6 +740,10 @@ function extractWorkLogItemType(
return undefined;
}
+function extractWorkLogItemId(payload: Record | null): string | undefined {
+ return typeof payload?.itemId === "string" && payload.itemId.length > 0 ? payload.itemId : undefined;
+}
+
function extractWorkLogRequestKind(
payload: Record | null,
): WorkLogEntry["requestKind"] | undefined {