( messages: Message[], teamInfo?: TeamInfo, startingParentUuidHint?: UUID, allMessages?: readonly Message[], )
| 1524 | } |
| 1525 | |
| 1526 | export async function recordTranscript( |
| 1527 | messages: Message[], |
| 1528 | teamInfo?: TeamInfo, |
| 1529 | startingParentUuidHint?: UUID, |
| 1530 | allMessages?: readonly Message[], |
| 1531 | ): Promise<UUID | null> { |
| 1532 | const sessionId = getSessionId() as UUID |
| 1533 | const messageSet = await getSessionMessages(sessionId) |
| 1534 | |
| 1535 | // Fast path: new messages are almost always appended at the end. |
| 1536 | // Scan backward to find the first new message from the end. |
| 1537 | let firstNewIdx = messages.length |
| 1538 | while (firstNewIdx > 0 && messageSet.has(messages[firstNewIdx - 1]!.uuid as UUID)) { |
| 1539 | firstNewIdx-- |
| 1540 | } |
| 1541 | |
| 1542 | // All messages already recorded (loop decremented all the way to 0) |
| 1543 | if (firstNewIdx === 0) { |
| 1544 | const lastRecorded = messages.findLast( |
| 1545 | m => isChainParticipant(m) && messageSet.has(m.uuid as UUID), |
| 1546 | ) |
| 1547 | return (lastRecorded?.uuid as UUID | undefined) ?? startingParentUuidHint ?? null |
| 1548 | } |
| 1549 | |
| 1550 | // Verify the prefix before firstNewIdx looks like a normal contiguous |
| 1551 | // recorded prefix (not a compaction mix where new messages are interleaved). |
| 1552 | // Spot-check up to SPOT_CHECK_DEPTH messages before the boundary. |
| 1553 | const SPOT_CHECK_DEPTH = 10 |
| 1554 | const boundaryIdx = firstNewIdx - 1 |
| 1555 | const beforeNew = boundaryIdx >= 0 ? messages[boundaryIdx] : undefined |
| 1556 | let canFastPath = |
| 1557 | beforeNew !== undefined && |
| 1558 | messageSet.has(beforeNew.uuid as UUID) && |
| 1559 | isChainParticipant(beforeNew) |
| 1560 | |
| 1561 | if (canFastPath) { |
| 1562 | for (let i = boundaryIdx - 1; i >= boundaryIdx - SPOT_CHECK_DEPTH && i >= 0; i--) { |
| 1563 | if (!messageSet.has(messages[i]!.uuid as UUID)) { |
| 1564 | canFastPath = false |
| 1565 | break |
| 1566 | } |
| 1567 | } |
| 1568 | } |
| 1569 | |
| 1570 | if (!canFastPath) { |
| 1571 | return recordTranscriptFull( |
| 1572 | messages, |
| 1573 | teamInfo, |
| 1574 | startingParentUuidHint, |
| 1575 | allMessages, |
| 1576 | ) |
| 1577 | } |
| 1578 | |
| 1579 | // Fast path confirmed: only clean the new suffix |
| 1580 | const startingParentUuid = beforeNew.uuid as UUID |
| 1581 | const suffix = messages.slice(firstNewIdx) |
| 1582 | const cleanedMessages = cleanMessagesForLogging(suffix, allMessages) |
| 1583 | const newMessages: typeof cleanedMessages = [] |
no test coverage detected