diff --git a/apps/server/src/services/threads/timeline.ts b/apps/server/src/services/threads/timeline.ts index 430d64660..eb8b0d70f 100644 --- a/apps/server/src/services/threads/timeline.ts +++ b/apps/server/src/services/threads/timeline.ts @@ -631,8 +631,30 @@ function filterExactEventRowsForRequestedTurn( ): FilterExactEventRowsForRequestedTurnResult { const rows: StoredEventRow[] = []; let removedRows = false; + // PROTOTYPE (#1714): item ids the requested turn started. A later turn's + // item/* row for one of these ids is the same lifecycle the projection + // merges by bare call id (upsertRunningExecCall keeps the first scope), so + // keep it. Another item/started for the id (ACP id reuse, #1398) ends the + // ownership. + const ownedItemIds = new Set(); for (const row of args.exactEventRows) { - if (row.scopeKind === "turn" && row.turnId !== args.turnId) { + if (row.scopeKind === "turn" && row.turnId === args.turnId) { + if (row.type === "item/started" && row.itemId !== null) { + ownedItemIds.add(row.itemId); + } + } else if (row.scopeKind === "turn" && row.turnId !== args.turnId) { + if (row.type === "item/started" && row.itemId !== null) { + ownedItemIds.delete(row.itemId); + } + if ( + row.itemId !== null && + row.itemKind !== "backgroundTask" && + row.type.startsWith("item/") && + ownedItemIds.has(row.itemId) + ) { + rows.push(row); + continue; + } removedRows = true; continue; }