Skip to content

feat(session-manager): ✨ add offset pagination to session_read tool#2114

Open
Lynricsy wants to merge 1 commit intocode-yeongyu:devfrom
Lynricsy:dev
Open

feat(session-manager): ✨ add offset pagination to session_read tool#2114
Lynricsy wants to merge 1 commit intocode-yeongyu:devfrom
Lynricsy:dev

Conversation

@Lynricsy
Copy link
Contributor

@Lynricsy Lynricsy commented Feb 25, 2026

Summary

  • Add 1-indexed offset parameter to session_read tool for message pagination, similar to how the file Read tool works
  • Users can now specify which message to start reading from (not just how many from the beginning)
  • Clear error messages for invalid offset values (< 1 or > total messages)
  • Pagination context header shows "Showing messages X-Y of Z" when offset/limit is used

Changes

File Change
types.ts Added offset?: number to SessionReadArgs
constants.ts Updated tool description with offset parameter docs and usage examples
tools.ts Added offset validation, pagination slicing logic, and conditional pagination info
session-formatter.ts Added pagination header and message numbering support
tools.test.ts Added 7 new test cases covering offset validation, pagination, and edge cases

Motivation

The existing session_read tool only supports limit (read N messages from the beginning), making it inconvenient to read recent messages in long sessions. This change adds offset support so users can jump to any position in the message history — useful since the most relevant information is often near the end of a session.

Testing

  • 21 tests pass (7 new + 14 existing), 0 failures
  • typecheck clean (tsc --noEmit)
  • No anti-patterns introduced

Summary by cubic

Added 1-indexed offset pagination to session_read so you can start reading from any message. Output now shows the message range and numbers each message, with clear errors for invalid offsets.

  • New Features
    • Added offset arg (1-indexed) that works with limit to return slices.
    • Validates offset bounds and returns helpful errors.
    • Shows "Showing messages X–Y of Z" and numbers each message.
    • Updated tool docs and added tests.

Written for commit e5d660b. Summary will update on new commits.

Add 1-indexed offset parameter for session_read, enabling pagination
similar to the file Read tool. Includes validation for out-of-range
offsets, pagination headers in output, and 7 new test cases.

Co-authored-by: Wine Fox <fox@ling.plus>
Copilot AI review requested due to automatic review settings February 25, 2026 08:33
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds 1-indexed offset support to the session_read tool so callers can paginate through session messages starting at an arbitrary message number, with updated formatting and tests.

Changes:

  • Extend SessionReadArgs and session_read tool schema to accept an offset parameter.
  • Implement offset/limit slicing and conditional pagination context passed into the session message formatter.
  • Update session message formatting to include a pagination header and message numbering; add tests for offset/limit behavior.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/tools/session-manager/types.ts Adds offset?: number to SessionReadArgs to support pagination.
