| ... | @@ -98,15 +98,22 @@ export interface WorkLogEntry { | ... | @@ -98,15 +98,22 @@ export interface WorkLogEntry { |
| 98 | }; | 98 | }; |
| 99 | } | 99 | } |
| 100 | | 100 | |
| | 101 | const workLogCollapseKey = Symbol(); |
| | 102 | |
| 101 | interface DerivedWorkLogEntry extends WorkLogEntry { | 103 | interface 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 | } |
| 109 | | 111 | |
| | 112 | const derivedWorkLogEntryByActivity = new WeakMap< |
| | 113 | OrchestrationThreadActivity, |
| | 114 | DerivedWorkLogEntry |
| | 115 | >(); |
| | 116 | |
| 110 | export interface PendingApproval { | 117 | export 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 | } |
| 862 | | 866 | |
| 863 | function isPlanBoundaryToolActivity(activity: OrchestrationThreadActivity): boolean { | 867 | function isPlanBoundaryToolActivity(activity: OrchestrationThreadActivity): boolean { |
| ... | @@ -892,6 +896,10 @@ function extractWorkLogToolLifecycleStatus( | ... | @@ -892,6 +896,10 @@ function extractWorkLogToolLifecycleStatus( |
| 892 | } | 896 | } |
| 893 | | 897 | |
| 894 | function toDerivedWorkLogEntry(activity: OrchestrationThreadActivity): DerivedWorkLogEntry { | 898 | function 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 | } |
| 1001 | | 1010 | |
| ... | @@ -1026,7 +1035,10 @@ function agentSpawnGroupKey(entry: DerivedWorkLogEntry): string { | ... | @@ -1026,7 +1035,10 @@ function agentSpawnGroupKey(entry: DerivedWorkLogEntry): string { |
| 1026 | } | 1035 | } |
| 1027 | | 1036 | |
| 1028 | function toolLifecycleCollapseMapKey(entry: DerivedWorkLogEntry): string | undefined { | 1037 | function 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) { |