fix(cursor-history): align pagination with visible rows

Why:
- Cursor normalization emits internal tool_result items so tool outputs can attach to tool cards.
- The UI does not render tool_result as standalone rows.
- Pagination previously mixed datasets: total excluded tool_result, but page slicing and
  hasMore used the unfiltered collection.
- That mismatch caused offset and limit drift versus what users actually see.
- Tool normalization also renamed ApplyPatch to Edit before input normalization.
- That hid the original tool identity from normalizeCursorToolInput and could drop
  patch-specific shaping.

What changed:
- Added one pagination source of truth: renderableMessages filters out tool_result.
- total, page slicing, and hasMore now all use renderableMessages.
- Unlimited-history responses now return renderableMessages for consistent semantics.
- normalizeCursorToolInput now receives rawToolName first.
- The user-facing rename from ApplyPatch to Edit is still preserved in toolName.

Impact:
- Offset and limit now map to visible Cursor rows.
- hasMore reflects remaining renderable history.
- ApplyPatch payloads keep patch-aware normalization while preserving UI naming.
This commit is contained in:
Haileyesus
2026-05-08 16:03:14 +03:00
parent de25f6d78e
commit 54130e8d14

View File

@@ -386,22 +386,17 @@ export class CursorSessionsProvider implements IProviderSessions {
try { try {
const blobs = await this.loadCursorBlobs(sessionId, projectPath); const blobs = await this.loadCursorBlobs(sessionId, projectPath);
const allNormalized = this.normalizeCursorBlobs(blobs, sessionId); const allNormalized = this.normalizeCursorBlobs(blobs, sessionId);
const totalNormalized = allNormalized.length; const renderableMessages = allNormalized.filter((msg) => msg.kind !== 'tool_result');
let total = 0; const total = renderableMessages.length;
for (const msg of allNormalized) {
if (msg.kind !== 'tool_result') {
total += 1;
}
}
if (limit !== null) { if (limit !== null) {
const start = offset; const start = offset;
const page = limit === 0 const page = limit === 0
? [] ? []
: allNormalized.slice(start, start + limit); : renderableMessages.slice(start, start + limit);
const hasMore = limit === 0 const hasMore = limit === 0
? start < totalNormalized ? start < total
: start + limit < totalNormalized; : start + limit < total;
return { return {
messages: page, messages: page,
total, total,
@@ -412,7 +407,7 @@ export class CursorSessionsProvider implements IProviderSessions {
} }
return { return {
messages: allNormalized, messages: renderableMessages,
total, total,
hasMore: false, hasMore: false,
offset: 0, offset: 0,
@@ -560,6 +555,7 @@ export class CursorSessionsProvider implements IProviderSessions {
|| normalizeToolId(part.tool_call_id) || normalizeToolId(part.tool_call_id)
|| normalizeToolId(part.id) || normalizeToolId(part.id)
|| `tool_${i}_${partIdx}`; || `tool_${i}_${partIdx}`;
const normalizedToolInput = normalizeCursorToolInput(rawToolName, part.args ?? part.input);
const message = createNormalizedMessage({ const message = createNormalizedMessage({
id: `${baseId}_${partIdx}`, id: `${baseId}_${partIdx}`,
sessionId, sessionId,
@@ -567,7 +563,7 @@ export class CursorSessionsProvider implements IProviderSessions {
provider: PROVIDER, provider: PROVIDER,
kind: 'tool_use', kind: 'tool_use',
toolName, toolName,
toolInput: normalizeCursorToolInput(toolName, part.args ?? part.input), toolInput: normalizedToolInput,
toolId, toolId,
}); });
messages.push(message); messages.push(message);