From 9108ed7ed22ce31e28a6226c4ed1715f28288ac1 Mon Sep 17 00:00:00 2001 From: hbrooks Date: Thu, 16 Jul 2026 22:50:03 -0400 Subject: [PATCH] Rename session transcript read: steps -> records Follows ellipsis-dev/ellipsis#5869 (merged), which renamed GET /v1/sessions/{id}/steps -> /records (native session_records: transcript + lifecycle, feed_seq-ordered; no compat shim). - verb: agent session steps -> agent session records - connect flag: --no-steps -> --no-records - ApiClient.getAgentSessionSteps -> getAgentSessionRecords, path /records - ListSessionStepsResponse -> ListSessionRecordsResponse - comments/README/help text to match The search scope 'steps' (sessions search arm) and the WS frame names are separate server contracts and unchanged. --- README.md | 2 +- src/commands/connect.ts | 26 ++++++++++++++------------ src/commands/session.tsx | 18 +++++++++--------- src/lib/api.ts | 8 ++++---- src/lib/steps.ts | 2 +- src/lib/types.ts | 7 +++---- src/ui/ConnectApp.tsx | 6 +++--- test/search.test.ts | 6 +++--- 8 files changed, 38 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index aab211a..ea2cfd8 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ agent session search "webhook retries" # search session history: transcripts, agent session search "acme/api#512" --author tony --since "3 days ago" # PR-shaped queries and facets agent session get # inspect one session (prints a dashboard link) agent session get --watch # follow a session until it finishes -agent session steps # read a session's stored transcript, one line per step +agent session records # read a session's stored transcript, one line per record agent session connect # connect to a session: transcript + live output + send messages agent session connect # inside an Ellipsis sandbox: connects to the running session agent session stop # stop an in-flight session diff --git a/src/commands/connect.ts b/src/commands/connect.ts index fa1dca1..bcba8c5 100644 --- a/src/commands/connect.ts +++ b/src/commands/connect.ts @@ -68,7 +68,7 @@ export function registerConnect(session: Command): void { .description( 'Connect to a cloud session: view the conversation, follow it live, and send messages', ) - .option('--no-steps', 'skip replaying prior steps on open') + .option('--no-records', 'skip replaying prior records on open') .option( '--no-input', 'follow read-only: never open the composer, even on a keyed session (for non-interactive callers)', @@ -79,17 +79,19 @@ export function registerConnect(session: Command): void { the session inbox — single-writer-safe and usable headless / inside a sandbox. Pass --no-input to follow read-only from a script or agent (no TTY needed).`, ) - .action(async (sessionId: string | undefined, opts: { steps: boolean; input: boolean }) => { - await runAction(async () => { - const id = resolveConnectSessionId(sessionId) - await runConnect(id, opts.steps, !opts.input) - }) - }) + .action( + async (sessionId: string | undefined, opts: { records: boolean; input: boolean }) => { + await runAction(async () => { + const id = resolveConnectSessionId(sessionId) + await runConnect(id, opts.records, !opts.input) + }) + }, + ) } export async function runConnect( sessionId: string, - showSteps: boolean, + showRecords: boolean, readOnly = false, ): Promise { const api = new ApiClient() @@ -114,15 +116,15 @@ export async function runConnect( console.log('type to send · /stop ends the turn · /exit or Ctrl+C detaches') } - // Fetch the stored records to seed the transcript (unless --no-steps), the + // Fetch the stored records to seed the transcript (unless --no-records), the // live-refresh cursor (so live updates only append what's new), and the // opening spend. Records are ordered by feed_seq (the shared transcript + // lifecycle feed); a claude_code record's payload is the Claude Code event // the UI renders live, a lifecycle record renders as a notice line. - const records = await api.getAgentSessionSteps(sessionId) + const records = await api.getAgentSessionRecords(sessionId) const ordered = [...records].sort((a, b) => a.feed_seq - b.feed_seq) const initialMaxFeedSeq = ordered.reduce((m, s) => Math.max(m, s.feed_seq), 0) - const initialItems = showSteps + const initialItems = showRecords ? ordered.flatMap((st) => recordToItems(st, `s${st.feed_seq}`)) : [] const initialCost = foldCosts(ordered.map((st) => st.payload as CCEvent)) @@ -135,7 +137,7 @@ export async function runConnect( wsBase, canSend, initialItems, - // Always advance the cursor past existing records: --no-steps skips + // Always advance the cursor past existing records: --no-records skips // *rendering* history, not re-streaming it live. initialMaxFeedSeq, initialStatus: session.surface?.status ?? session.status, diff --git a/src/commands/session.tsx b/src/commands/session.tsx index cc0aeea..7f7e381 100644 --- a/src/commands/session.tsx +++ b/src/commands/session.tsx @@ -381,30 +381,30 @@ export function registerSession(program: Command): void { } } console.log( - '\nInspect one: agent session get ; full transcript: agent session steps ', + '\nInspect one: agent session get ; full transcript: agent session records ', ) }) }, ) session - .command('steps ') - .description("Read a session's steps (GET /v1/sessions/{id}/steps)") - .option('--json', 'output raw JSON (full step payloads)') + .command('records ') + .description("Read a session's records (GET /v1/sessions/{id}/records)") + .option('--json', 'output raw JSON (full record payloads)') .action(async (sessionId: string, opts: { json?: boolean }) => { await runAction(async () => { - const steps = await new ApiClient().getAgentSessionSteps(sessionId) + const records = await new ApiClient().getAgentSessionRecords(sessionId) if (opts.json) { - printJson(steps) + printJson(records) return } - if (steps.length === 0) { - console.log('No steps recorded for this session.') + if (records.length === 0) { + console.log('No records stored for this session.') return } // Feed order (transcript + lifecycle merged), one line per record; // --json has the full payloads. - const ordered = [...steps].sort((a, b) => a.feed_seq - b.feed_seq) + const ordered = [...records].sort((a, b) => a.feed_seq - b.feed_seq) for (const record of ordered) console.log(formatStepLine(record)) }) }) diff --git a/src/lib/api.ts b/src/lib/api.ts index d433e6f..9cc0e05 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -32,7 +32,7 @@ import type { ListGithubRepositoriesResponse, ListLinearTeamsResponse, ListSentryOrganizationsResponse, - ListSessionStepsResponse, + ListSessionRecordsResponse, ListSessionTranscriptsResponse, ListSlackChannelsResponse, ListSlackMembersResponse, @@ -197,10 +197,10 @@ export class ApiClient { // The session's full stored transcript as native session_records (transcript // + lifecycle), ordered by feed_seq. - async getAgentSessionSteps(sessionId: string): Promise { - const res = await this.request( + async getAgentSessionRecords(sessionId: string): Promise { + const res = await this.request( 'GET', - `/v1/sessions/${encodeURIComponent(sessionId)}/steps`, + `/v1/sessions/${encodeURIComponent(sessionId)}/records`, ) return res.records } diff --git a/src/lib/steps.ts b/src/lib/steps.ts index f72e46d..c830d98 100644 --- a/src/lib/steps.ts +++ b/src/lib/steps.ts @@ -1,7 +1,7 @@ import { formatTs } from './output' import type { SessionRecord } from './types' -// Step-rendering helpers shared by `session steps` and `session connect` +// Record-rendering helpers shared by `session records` and `session connect` // (moved out of commands/session.tsx so connect.ts can use them without an // import cycle; session.tsx re-exports them for compatibility). diff --git a/src/lib/types.ts b/src/lib/types.ts index 757f32c..6f20b08 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -284,9 +284,8 @@ export interface ListAgentSessionsQuery { author_id?: number } -// ------------------------------ session steps ---------------------------- +// ----------------------------- session records --------------------------- -// One stored transcript event, from GET /v1/sessions/{id}/steps. Loosely // The render switch on a session_record (session_records.source). The CLI // renders claude_code records natively and lifecycle records as system lines. export type RecordSource = 'claude_code' | 'lifecycle' @@ -301,7 +300,7 @@ export type LifecycleRecordType = | 'session_closed' | 'session_cancelled' -// One native session_record from GET /v1/sessions/{id}/steps. `payload` is the +// One native session_record from GET /v1/sessions/{id}/records. `payload` is the // harness's verbatim line — for claude_code, a Claude Code stream event // (assistant turn, tool call/result, result); for lifecycle, a small blob. The // CLI switches on `source` and only extracts display text. `feed_seq` is the @@ -321,7 +320,7 @@ export interface SessionRecord { [key: string]: unknown } -export interface ListSessionStepsResponse { +export interface ListSessionRecordsResponse { records: SessionRecord[] } diff --git a/src/ui/ConnectApp.tsx b/src/ui/ConnectApp.tsx index 5ff3b7e..477fcc7 100644 --- a/src/ui/ConnectApp.tsx +++ b/src/ui/ConnectApp.tsx @@ -21,8 +21,8 @@ import { hyperlink } from '../lib/urls' // composer that echoes what you send. Rendering shape lives in lib/events.ts // (pure); this component owns the data flow, the composer, and the colours. // -// Data flow: the committed transcript comes from the structured steps API -// (GET /v1/sessions/{id}/steps, whose `data` is the full Claude Code event) — +// Data flow: the committed transcript comes from the structured records API +// (GET /v1/sessions/{id}/records, whose payload is the full native event) — // grouped into tool calls / results — with the socket as a low-latency // "something changed" wake plus status source, backed by a slow poll. On top of // that, the socket also carries EPHEMERAL `delta` frames (partial assistant text @@ -170,7 +170,7 @@ export function ConnectApp(props: ConnectAppProps): React.ReactElement { } refreshing.current = true try { - const records = await api.getAgentSessionSteps(sessionId) + const records = await api.getAgentSessionRecords(sessionId) const ordered = [...records].sort((a, b) => a.feed_seq - b.feed_seq) const fresh = ordered.filter((st) => st.feed_seq > maxFeed.current) if (fresh.length) { diff --git a/test/search.test.ts b/test/search.test.ts index 46f1e0d..fd966f4 100644 --- a/test/search.test.ts +++ b/test/search.test.ts @@ -61,7 +61,7 @@ describe('searchSessions', () => { }) }) -describe('getAgentSessionSteps', () => { +describe('getAgentSessionRecords', () => { afterEach(() => vi.unstubAllGlobals()) it('unwraps the records array from the session-scoped path (encoded)', async () => { @@ -70,9 +70,9 @@ describe('getAgentSessionSteps', () => { ) vi.stubGlobal('fetch', fetchMock) - const out = await new ApiClient('http://api.test', 't').getAgentSessionSteps('session/1') + const out = await new ApiClient('http://api.test', 't').getAgentSessionRecords('session/1') expect(out.map((s) => s.id)).toEqual(['rec_1']) - expect(fetchMock.mock.calls[0][0]).toBe('http://api.test/v1/sessions/session%2F1/steps') + expect(fetchMock.mock.calls[0][0]).toBe('http://api.test/v1/sessions/session%2F1/records') }) })