diff --git a/packages/opencode/src/altimate/observability/tracing.ts b/packages/opencode/src/altimate/observability/tracing.ts index c653826789..cc199f9d1c 100644 --- a/packages/opencode/src/altimate/observability/tracing.ts +++ b/packages/opencode/src/altimate/observability/tracing.ts @@ -982,6 +982,32 @@ export class Trace { } } + /** + * List traces with pagination support. + * Returns a page of traces plus total count for building pagination UI. + */ + static async listTracesPaginated( + dir?: string, + options?: { offset?: number; limit?: number }, + ): Promise<{ + traces: Array<{ sessionId: string; file: string; trace: TraceFile }> + total: number + offset: number + limit: number + }> { + const all = await Trace.listTraces(dir) + const rawOffset = options?.offset ?? 0 + const rawLimit = options?.limit ?? 20 + const offset = Number.isFinite(rawOffset) ? Math.max(0, Math.trunc(rawOffset)) : 0 + const limit = Number.isFinite(rawLimit) ? Math.max(1, Math.trunc(rawLimit)) : 20 + return { + traces: all.slice(offset, offset + limit), + total: all.length, + offset, + limit, + } + } + static async loadTrace(sessionId: string, dir?: string): Promise { const tracesDir = dir ?? DEFAULT_TRACES_DIR try { diff --git a/packages/opencode/src/cli/cmd/trace.ts b/packages/opencode/src/cli/cmd/trace.ts index 3c9a0b1b9b..cf35ee71c8 100644 --- a/packages/opencode/src/cli/cmd/trace.ts +++ b/packages/opencode/src/cli/cmd/trace.ts @@ -49,13 +49,23 @@ function truncate(str: string, len: number): string { } // altimate_change start — trace: list session traces (recordings/recaps of agent sessions) -function listTraces(traces: Array<{ sessionId: string; trace: TraceFile }>, tracesDir?: string) { - if (traces.length === 0) { +function listTraces( + traces: Array<{ sessionId: string; trace: TraceFile }>, + pagination: { total: number; offset: number; limit: number }, + tracesDir?: string, +) { + if (traces.length === 0 && pagination.total === 0) { UI.println("No traces found. Run a command with tracing enabled:") UI.println(" altimate-code run \"your prompt here\"") return } + if (traces.length === 0 && pagination.total > 0) { + UI.println(`No traces on this page (offset ${pagination.offset} past end of ${pagination.total} traces).`) + UI.println(UI.Style.TEXT_DIM + `Try: altimate-code trace list --offset 0 --limit ${pagination.limit}` + UI.Style.TEXT_NORMAL) + return + } + // Header const header = [ "DATE".padEnd(13), @@ -97,8 +107,13 @@ function listTraces(traces: Array<{ sessionId: string; trace: TraceFile }>, trac } UI.empty() - // altimate_change start — trace: session trace messages - UI.println(UI.Style.TEXT_DIM + `${traces.length} trace(s) in ${Trace.getTracesDir(tracesDir)}` + UI.Style.TEXT_NORMAL) + // altimate_change start — trace: session trace messages with pagination footer + const rangeStart = pagination.offset + 1 + const rangeEnd = pagination.offset + traces.length + UI.println(UI.Style.TEXT_DIM + `Showing ${rangeStart}-${rangeEnd} of ${pagination.total} trace(s) in ${Trace.getTracesDir(tracesDir)}` + UI.Style.TEXT_NORMAL) + if (rangeEnd < pagination.total) { + UI.println(UI.Style.TEXT_DIM + `Next page: altimate-code trace list --offset ${rangeEnd} --limit ${pagination.limit}` + UI.Style.TEXT_NORMAL) + } UI.println(UI.Style.TEXT_DIM + "View a trace: altimate-code trace view " + UI.Style.TEXT_NORMAL) // altimate_change end } @@ -134,6 +149,11 @@ export const TraceCommand = cmd({ describe: "number of traces to show", default: 20, }) + .option("offset", { + type: "number", + describe: "number of traces to skip (for pagination)", + default: 0, + }) .option("live", { type: "boolean", describe: "auto-refresh the viewer as the trace updates (for in-progress sessions)", @@ -148,8 +168,11 @@ export const TraceCommand = cmd({ const tracesDir = (cfg as any).tracing?.dir as string | undefined if (action === "list") { - const traces = await Trace.listTraces(tracesDir) - listTraces(traces.slice(0, args.limit || 20), tracesDir) + const page = await Trace.listTracesPaginated(tracesDir, { + offset: args.offset || 0, + limit: args.limit || 20, + }) + listTraces(page.traces, page, tracesDir) return } @@ -168,7 +191,7 @@ export const TraceCommand = cmd({ if (!match) { UI.error(`Trace not found: ${args.id}`) UI.println("Available traces:") - listTraces(traces.slice(0, 10), tracesDir) + listTraces(traces.slice(0, 10), { total: traces.length, offset: 0, limit: 10 }, tracesDir) process.exit(1) } diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-trace-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-trace-list.tsx index 0f6100a0d7..cfb07db9c5 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-trace-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-trace-list.tsx @@ -61,7 +61,7 @@ export function DialogTraceList(props: { }) } - result.push(...items.slice(0, 50).map((item) => { + result.push(...items.map((item) => { const rawStartedAt = item.trace.startedAt const parsedDate = typeof rawStartedAt === "string" || typeof rawStartedAt === "number" ? new Date(rawStartedAt)