diff --git a/skills/cli-conventions/SKILL.md b/skills/cli-conventions/SKILL.md index 063dfad..2ac341b 100644 --- a/skills/cli-conventions/SKILL.md +++ b/skills/cli-conventions/SKILL.md @@ -50,7 +50,7 @@ const file = alsoKnownAs( apiRoutes( alsoKnownAs(file.command('delete ').description('...'), 'rm'), - 'DELETE /files/{id}', + 'DELETE /v1/files/{id}', ) ``` @@ -98,7 +98,7 @@ Other rules: One line, imperative verb first, no trailing period. - **Say what the caller gets, not which endpoint answers.** `List your stored - files, newest first` — not `List files (GET /files)`. + files, newest first` — not `List files (GET /v1/files)`. - **Routes go in the long help**, last, via `apiRoutes(cmd, 'GET /...')`. When a command also has an `addHelpText('after', ...)` usage note, chain the note *inside* the `apiRoutes()` call so the route line still lands last. diff --git a/skills/ellipsis/SKILL.md b/skills/ellipsis/SKILL.md index 4adcddb..56b8c9b 100644 --- a/skills/ellipsis/SKILL.md +++ b/skills/ellipsis/SKILL.md @@ -70,7 +70,7 @@ fee. There are no seats. - **Catching bugs before merge**: turn code review on and every pull request is reviewed, or commit a pipeline file to scope and customize it. - **Delegation from scripts or CI**: `agent session start` or - `POST /sessions`. With `--watch` it streams into the log and exits nonzero + `POST /v1/sessions`. With `--watch` it streams into the log and exits nonzero unless the session completes, so it works as a gate. Things teams actually build: screenshot every pull request that touches the diff --git a/src/commands/analytics.ts b/src/commands/analytics.ts index a513c1a..b85cddc 100644 --- a/src/commands/analytics.ts +++ b/src/commands/analytics.ts @@ -78,7 +78,7 @@ export function registerAnalytics(program: Command): void { .description('Rank who reviews the most PRs, people and apps alike'), 'reviewers', ), - 'GET /analytics/metrics', + 'GET /v1/analytics/metrics', ) .option( '-r, --repo ', @@ -148,7 +148,7 @@ export function registerAnalytics(program: Command): void { .description('Show pull request volume and trend, split human vs bot'), 'prs', ), - 'GET /analytics/pull-requests', + 'GET /v1/analytics/pull-requests', ) .option( '--account-type ', @@ -211,7 +211,7 @@ export function registerAnalytics(program: Command): void { .description('Show review totals, verdicts, and the top reviewers'), 'reviews', ), - 'GET /analytics/reviews', + 'GET /v1/analytics/reviews', ) .option( '-r, --repo ', diff --git a/src/commands/config.ts b/src/commands/config.ts index 05b483c..11c3b35 100644 --- a/src/commands/config.ts +++ b/src/commands/config.ts @@ -31,7 +31,7 @@ export function registerConfig(program: Command): void { config.command('list').description('List your saved agent configs'), 'ls', ), - 'GET /agents/configs', + 'GET /v1/agents/configs', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { @@ -61,7 +61,7 @@ export function registerConfig(program: Command): void { config .command('get ') .description('Print one agent config as YAML, or as JSON with --json'), - 'GET /agents/configs/{id}', + 'GET /v1/agents/configs/{id}', ) .option('--json', 'output raw JSON') .action(async (configId: string, opts: { json?: boolean }) => { @@ -92,7 +92,7 @@ export function registerConfig(program: Command): void { config .command('create') .description('Create an agent, live immediately or by pull request with --repo'), - 'POST /agents/configs', + 'POST /v1/agents/configs', ) .option( '-r, --repo ', @@ -151,7 +151,7 @@ export function registerConfig(program: Command): void { .description("Replace an API-managed agent's definition from a file, live immediately"), 'update', ), - 'PUT /agents/configs/{id}', + 'PUT /v1/agents/configs/{id}', ) .requiredOption('-f, --file ', 'agent config file (.yaml/.yml or .json) to replace it with') .option('--json', 'output raw JSON') @@ -175,7 +175,7 @@ export function registerConfig(program: Command): void { .description('Delete an API-managed agent; it stops running and frees its name'), 'rm', ), - 'DELETE /agents/configs/{id}', + 'DELETE /v1/agents/configs/{id}', ) .option('--json', 'output raw JSON') .action(async (configId: string, opts: { json?: boolean }) => { @@ -194,7 +194,7 @@ export function registerConfig(program: Command): void { config .command('link ') .description('Move an agent into a repository by opening a pull request that adds its file'), - 'POST /agents/configs/{id}/link', + 'POST /v1/agents/configs/{id}/link', ) .requiredOption('-r, --repo ', 'repository in your account to move the agent into') .option( @@ -224,7 +224,7 @@ export function registerConfig(program: Command): void { config .command('unlink ') .description('Take an agent over from its file, so this API changes it instead'), - 'POST /agents/configs/{id}/unlink', + 'POST /v1/agents/configs/{id}/unlink', ) .option('--json', 'output raw JSON') .action(async (configId: string, opts: { json?: boolean }) => { @@ -254,7 +254,7 @@ export function registerConfig(program: Command): void { .description('Show or set which agent config runs when a session names none'), 'defaults', ), - 'GET /agents/defaults', + 'GET /v1/agents/defaults', ) .option('--json', 'output raw JSON') // Bare `agent config default`: the effective default for the repo you're @@ -290,7 +290,7 @@ export function registerConfig(program: Command): void { .description('List every default that is set, account rung and per-repo rungs'), 'ls', ), - 'GET /agents/defaults', + 'GET /v1/agents/defaults', ) .option('--json', 'output raw JSON') // The group also defines --json (for the bare view), and commander parses @@ -328,7 +328,7 @@ export function registerConfig(program: Command): void { defaults .command('set ') .description('Set the account default agent config, or a repo default with --repo'), - 'PUT /agents/defaults', + 'PUT /v1/agents/defaults', ) .option( '-r, --repo [repository]', @@ -362,7 +362,7 @@ export function registerConfig(program: Command): void { 'rm', 'delete', ), - 'DELETE /agents/defaults', + 'DELETE /v1/agents/defaults', ) .option( '-r, --repo [repository]', @@ -384,7 +384,7 @@ export function registerConfig(program: Command): void { .description( `Scaffold a starter agent config YAML locally (default: ${DEFAULT_CONFIG_PATH})`, ), - 'POST /agents/configs with --template', + 'POST /v1/agents/configs with --template', ) // No `-f` short: CLI-wide, `-f` means an input file (see `config create`). .option('--force', 'overwrite the file if it already exists') diff --git a/src/commands/connect.ts b/src/commands/connect.ts index 8297f8f..8976c52 100644 --- a/src/commands/connect.ts +++ b/src/commands/connect.ts @@ -60,7 +60,7 @@ export function registerConnect(session: Command): void { headless or from inside the session's own sandbox, where the id is optional. Pass --no-input to follow read-only from a script or agent (no TTY needed). -API: GET /sessions/{id}, GET /sessions/{id}/records, POST /sessions/{id}/messages, WS /sessions/{id}/stream`, +API: GET /v1/sessions/{id}, GET /v1/sessions/{id}/records, POST /v1/sessions/{id}/messages, WS /v1/sessions/{id}/stream`, ) .action(async (sessionId: string | undefined, opts: { records: boolean; input: boolean }) => { await runAction(async () => { diff --git a/src/commands/file.ts b/src/commands/file.ts index 499e590..19496ad 100644 --- a/src/commands/file.ts +++ b/src/commands/file.ts @@ -88,7 +88,7 @@ export function registerFile(program: Command): void { file .command('upload ') .description('Upload a PNG and print its org-gated URL, ready to paste into a PR comment'), - 'POST /files', + 'POST /v1/files', ) .option('--json', 'output raw JSON') .action(async (path: string, opts: { json?: boolean }) => { @@ -104,7 +104,7 @@ export function registerFile(program: Command): void { apiRoutes( alsoKnownAs(file.command('list').description('List your stored files, newest first'), 'ls'), - 'GET /files', + 'GET /v1/files', ) .option('--session ', 'only files uploaded by this agent session') .option('-l, --limit ', 'max results (server cap: 250)', parsePositiveInt) @@ -139,7 +139,7 @@ export function registerFile(program: Command): void { file .command('get ') .description("Print one file's metadata, or download its bytes with -o"), - 'GET /files/{id}', + 'GET /v1/files/{id}', 'presigned S3 GET', ) .option('-o, --output ', 'write the file contents to this path') @@ -165,7 +165,7 @@ export function registerFile(program: Command): void { file.command('delete ').description('Delete a file, so its link stops resolving'), 'rm', ), - 'DELETE /files/{id}', + 'DELETE /v1/files/{id}', ) .option('--json', 'output raw JSON') .action(async (fileId: string, opts: { json?: boolean }) => { diff --git a/src/commands/github.ts b/src/commands/github.ts index c5b3400..cd1b55b 100644 --- a/src/commands/github.ts +++ b/src/commands/github.ts @@ -18,7 +18,7 @@ export function registerGithub(program: Command): void { .description('List the repositories the GitHub installation can reach'), 'repo', ), - 'GET /integrations/github/repos', + 'GET /v1/integrations/github/repos', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { @@ -53,7 +53,7 @@ export function registerGithub(program: Command): void { ), 'member', ), - 'GET /integrations/github/members', + 'GET /v1/integrations/github/members', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { diff --git a/src/commands/help.ts b/src/commands/help.ts index 8707fd1..f15937e 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -21,7 +21,7 @@ export function registerHelp(program: Command): void { program .command('help') .description('Show help for a command, or ask the help agent with --interactive'), - 'POST /sessions with --interactive', + 'POST /v1/sessions with --interactive', ) .argument('[command...]', 'command to show help for (e.g. `session start`)') .option( diff --git a/src/commands/integrations.ts b/src/commands/integrations.ts index 2d9982c..d849424 100644 --- a/src/commands/integrations.ts +++ b/src/commands/integrations.ts @@ -12,7 +12,7 @@ export function registerIntegration(program: Command): void { .description('Show which integrations are connected, in one table'), 'integrations', ), - 'GET /integrations', + 'GET /v1/integrations', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { diff --git a/src/commands/linear.ts b/src/commands/linear.ts index 5ee3c8d..350d518 100644 --- a/src/commands/linear.ts +++ b/src/commands/linear.ts @@ -15,7 +15,7 @@ export function registerLinear(program: Command): void { .description('List the Linear teams, marking which have Ellipsis enabled'), 'team', ), - 'GET /integrations/linear/teams', + 'GET /v1/integrations/linear/teams', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { diff --git a/src/commands/me.ts b/src/commands/me.ts index e71fd05..52be8c2 100644 --- a/src/commands/me.ts +++ b/src/commands/me.ts @@ -18,7 +18,7 @@ export function renderMe(me: WhoAmI): void { export function registerMe(program: Command): void { apiRoutes( program.command('me').description('Show the identity behind the current credential'), - 'GET /me', + 'GET /v1/me', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { diff --git a/src/commands/model.ts b/src/commands/model.ts index e65b669..6b8e76e 100644 --- a/src/commands/model.ts +++ b/src/commands/model.ts @@ -18,7 +18,7 @@ export function registerModel(program: Command): void { ), 'ls', ), - 'GET /models', + 'GET /v1/models', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { diff --git a/src/commands/ping.ts b/src/commands/ping.ts index 7a03448..01a60d5 100644 --- a/src/commands/ping.ts +++ b/src/commands/ping.ts @@ -7,7 +7,7 @@ export function registerPing(program: Command): void { program .command('ping') .description('Check that the API is reachable and the credential is valid'), - 'GET /me', + 'GET /v1/me', ) .action(async () => { // There's no unauthenticated health route on the public API, so we probe diff --git a/src/commands/review.ts b/src/commands/review.ts index b64bfe2..e68069b 100644 --- a/src/commands/review.ts +++ b/src/commands/review.ts @@ -51,9 +51,9 @@ export function registerReview(program: Command): void { review .command('start ', { isDefault: true }) .description('Review a pull request by number'), - 'POST /reviews', - 'WS /sessions/{id}/stream', - 'GET /reviews/{id}', + 'POST /v1/reviews', + 'WS /v1/sessions/{id}/stream', + 'GET /v1/reviews/{id}', ) .option('--repo ', 'repository to review (default: this git remote)') .option('--full', 're-review the whole pull request, not just the new commits') @@ -111,7 +111,7 @@ export function registerReview(program: Command): void { review .command('get ') .description("Print a review's findings, scope, and whether it posted"), - 'GET /reviews/{id}', + 'GET /v1/reviews/{id}', ) .option('--json', 'output raw JSON') .action(async (reviewId: string, opts: { json?: boolean }) => { @@ -127,7 +127,7 @@ export function registerReview(program: Command): void { review.command('list').description("List a pull request's reviews, newest first"), 'ls', ), - 'GET /reviews', + 'GET /v1/reviews', ) .option('--repo ', 'only reviews of this repository') .option('--pr ', 'only reviews of this pull request', parsePositiveInt) diff --git a/src/commands/sentry.ts b/src/commands/sentry.ts index 90f0dd7..879ad84 100644 --- a/src/commands/sentry.ts +++ b/src/commands/sentry.ts @@ -14,7 +14,7 @@ export function registerSentry(program: Command): void { 'org', 'organizations', ), - 'GET /integrations/sentry/organizations', + 'GET /v1/integrations/sentry/organizations', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { diff --git a/src/commands/session.tsx b/src/commands/session.tsx index 72f2435..8cdf5e6 100644 --- a/src/commands/session.tsx +++ b/src/commands/session.tsx @@ -79,8 +79,8 @@ export function registerSession(program: Command): void { apiRoutes( session.command('start').description('Start a new agent session in the cloud'), - 'POST /sessions', - 'WS /sessions/{id}/stream with --watch or --connect', + 'POST /v1/sessions', + 'WS /v1/sessions/{id}/stream with --watch or --connect', ) .argument( '[prompt...]', @@ -318,8 +318,8 @@ export function registerSession(program: Command): void { '\nSources: react, manual, api, cli, mention, cron. ' + '--since/--until accept ISO 8601 or "today", "yesterday", "N days ago".', ), - 'GET /sessions', - 'GET /integrations/github/members to resolve --author', + 'GET /v1/sessions', + 'GET /v1/integrations/github/members to resolve --author', ) .option('-c, --config ', 'only sessions run by this saved agent config') .option( @@ -394,8 +394,8 @@ export function registerSession(program: Command): void { 'Sources: react, manual, api, cli, mention, cron. ' + '--since/--until accept ISO 8601 or "today", "yesterday", "N days ago".', ), - 'GET /sessions/search', - 'GET /integrations/github/members to resolve --author', + 'GET /v1/sessions/search', + 'GET /v1/integrations/github/members to resolve --author', ) .option( '-a, --author ', @@ -491,7 +491,7 @@ export function registerSession(program: Command): void { .description("Print a session's stored transcript, one line per record"), 'records', ), - 'GET /sessions/{id}/records', + 'GET /v1/sessions/{id}/records', ) .option('--json', 'output raw JSON (full record payloads)') .action(async (sessionId: string, opts: { json?: boolean }) => { @@ -520,7 +520,7 @@ export function registerSession(program: Command): void { 'log', 'logs', ), - 'GET /sessions/{id}/export', + 'GET /v1/sessions/{id}/export', ) .option('-o, --output ', 'write to a file instead of stdout') .option('--gzip', 'keep the concatenated .jsonl.gz bytes as-is (skip gunzip)') @@ -574,8 +574,8 @@ export function registerSession(program: Command): void { session .command('get ') .description("Show one session's status, cost, and dashboard link"), - 'GET /sessions/{id}', - 'WS /sessions/{id}/stream with --watch', + 'GET /v1/sessions/{id}', + 'WS /v1/sessions/{id}/stream with --watch', ) .option( '-w, --watch', @@ -622,8 +622,8 @@ export function registerSession(program: Command): void { session .command('replay ') .description("Re-run an existing session's trigger input as a fresh session"), - 'POST /sessions/{id}/replay', - 'WS /sessions/{id}/stream with --watch', + 'POST /v1/sessions/{id}/replay', + 'WS /v1/sessions/{id}/stream with --watch', ) .option( '-c, --config ', @@ -704,7 +704,7 @@ export function registerSession(program: Command): void { apiRoutes( session.command('stop ').description('Stop an in-flight session'), - 'POST /sessions/{id}/stop', + 'POST /v1/sessions/{id}/stop', ) .option('--json', 'output raw JSON') .action(async (sessionId: string, opts: { json?: boolean }) => { diff --git a/src/commands/slack.ts b/src/commands/slack.ts index 8239f9c..ca71c46 100644 --- a/src/commands/slack.ts +++ b/src/commands/slack.ts @@ -13,7 +13,7 @@ export function registerSlack(program: Command): void { slack.command('channels').description('List the channels in the Slack workspace'), 'channel', ), - 'GET /integrations/slack/channels', + 'GET /v1/integrations/slack/channels', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { @@ -46,7 +46,7 @@ export function registerSlack(program: Command): void { .description('List the workspace members, with linked GitHub identities'), 'member', ), - 'GET /integrations/slack/members', + 'GET /v1/integrations/slack/members', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { diff --git a/src/commands/template.ts b/src/commands/template.ts index 6ccad81..31476ef 100644 --- a/src/commands/template.ts +++ b/src/commands/template.ts @@ -16,7 +16,7 @@ export function registerTemplate(program: Command): void { .description('List the built-in agent templates and the slugs --template takes'), 'ls', ), - 'GET /agents/templates', + 'GET /v1/agents/templates', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { diff --git a/src/commands/usage.ts b/src/commands/usage.ts index 0eb6ca2..17f71ff 100644 --- a/src/commands/usage.ts +++ b/src/commands/usage.ts @@ -6,7 +6,7 @@ import { printJson, runAction, usd, usdFromMillicents } from '../lib/output' export function registerUsage(program: Command): void { apiRoutes( program.command('budget').description("Show this period's spend against the account budget"), - 'GET /budget', + 'GET /v1/budget', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { @@ -28,7 +28,7 @@ export function registerUsage(program: Command): void { program .command('usage') .description("Show this period's tokens and cost, broken down by model"), - 'GET /usage', + 'GET /v1/usage', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { diff --git a/src/commands/variable.ts b/src/commands/variable.ts index 848cccc..b4f10fc 100644 --- a/src/commands/variable.ts +++ b/src/commands/variable.ts @@ -20,7 +20,7 @@ export function registerVariable(program: Command): void { variable.command('list').description('List the variable names (never their values)'), 'ls', ), - 'GET /secrets', + 'GET /v1/secrets', ) .option('--json', 'output raw JSON') .action(async (opts: { json?: boolean }) => { @@ -34,7 +34,7 @@ export function registerVariable(program: Command): void { variable .command('set [assignments...]') .description('Create or update variables, e.g. `set A=1 B=2`'), - 'PUT /secrets', + 'PUT /v1/secrets', ) .option('-f, --from-file ', 'load variables from a .env or .json file') .option('--json', 'output raw JSON') @@ -52,7 +52,7 @@ export function registerVariable(program: Command): void { apiRoutes( alsoKnownAs(variable.command('delete ').description('Delete a variable'), 'rm'), - 'DELETE /secrets/{name}', + 'DELETE /v1/secrets/{name}', ) .option('--json', 'output raw JSON') .action(async (name: string, opts: { json?: boolean }) => { diff --git a/src/lib/stream.ts b/src/lib/stream.ts index 2b5dee0..fc68f49 100644 --- a/src/lib/stream.ts +++ b/src/lib/stream.ts @@ -21,11 +21,15 @@ export function resolveWsBase(apiBase?: string): string { return DEFAULT_WS_BASE } -// The bearer door's stream URL: /sessions/{id}/stream plus the SDK's +// The bearer door's stream URL: /v1/sessions/{id}/stream plus the SDK's // handshake query (`protocol`, `after_seq` when resuming) — the version comes // from the SDK, so a protocol bump ships with an SDK bump. +// +// The /v1 prefix is spelled out here because this is the one route the CLI still +// builds by hand: every REST path comes from the SDK, which carries its own +// prefix, so the base URL a host stores is the bare origin. export function buildStreamUrl(wsBase: string, sessionId: string, query: string): string { - return `${wsBase}/sessions/${encodeURIComponent(sessionId)}/stream?${query}` + return `${wsBase}/v1/sessions/${encodeURIComponent(sessionId)}/stream?${query}` } // An OpenSocket over the `ws` package with bearer auth — what every CLI diff --git a/src/ui/transcriptRows.ts b/src/ui/transcriptRows.ts index 5503892..aa831c5 100644 --- a/src/ui/transcriptRows.ts +++ b/src/ui/transcriptRows.ts @@ -146,12 +146,13 @@ export function itemRows( ): TranscriptRow[] { const indent = opts.indent ?? 0 // Nested lines are marked by their INDENT, so each keeps the glyph that says - // what it is: ● the call, ⎿ the result that came back. Only a collapsed fold - // (key grp:*, "Ran 2 tool calls") takes the branch glyph — as a notice it - // would otherwise wear ✦, the mark for the infrastructure speaking, which is - // not what a fold is. Keyed on the fold itself, not on `nested`: a - // turn-opening run is flat, and it is still a fold. - const gutter = item.key.startsWith('grp:') ? BRANCH_GLYPH : gutterFor(item) + // what it is: ● the call, ⎿ the result that came back. A collapsed fold (key + // grp:*, "Ran 2 tool calls") takes the branch glyph only when it hangs off a + // message — a turn-opening run branches off nothing, so it wears ● like any + // other line the agent owns. Either way not ✦: that mark is the + // infrastructure speaking, which a fold is not. + const isFold = item.key.startsWith('grp:') + const gutter = isFold ? (opts.nested ? BRANCH_GLYPH : '●') : gutterFor(item) const textPad = gutter === BRANCH_GLYPH ? BRANCH_TEXT_PAD : 0 const width = contentWidth(cols, { indent, textPad }) const shown = withRenderedMarkdown(item, width) diff --git a/test/connect-app.test.ts b/test/connect-app.test.ts index 6cef0a6..3655c08 100644 --- a/test/connect-app.test.ts +++ b/test/connect-app.test.ts @@ -788,12 +788,10 @@ describe('itemRows', () => { expect(withGutter[0].gutter?.text).toBe('▶') }) - it('marks a fold ⎿ whether or not it nests, since a flat one is still a fold', () => { + it('marks a fold ⎿ only when it branches off a message, ● when it opens a turn', () => { const foldItem: TranscriptItem = { key: 'grp:t1', kind: 'notice', text: 'Ran 1 shell command' } - for (const nested of [true, false]) { - const rows = itemRows(foldItem, 40, { nested }) - expect(rows[0].gutter?.text, `nested=${nested}`).toBe('⎿') - } + expect(itemRows(foldItem, 40, { nested: true })[0].gutter?.text).toBe('⎿') + expect(itemRows(foldItem, 40, { nested: false })[0].gutter?.text).toBe('●') // A real ✦ notice keeps its own mark either way. const notice: TranscriptItem = { key: 'n', kind: 'notice', text: 'Session asleep' } expect(itemRows(notice, 40, { nested: true })[0].gutter?.text).toBe('✦') diff --git a/test/stream.test.ts b/test/stream.test.ts index deab8a1..8b132dc 100644 --- a/test/stream.test.ts +++ b/test/stream.test.ts @@ -22,10 +22,10 @@ describe('resolveWsBase', () => { describe('buildStreamUrl', () => { it('builds the bearer-door URL with the SDK handshake query', () => { expect(buildStreamUrl('wss://h', 'session 1', 'protocol=2')).toBe( - 'wss://h/sessions/session%201/stream?protocol=2', + 'wss://h/v1/sessions/session%201/stream?protocol=2', ) expect(buildStreamUrl('wss://h', 's', 'protocol=2&after_seq=7')).toBe( - 'wss://h/sessions/s/stream?protocol=2&after_seq=7', + 'wss://h/v1/sessions/s/stream?protocol=2&after_seq=7', ) }) })