feat(cli): add a client-only migrate API to bring Claude Code and Codex sessions into Kilo - #13294
Conversation
…ume endpoint Expose the existing SessionResume transcript importer (Claude Code / OpenAI Codex JSONL, Anthropic message format) through a new CLI HTTP endpoint, POST /kilocode/session-resume, so every client can import an external transcript into an empty Kilo session without reimplementing the write path. The map + write logic is extracted into a shared SessionResumeImport module, which both the /resume-claude and /resume-codex slash commands and the new endpoint call into. Regenerate the SDK and add integration tests covering the raw-content import path and its error cases.
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Files Reviewed (2 files)
Previous Review Summaries (5 snapshots, latest commit 6cc21af)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 6cc21af)Status: 1 Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (9 files)
Fix these issues in Kilo Cloud Previous review (commit 8d05e8a)Status: No Issues Found | Recommendation: Merge Files Reviewed (11 files)
Previous review (commit 0bfc479)Status: No Issues Found | Recommendation: Merge Files Reviewed (5 files)
Previous review (commit 19ceae0)Status: 2 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (8 files)
Fix these issues in Kilo Cloud Previous review (commit b59ebd7)Status: 2 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (10 files)
Reviewed by grok-4.6 · Input: 94.5K · Output: 9.4K · Cached: 449.9K Review guidance: REVIEW.md from base branch |
…on-resume endpoint Add POST /kilocode/session-resume/discover, a read-only companion to the session-resume import endpoint. It scans the Claude Code / Codex JSONL transcript locations for a directory (reusing SessionResume.discover*) and previews each transcript (title, format, version, message count, model) so clients can list importable sessions before importing. Discovery is its own SessionResumeImport.discover handler; it never writes and does not fold into the import endpoint. Adds a pure SessionResume.preview helper, wires the new endpoint into the session-resume HttpApi group, and regenerates the SDK. No client code changes.
Address review feedback on the session-resume import endpoint. `fromContent` resolved the agent without checking that `agents.get` actually found one, so an unknown `agent` crashed on `agent.name` as a defect the HTTP handler could not map to 422. It also skipped a session existence check, so a bad `sessionID` died inside `messages` instead of reporting a user-actionable failure. Both now fail with `NamedError.Unknown` before anything is written. Also fix the CI failures on this branch: the discover handler mapped an error channel that is `never` (discovery reports problems through `dropped`), the discover result union needed an explicit annotation, the slash-command path leaked a `Session.Service` requirement into `SessionPrompt.command`, and both new routes had no HttpApi exerciser scenarios.
|
Pushed 0bfc479 addressing both review comments, plus the CI failures that were red on this branch:
GitHub did not create
|
…ion-import-endpoint # Conflicts: # packages/opencode/src/session/prompt.ts # packages/sdk/js/src/v2/gen/sdk.gen.ts
Rename the HTTP surface from `session-resume` to `migrate` so the route says what it is for: bringing existing work into Kilo from another coding agent. - `POST /kilocode/session-resume` -> `POST /kilocode/migrate/sessions` - `POST /kilocode/session-resume/discover` -> `POST /kilocode/migrate/sessions/discover` The old name collided conceptually with two unrelated things: the legacy `session-import` group (raw DB row import) and session *resume* as in resuming an interrupted turn. `migrate` leaves room for future "bring your existing X into Kilo" routes. Generated SDK is now `client.migrate.sessions(...)` and `client.migrate.discover(...)`. Discover deliberately keeps a flat identifier so the generator does not emit a `sessions` method plus a `sessions2` namespace getter for the same name. Internal module names (`SessionResume`, `SessionResumeImport`) are unchanged; they are shared with the `/resume-claude` and `/resume-codex` slash commands.
`POST /kilocode/migrate/sessions` no longer takes transcript bytes and a target session. It re-discovers Claude Code / Codex transcripts server-side and migrates each one into a session it creates itself, so clients only pass filters. This removes the asymmetry where `discover` returned a path on the CLI host that the import endpoint could not accept, which made the pair unusable for any client that does not share the backend's filesystem. Migration is now idempotent. Each created session records its source in `session.metadata.migrate` (format + source UUID + path + time), and a migration reads that back and skips sources that already landed, so calling the endpoint repeatedly is a no-op once everything has been migrated. This mirrors how the v5 session import decides to skip -- `skipped: true` per source with a `force` escape hatch -- but keyed off an explicit stored marker instead of a hashed row ID, because Kilo session IDs must stay time-sortable. `discover` now reports `sessionID` for sources it has already migrated so clients can mark them done in a picker. Failure handling is split by blast radius: unknown agent, unloadable model and unknown requested IDs fail the request, while one unreadable transcript is reported against its own entry so it cannot block the rest. A write that fails after the session was created removes that session, otherwise the next run would see a marked-but-empty session and skip the source forever.
…imit `migrated()` built the skip map from `Session.list()`, which pages to the 100 most recently updated sessions in the project. Once a project accumulated 100 sessions newer than a migrated one, its `metadata.migrate` marker fell out of that window, so `discover` stopped reporting the source as migrated and `migrate` imported it again as a duplicate. That defeated the whole point of the marker. Query the session table directly instead, scoped to the current project and narrowed to rows that carry metadata at all. Scoping matches what `Session.list()` applied for this call (`KiloSession.filters` with no directory reduces to `project_id`), so only the row cap is gone. Adds a regression test that pushes the migrated session past the 100-row page and asserts the source is still skipped; it fails against the previous implementation.
What
Adds a CLI server HTTP API for migrating sessions into Kilo from another coding agent, starting with Claude Code and OpenAI Codex JSONL transcripts.
This is API-only, for clients — not a user-facing feature by itself. It adds no CLI flag, no slash command, and no TUI screen. The routes exist so a client (VS Code extension, JetBrains plugin, or anything else driving
kilo servethrough@kilocode/sdk) can build its own "bring your Claude Code session into Kilo" UI. The only end-user-visible surfaces remain the pre-existing/resume-claudeand/resume-codexTUI slash commands, which this PR did not add — it just moved their write path into a module these routes also call.POST /kilocode/migrate/sessions/discoverPOST /kilocode/migrate/sessionsSDK surface is
client.migrate.discover(...)andclient.migrate.sessions(...).Why
Transcript import already existed, but only as the
/resume-claudeand/resume-codexTUI slash commands, with the mapping and write logic inside the slash-command handler. That made it unreachable for any other client: VS Code and JetBrains had no way to offer "bring your Claude Code session into Kilo" without reimplementing the parser and the message/part write path.The map + write step now lives in a shared module that both the slash commands and these endpoints call, so every client gets identical behavior from one implementation.
Client flow
Calling
migrate.sessionsagain is a no-op, so it is safe behind a "check again" button or on startup.Design notes
The server does the discovery, not the client. An earlier revision of this PR had
migrate.sessionsaccept raw transcriptcontent, which was wrong:discoverhands back a path on the CLI host's filesystem, and nothing bridged the two.GET /file/contentrefuses paths outside the project directory, and transcripts live under$HOME, so a client had to read the file itself — fine for the VS Code extension, broken for any remote, containerized, or split-mode backend. Re-discovering server-side also avoids round-tripping multi-MB JSONL to a server that just read the file to build the preview.Idempotency is stored, not inferred.
writemints fresh ascending IDs, so nothing about a migrated session says where its content came from and a second run would silently duplicate it. Each created session now recordsmetadata.migrate = { format, id, path, time }, and migration reads that back to skip sources that already landed. This follows the v5 session import's contract (skipped: trueper source,forceto override) but keys off an explicit marker rather than v5's hashed row ID, since Kilo session IDs must stay time-sortable. The marker lookup queries the session table directly rather than throughSession.list(), which pages to the 100 most recently updated sessions — that page limit would otherwise let old markers fall out of view and cause re-migration.Failures are scoped by blast radius. Unknown agent, unloadable model, and unknown requested IDs fail the whole request (422) because they apply to every source. A single unreadable transcript is reported on its own entry so it cannot block the rest of the batch. If a write fails after the session was created, that session is removed — otherwise the next run would see a marked-but-empty session and skip the source forever.
discoverkeeps a flat identifier (kilocode.migrate.discover). Nesting it undersessionsmade the SDK generator emit both asessionsmethod and asessions2namespace getter for the same name.Naming
The group is
migrate, notsession-resume, which collided conceptually with two existing things: the legacysession-importgroup (raw DB row import) and session resume meaning resuming an interrupted turn. Internal module names (SessionResume,SessionResumeImport) are unchanged since they're shared with the slash commands.Testing
test/kilocode/session-resume-integration.test.tscovers the migrate path against real fixtures through theResumeRootsseam: one session created per transcript with the marker persisted, second call skipping,forcere-migrating,discoverreporting already-migrated sources, id filtering, unknown-id rejection, per-entry failure leaving no stray session, the empty no-op, and the marker surviving past the 100-session page limit. The HttpApi exerciser covers route/auth/shape — it asserts the no-op contract, since it can't plant transcripts in the host's home directory.