src/tools/session-manager/tools.ts Adds offset validation and applies offset/limit slicing; passes pagination context to formatter.
src/tools/session-manager/session-formatter.ts Adds pagination header and message numbering to formatted output.
src/tools/session-manager/constants.ts Updates session_read tool description/docs to mention offset and pagination example.
src/tools/session-manager/tools.test.ts Adds new tests intended to cover offset validation and pagination formatting.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +150 to +154
beforeAll(() => {
mock.module("./storage", () => ({
sessionExists: () => Promise.resolve(true),
readSessionMessages: () => Promise.resolve([...mockMessages]),
readSessionTodos: () => Promise.resolve([]),
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

mock.module("./storage", ...) is set up in this hook, but session_read is instantiated earlier at module load time. With ESM imports, mocking after the tool is created may not replace the already-imported ./storage bindings, so these tests can unintentionally hit the real filesystem. Apply the mock before importing/creating the tools (e.g., dynamic import after mocking or create a fresh tool instance inside the setup).

Copilot uses AI. Check for mistakes.
const result = await session_read.execute({ session_id: "ses_mock", offset: 2, limit: 2 }, mockContext)

expect(result).not.toContain("Error")
expect(typeof result).toBe("string")
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

This test claims to "return correct slice" but only asserts that the result is a string and doesn’t contain "Error". Add assertions that the returned output includes the expected messages (and excludes messages outside the requested offset/limit), otherwise the new pagination logic isn’t actually verified.

Suggested change
expect(typeof result).toBe("string")
expect(typeof result).toBe("string")
// Expect pagination header for the slice corresponding to offset=2, limit=2 on 5 messages.
expect(result).toContain("Showing messages 2-3 of 5")
// Ensure headers for other ranges are not present.
expect(result).not.toContain("Showing messages 1-2 of 5")
expect(result).not.toContain("Showing messages 3-5 of 5")

Copilot uses AI. Check for mistakes.
Comment on lines +86 to +90
if (args.offset !== undefined) {
if (args.offset < 1) {
return `Error: offset must be >= 1, got ${args.offset}`
}
if (args.offset > totalMessages) {
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

offset accepts any number (including NaN/Infinity or non-integers). With the current checks, values like 1.5 or NaN can pass validation and then Array.slice() will coerce them, producing an incorrect start position. Validate that offset is a finite integer before using it (and return a clear error otherwise).

Copilot uses AI. Check for mistakes.
return `Error: offset ${args.offset} exceeds total message count (${totalMessages})`
}
}

Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

limit is also treated as a generic number; fractional/NaN values will be coerced by slice() (and negative values silently disable limiting). Since limit is described as a message count, it should be validated as a finite integer (and probably >= 1 when provided) to avoid surprising pagination behavior.

Suggested change
if (args.limit !== undefined) {
if (!Number.isFinite(args.limit) || !Number.isInteger(args.limit)) {
return `Error: limit must be a finite integer, got ${args.limit}`
}
if (args.limit < 1) {
return `Error: limit must be >= 1, got ${args.limit}`
}
}

Copilot uses AI. Check for mistakes.
if (pagination) {
const endIndex = pagination.offset + messages.length - 1
lines.push(`Showing messages ${pagination.offset}-${endIndex} of ${pagination.totalMessages}`)
lines.push("")
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

lines.push("") adds a blank line after the pagination header, but each message line is also prefixed with \n later. When pagination is set this results in extra blank lines (e.g., three consecutive newlines before the first message). Consider removing the empty-string push and/or removing the leading newline from message lines so spacing is consistent.

Suggested change
lines.push("")

Copilot uses AI. Check for mistakes.
const timestamp = msg.time?.created ? new Date(msg.time.created).toISOString() : "Unknown time"
const agent = msg.agent ? ` (${msg.agent})` : ""
lines.push(`\n[${msg.role}${agent}] ${timestamp}`)
lines.push(`\n[Message ${messageNumber}] ${msg.role}${agent} ${timestamp}`)
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The message header string begins with \n, which creates a leading blank line in the output and compounds with the pagination header spacing. If you want a blank line between messages, it’s usually clearer to add separators explicitly (e.g., push an empty line between messages) rather than embedding newlines in the content lines.

Copilot uses AI. Check for mistakes.
Comment on lines 33 to 37
Example output:
Session: ses_abc123
Messages: 45
Messages: 3-4 of 45
Date Range: 2025-12-20 to 2025-12-24

Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The SESSION_READ_DESCRIPTION example output includes Session: ..., Messages: ..., and Date Range: ..., but session_read currently returns only formatSessionMessages(...) output (no session/date-range header). This makes the tool docs misleading; update the example to match the real output or update session_read to actually include those header lines.

Copilot uses AI. Check for mistakes.
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 5 files

Confidence score: 4/5

  • This PR looks safe to merge with minimal risk; the only issue is a moderate-strength test weakness rather than a functional bug.
  • src/tools/session-manager/tools.test.ts claims to verify the correct slice but doesn’t assert slice contents, so it may miss regressions.
  • Pay close attention to src/tools/session-manager/tools.test.ts - strengthen assertions to validate slice contents.
Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/tools/session-manager/tools.test.ts">

<violation number="1" location="src/tools/session-manager/tools.test.ts:201">
P2: Test claims to verify 'correct slice' but has weak assertions that don't check slice contents</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

})
})

describe("#when session_read is called with offset=2 and limit=2", () => {
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 25, 2026

Choose a reason for hiding this comment

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

P2: Test claims to verify 'correct slice' but has weak assertions that don't check slice contents

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/tools/session-manager/tools.test.ts, line 201:

<comment>Test claims to verify 'correct slice' but has weak assertions that don't check slice contents</comment>

<file context>
@@ -134,3 +137,103 @@ describe("session-manager tools", () => {
+      })
+    })
+
+    describe("#when session_read is called with offset=2 and limit=2", () => {
+      test("#then executes without error and returns correct slice", async () => {
+        const result = await session_read.execute({ session_id: "ses_mock", offset: 2, limit: 2 }, mockContext)
</file context>
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants