authorgravatar for 62337003+Bil0000@users.noreply.github.comBilal Bakr <62337003+Bil0000@users.noreply.github.com> 2026-08-23 23:53:54+03:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-23 16:53:54-04:00
logfa219001dc2f14cfd9c7774c2c03c153359144be
treedfa4b18ad380f9599aef5b9e158a6a8bb8ebe04a
parent5427ca056270ad49ac88431ac1fb8bb54dd7b91b
signature Signed by PGP key B5690EEEBB952194

perf(web): reuse work log rows during streaming (#8006)


2 files changed, 102 insertions(+), 20 deletions(-)

apps/web/src/session-logic.test.ts+61
...@@ -2205,3 +2205,64 @@ describe("rerun workflows", () => {...@@ -2205,3 +2205,64 @@ describe("rerun workflows", () => {
2205 expect(spawnRows.map((row) => row.turnId)).toEqual(["turn-1", "turn-2"]);2205 expect(spawnRows.map((row) => row.turnId)).toEqual(["turn-1", "turn-2"]);
2206 });2206 });
2207});2207});
2208
2209describe("session activity performance", () => {
2210 it("reuses entries for unchanged activities", () => {
2211 const activities = ["status", "diff", "log"].map((command, index) =>
2212 makeActivity({
2213 id: `stable-tool-${index}`,
2214 kind: "tool.completed",
2215 sequence: index,
2216 payload: {
2217 itemType: "command_execution",
2218 data: { toolCallId: `stable-tool-${index}`, item: { command: ["git", command] } },
2219 },
2220 }),
2221 );
2222
2223 const initialEntries = deriveWorkLogEntries(activities.slice(0, 2));
2224 const appendedEntries = deriveWorkLogEntries(activities);
2225 expect(appendedEntries[0]).toBe(initialEntries[0]);
2226 expect(appendedEntries[1]).toBe(initialEntries[1]);
2227 });
2228
2229 it("updates 20,000 ordered tool activities within 100 ms", () => {
2230 const activities = Array.from({ length: 20_000 }, (_, index) =>
2231 makeActivity({
2232 id: `benchmark-tool-${index}`,
2233 createdAt: new Date(1_700_000_000_000 + index).toISOString(),
2234 kind: "tool.completed",
2235 summary: "Ran command",
2236 sequence: index,
2237 payload: {
2238 itemType: "command_execution",
2239 title: "Ran command",
2240 data: {
2241 toolCallId: `benchmark-tool-${index}`,
2242 item: { command: ["git", "status"] },
2243 },
2244 },
2245 }),
2246 );
2247 deriveWorkLogEntries(activities);
2248 const updatedActivities = [
2249 ...activities,
2250 makeActivity({
2251 id: "benchmark-tool-appended",
2252 createdAt: new Date(1_700_000_000_000 + activities.length).toISOString(),
2253 kind: "tool.completed",
2254 summary: "Ran command",
2255 sequence: activities.length,
2256 payload: {
2257 itemType: "command_execution",
2258 title: "Ran command",
2259 data: { toolCallId: "benchmark-tool-appended", item: { command: ["git", "diff"] } },
2260 },
2261 }),
2262 ];
2263
2264 const startedAt = performance.now();
2265 expect(deriveWorkLogEntries(updatedActivities)).toHaveLength(20_001);
2266 expect(performance.now() - startedAt).toBeLessThan(100);
2267 });
2268});
apps/web/src/session-logic.ts+41-20
...@@ -98,15 +98,22 @@ export interface WorkLogEntry {...@@ -98,15 +98,22 @@ export interface WorkLogEntry {
98 };98 };
99}99}
100100
101const workLogCollapseKey = Symbol();
102
101interface DerivedWorkLogEntry extends WorkLogEntry {103interface DerivedWorkLogEntry extends WorkLogEntry {
102 activityKind: OrchestrationThreadActivity["kind"];104 sourceActivityKind: OrchestrationThreadActivity["kind"];
103 collapseKey?: string;105 [workLogCollapseKey]?: string;
104 toolCallId?: string;106 toolCallId?: string;
105 isWorkflowCoordinator?: boolean;107 isWorkflowCoordinator?: boolean;
106 /** Shell/monitor/plan tasks: ordinary work-log rows, never spawn CTAs. */108 /** Shell/monitor/plan tasks: ordinary work-log rows, never spawn CTAs. */
107 isBackgroundTask?: boolean;109 isBackgroundTask?: boolean;
108}110}
109111
112const derivedWorkLogEntryByActivity = new WeakMap<
113 OrchestrationThreadActivity,
114 DerivedWorkLogEntry
115>();
116
110export interface PendingApproval {117export interface PendingApproval {
111 requestId: ApprovalRequestId;118 requestId: ApprovalRequestId;
112 requestKind: "command" | "file-read" | "file-change";119 requestKind: "command" | "file-read" | "file-change";
...@@ -854,10 +861,7 @@ export function deriveWorkLogEntries(...@@ -854,10 +861,7 @@ export function deriveWorkLogEntries(
854 if (isAgentInternalActivity(activity)) continue;861 if (isAgentInternalActivity(activity)) continue;
855 entries.push(toDerivedWorkLogEntry(activity));862 entries.push(toDerivedWorkLogEntry(activity));
856 }863 }
857 return collapseDerivedWorkLogEntries(entries).map((entry) => {864 return collapseDerivedWorkLogEntries(entries);
858 const { activityKind, collapseKey: _collapseKey, ...rest } = entry;
859 return Object.assign(rest, { sourceActivityKind: activityKind });
860 });
861}865}
862866
863function isPlanBoundaryToolActivity(activity: OrchestrationThreadActivity): boolean {867function isPlanBoundaryToolActivity(activity: OrchestrationThreadActivity): boolean {
...@@ -892,6 +896,10 @@ function extractWorkLogToolLifecycleStatus(...@@ -892,6 +896,10 @@ function extractWorkLogToolLifecycleStatus(
892}896}
893897
894function toDerivedWorkLogEntry(activity: OrchestrationThreadActivity): DerivedWorkLogEntry {898function toDerivedWorkLogEntry(activity: OrchestrationThreadActivity): DerivedWorkLogEntry {
899 const cachedEntry = derivedWorkLogEntryByActivity.get(activity);
900 if (cachedEntry) {
901 return cachedEntry;
902 }
895 const payload =903 const payload =
896 activity.payload && typeof activity.payload === "object"904 activity.payload && typeof activity.payload === "object"
897 ? (activity.payload as Record<string, unknown>)905 ? (activity.payload as Record<string, unknown>)
...@@ -935,7 +943,7 @@ function toDerivedWorkLogEntry(activity: OrchestrationThreadActivity): DerivedWo...@@ -935,7 +943,7 @@ function toDerivedWorkLogEntry(activity: OrchestrationThreadActivity): DerivedWo
935 : activity.tone === "approval"943 : activity.tone === "approval"
936 ? "info"944 ? "info"
937 : activity.tone,945 : activity.tone,
938 activityKind: activity.kind,946 sourceActivityKind: activity.kind,
939 };947 };
940 const itemType = extractWorkLogItemType(payload);948 const itemType = extractWorkLogItemType(payload);
941 const requestKind = extractWorkLogRequestKind(payload);949 const requestKind = extractWorkLogRequestKind(payload);
...@@ -994,8 +1002,9 @@ function toDerivedWorkLogEntry(activity: OrchestrationThreadActivity): DerivedWo...@@ -994,8 +1002,9 @@ function toDerivedWorkLogEntry(activity: OrchestrationThreadActivity): DerivedWo
994 }1002 }
995 const collapseKey = deriveToolLifecycleCollapseKey(entry);1003 const collapseKey = deriveToolLifecycleCollapseKey(entry);
996 if (collapseKey) {1004 if (collapseKey) {
997 entry.collapseKey = collapseKey;1005 entry[workLogCollapseKey] = collapseKey;
998 }1006 }
1007 derivedWorkLogEntryByActivity.set(activity, entry);
999 return entry;1008 return entry;
1000}1009}
10011010
...@@ -1026,7 +1035,10 @@ function agentSpawnGroupKey(entry: DerivedWorkLogEntry): string {...@@ -1026,7 +1035,10 @@ function agentSpawnGroupKey(entry: DerivedWorkLogEntry): string {
1026}1035}
10271036
1028function toolLifecycleCollapseMapKey(entry: DerivedWorkLogEntry): string | undefined {1037function toolLifecycleCollapseMapKey(entry: DerivedWorkLogEntry): string | undefined {
1029 if (entry.activityKind !== "tool.updated" && entry.activityKind !== "tool.completed") {1038 if (
1039 entry.sourceActivityKind !== "tool.updated" &&
1040 entry.sourceActivityKind !== "tool.completed"
1041 ) {
1030 return undefined;1042 return undefined;
1031 }1043 }
1032 return entry.toolCallId ? `tool:${entry.turnId ?? "no-turn"}:${entry.toolCallId}` : undefined;1044 return entry.toolCallId ? `tool:${entry.turnId ?? "no-turn"}:${entry.toolCallId}` : undefined;
...@@ -1053,9 +1065,9 @@ function collapseDerivedWorkLogEntries(...@@ -1053,9 +1065,9 @@ function collapseDerivedWorkLogEntries(
1053 const isTaskRow =1065 const isTaskRow =
1054 entry.taskId !== undefined &&1066 entry.taskId !== undefined &&
1055 !entry.isBackgroundTask &&1067 !entry.isBackgroundTask &&
1056 (entry.activityKind === "task.started" ||1068 (entry.sourceActivityKind === "task.started" ||
1057 entry.activityKind === "task.progress" ||1069 entry.sourceActivityKind === "task.progress" ||
1058 entry.activityKind === "task.completed");1070 entry.sourceActivityKind === "task.completed");
1059 if (isTaskRow && entry.taskId !== undefined) {1071 if (isTaskRow && entry.taskId !== undefined) {
1060 const rememberedKey = groupKeyByTaskId.get(entry.taskId);1072 const rememberedKey = groupKeyByTaskId.get(entry.taskId);
1061 const groupKey = rememberedKey ?? agentSpawnGroupKey(entry);1073 const groupKey = rememberedKey ?? agentSpawnGroupKey(entry);
...@@ -1131,19 +1143,25 @@ function shouldCollapseToolLifecycleEntries(...@@ -1131,19 +1143,25 @@ function shouldCollapseToolLifecycleEntries(
1131 previous: DerivedWorkLogEntry,1143 previous: DerivedWorkLogEntry,
1132 next: DerivedWorkLogEntry,1144 next: DerivedWorkLogEntry,
1133): boolean {1145): boolean {
1134 if (previous.activityKind !== "tool.updated" && previous.activityKind !== "tool.completed") {1146 if (
1147 previous.sourceActivityKind !== "tool.updated" &&
1148 previous.sourceActivityKind !== "tool.completed"
1149 ) {
1135 return false;1150 return false;
1136 }1151 }
1137 if (next.activityKind !== "tool.updated" && next.activityKind !== "tool.completed") {1152 if (next.sourceActivityKind !== "tool.updated" && next.sourceActivityKind !== "tool.completed") {
1138 return false;1153 return false;
1139 }1154 }
1140 if (previous.turnId !== next.turnId) {1155 if (previous.turnId !== next.turnId) {
1141 return false;1156 return false;
1142 }1157 }
1143 if (previous.activityKind === "tool.completed") {1158 if (previous.sourceActivityKind === "tool.completed") {
1144 return false;1159 return false;
1145 }1160 }
1146 if (previous.collapseKey !== undefined && previous.collapseKey === next.collapseKey) {1161 if (
1162 previous[workLogCollapseKey] !== undefined &&
1163 previous[workLogCollapseKey] === next[workLogCollapseKey]
1164 ) {
1147 return true;1165 return true;
1148 }1166 }
1149 return (1167 return (
...@@ -1166,7 +1184,7 @@ function mergeDerivedWorkLogEntries(...@@ -1166,7 +1184,7 @@ function mergeDerivedWorkLogEntries(
1166 const toolTitle = next.toolTitle ?? previous.toolTitle;1184 const toolTitle = next.toolTitle ?? previous.toolTitle;
1167 const itemType = next.itemType ?? previous.itemType;1185 const itemType = next.itemType ?? previous.itemType;
1168 const requestKind = next.requestKind ?? previous.requestKind;1186 const requestKind = next.requestKind ?? previous.requestKind;
1169 const collapseKey = next.collapseKey ?? previous.collapseKey;1187 const collapseKey = next[workLogCollapseKey] ?? previous[workLogCollapseKey];
1170 const toolCallId = next.toolCallId ?? previous.toolCallId;1188 const toolCallId = next.toolCallId ?? previous.toolCallId;
1171 const toolLifecycleStatus = next.toolLifecycleStatus ?? previous.toolLifecycleStatus;1189 const toolLifecycleStatus = next.toolLifecycleStatus ?? previous.toolLifecycleStatus;
1172 const toolData = next.toolData ?? previous.toolData;1190 const toolData = next.toolData ?? previous.toolData;
...@@ -1180,7 +1198,7 @@ function mergeDerivedWorkLogEntries(...@@ -1180,7 +1198,7 @@ function mergeDerivedWorkLogEntries(
1180 ...(toolTitle ? { toolTitle } : {}),1198 ...(toolTitle ? { toolTitle } : {}),
1181 ...(itemType ? { itemType } : {}),1199 ...(itemType ? { itemType } : {}),
1182 ...(requestKind ? { requestKind } : {}),1200 ...(requestKind ? { requestKind } : {}),
1183 ...(collapseKey ? { collapseKey } : {}),1201 ...(collapseKey ? { [workLogCollapseKey]: collapseKey } : {}),
1184 ...(toolCallId ? { toolCallId } : {}),1202 ...(toolCallId ? { toolCallId } : {}),
1185 ...(toolLifecycleStatus !== undefined ? { toolLifecycleStatus } : {}),1203 ...(toolLifecycleStatus !== undefined ? { toolLifecycleStatus } : {}),
1186 ...(toolData !== undefined ? { toolData } : {}),1204 ...(toolData !== undefined ? { toolData } : {}),
...@@ -1203,11 +1221,14 @@ function deriveToolLifecycleCollapseKey(entry: DerivedWorkLogEntry): string | un...@@ -1203,11 +1221,14 @@ function deriveToolLifecycleCollapseKey(entry: DerivedWorkLogEntry): string | un
1203 // progress ticks fold into it, the terminal row wins the label.1221 // progress ticks fold into it, the terminal row wins the label.
1204 if (1222 if (
1205 entry.taskId &&1223 entry.taskId &&
1206 (entry.activityKind === "task.progress" || entry.activityKind === "task.completed")1224 (entry.sourceActivityKind === "task.progress" || entry.sourceActivityKind === "task.completed")
1207 ) {1225 ) {
1208 return `task${entry.taskId}`;1226 return `task${entry.taskId}`;
1209 }1227 }
1210 if (entry.activityKind !== "tool.updated" && entry.activityKind !== "tool.completed") {1228 if (
1229 entry.sourceActivityKind !== "tool.updated" &&
1230 entry.sourceActivityKind !== "tool.completed"
1231 ) {
1211 return undefined;1232 return undefined;
1212 }1233 }
1213 if (entry.toolCallId) {1234 if (entry.toolCallId) {