Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions assistant/src/daemon/handlers/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -740,10 +740,12 @@ export function handleHistoryRequest(

// Apply text truncation when maxTextChars is set
let wasTruncated = false;
let textWasTruncated = false;
let text = m.text;
if (msg.maxTextChars !== undefined && text.length > msg.maxTextChars) {
text = text.slice(0, msg.maxTextChars) + ' \u2026 [truncated]';
wasTruncated = true;
textWasTruncated = true;
}

// Apply tool result truncation when maxToolResultChars is set
Expand All @@ -764,8 +766,8 @@ export function handleHistoryRequest(
timestamp: m.timestamp,
...(truncatedToolCalls.length > 0 ? { toolCalls: truncatedToolCalls, toolCallsBeforeText: m.toolCallsBeforeText } : {}),
...(attachments ? { attachments } : {}),
...(!wasTruncated && m.textSegments.length > 0 ? { textSegments: m.textSegments } : {}),
...(!wasTruncated && m.contentOrder.length > 0 ? { contentOrder: m.contentOrder } : {}),
...(!textWasTruncated && m.textSegments.length > 0 ? { textSegments: m.textSegments } : {}),
...(!textWasTruncated && m.contentOrder.length > 0 ? { contentOrder: m.contentOrder } : {}),
...(filteredSurfaces.length > 0 ? { surfaces: filteredSurfaces } : {}),
...(m.subagentNotification ? { subagentNotification: m.subagentNotification } : {}),
...(wasTruncated ? { wasTruncated: true } : {}),
Expand Down Expand Up @@ -933,7 +935,7 @@ export function handleMessageContentRequest(
// following user message rather than inline with the assistant message.
// This mirrors the mergeToolResults logic used by handleHistoryRequest.
if (dbMessage.role === 'assistant' && mergedToolCalls.some((tc) => tc.result === undefined)) {
const nextMsg = conversationStore.getNextMessage(msg.sessionId, dbMessage.createdAt);
const nextMsg = conversationStore.getNextMessage(msg.sessionId, dbMessage.createdAt, dbMessage.id);
if (nextMsg && nextMsg.role === 'user') {
try {
const nextContent = JSON.parse(nextMsg.content);
Expand Down
13 changes: 8 additions & 5 deletions assistant/src/memory/conversation-store.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { and, asc, count, desc, eq, gt, inArray, isNull, lt, lte, ne, sql } from 'drizzle-orm';
import { and, asc, count, desc, eq, gt, gte, inArray, isNull, lt, lte, ne, sql } from 'drizzle-orm';
import { v4 as uuid } from 'uuid';
import { z } from 'zod';

Expand Down Expand Up @@ -339,17 +339,20 @@ export function getMessageById(messageId: string, conversationId?: string): Mess
}

/**
* Get the next message in a conversation after a given message (by timestamp).
* Used for legacy tool_result merging in the rehydrate endpoint.
* Get the next message in a conversation after a given message.
* Uses gte + ne(id) instead of gt on timestamp so that messages sharing the
* same millisecond (common in legacy conversations where an assistant turn and
* the following user tool_result are saved in the same tick) are not skipped.
*/
export function getNextMessage(conversationId: string, afterTimestamp: number): MessageRow | null {
export function getNextMessage(conversationId: string, afterTimestamp: number, excludeMessageId: string): MessageRow | null {
const db = getDb();
const row = db
.select()
.from(messages)
.where(and(
eq(messages.conversationId, conversationId),
gt(messages.createdAt, afterTimestamp),
gte(messages.createdAt, afterTimestamp),
ne(messages.id, excludeMessageId),
Comment on lines +354 to +355
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Select true successor for same-millisecond messages

Using gte(messages.createdAt, afterTimestamp) with only ne(messages.id, excludeMessageId) can pick a message that is earlier than the current one when multiple rows share the same millisecond, because the query still orders solely by createdAt and has no tie-breaker relative to the excluded row. In the handleMessageContentRequest legacy merge flow, that means the lookup can return a preceding user/assistant row instead of the following tool_result row, so tool results may be merged incorrectly or not merged at all for bulk-imported legacy conversations with identical timestamps.

Useful? React with 👍 / 👎.

))
.orderBy(asc(messages.createdAt))
.limit(1)
Expand Down