Skip to content

Import local chat history into agent host session as editable turns#324858

Draft
vijayupadya wants to merge 6 commits into
mainfrom
vijayu/ah-import
Draft

Import local chat history into agent host session as editable turns#324858
vijayupadya wants to merge 6 commits into
mainfrom
vijayu/ah-import

Conversation

@vijayupadya

@vijayupadya vijayupadya commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

When continuing a local chat into Copilot CLI (Continue in… → Copilot CLI), the prior conversation is now imported into the new agent-host session as real, editable turns — edit, fork, and truncate all work.

This is done by translating the local IChatModel into the Copilot SDK's native events.jsonl format and resuming the session, so the CLI reconstitutes the history as genuine backend turns.

How it works

The conversation is translated once, seeded on disk, and resumed:

  Local chat            Translate            Seed + resume            Copilot CLI
  (IChatModel)   ---->  to Turn[]   ---->    events.jsonl     ---->   session with
                                             + protocol turns         editable history
  [browser]             [browser]            [node]                   [display]

High-level architecture

                          BROWSER (renderer)
  +--------------------------------------------------------------------+
  |                                                                    |
  |  chatContinueInAction.ts  ......  "Continue in…" trigger           |
  |        |          |                                                |
  |        |          +---------------------------+                    |
  |        v                                      v                    |
  |  importLocalConversation...           chatSessions.contribution    |
  |  (IChatModel -> Turn[])               (openChatSession)            |
  |                                              |                     |
  |                                        set(untitled-X)             |
  |                                              v                     |
  |                              +---------------------------------+   |
  |                              | agentHostImportConversationStore|   |
  |                              |  resource -> { turns, model }   |   |
  |                              +---------------------------------+   |
  |                                 ^          ^              ^        |
  |                                 |          |              |        |
  |   rename (graduation) ----------+          |              |        |
  |   agentHostSessionListController           |              |        |
  |   (moves stash untitled -> real)           |              |        |
  |                                            |              |        |
  |   take  <- tryRebind ----------------------+              |        |
  |   agentHostUntitledProvisionalService                     |        |
  |   (normal path: consume on surviving session)             |        |
  |                                                           |        |
  |   take  <- firstSend --------------------------------------+       |
  |   agentHostSessionHandler                                          |
  |   (fallback path: only if no graduation happened)                 |
  |        |                                                           |
  +--------|-----------------------------------------------------------+
           | createSession({ model, importConversation:{turns,model} })
           v
                             NODE (agent host)
  +--------------------------------------------------------------------+
  |  node/agentService.ts                                              |
  |    - assign fresh UUID turn ids                                    |
  |    - seedDefaultChatTurns(real, turns)  --> display                |
  |        |                                                           |
  |        v                                                           |
  |  copilotAgent.ts :: _importConversation                           |
  |        |                         |                                 |
  |        v                         v                                 |
  |  buildSessionEvents.ts     resumeSession  --> editability          |
  |  (Turn[] -> events)                                                |
  +--------|-----------------------------------------------------------+
           | write / read
           v
     <COPILOT_HOME>/session-state/<id>/events.jsonl

Trigger → graduation → seed (sequence)

The session goes through heavy lifecycle churn: an untitled-<uuid> provisional session is pre-warmed, then graduates to a real backend session. The import is keyed by resource and renamed across the graduation so it lands on the session that actually survives.

 User: Continue in… -> Copilot CLI
   |
   v
 chatContinueInAction
   |  importedTurnsFromChatModel(chatModel) -> Turn[]
   |  capture source model (selectedLanguageModel.metadata.id)
   v
 openChatSession({ importConversation: { turns, model } })
   |
   |  Store.set(untitled-X, { turns, model })
   v
 [pre-warm] getOrCreate(untitled-X)          <-- does NOT consume the stash
   |
   v
 SessionListController (graduation)
   |  Store.rename(untitled-X  ->  realY)     <-- stash follows the resource
   |  tryRebind(untitled-X, realY)
   v
 ProvisionalService.tryRebind
   |  Store.take(realY) -> { turns, model }   <-- consumed on the SURVIVING session
   |  createSession({ model, importConversation: { turns, model } })
   v
 node/agentService
   |  assign fresh UUID turn ids
   |  seedDefaultChatTurns(realY, turns)      --> DISPLAY (protocol turns)
   v
 copilotAgent._importConversation
   |  buildSessionEventLogFromTurns -> events.jsonl
   |  resumeSession                           --> EDITABILITY (SDK turns)
   v
 session renders imported history

Why resource-keyed rename

tryRebind creates a fresh backend session for the real resource and disposes the untitled one. If the import were consumed at the pre-warm (getOrCreate) it would be seeded into the discarded session. Keeping the stash resource-keyed and renaming it at graduation
(untitled → real) ensures tryRebind consumes it and seeds the surviving session.

  stash          getOrCreate         rename            tryRebind          firstSend
  untitled-X --> (no consume)  -->  untitled-X --> consume realY   -->   reuses realY
                                    -> realY       createSession+import   history shows [OK]

Fidelity

Preserved Notes
Markdown, reasoning, tool calls + output Full
Inline file/symbol references Rendered as markdown links (no gaps)
Sub-agent name / description Durable across reload via a subagent.started event
Cancelled turn state Durable across reload
Error turn state Partial — first-load display only; reverts to "complete" after reload (pre-existing gap, reverse mapper has no error path)
System-initiated turns (e.g. terminal notifications) Folded into the preceding turn, not shown as user turns

Copilot AI review requested due to automatic review settings July 8, 2026 01:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR enables “Continue in… → Copilot CLI” to migrate an existing local chat into a new agent-host (Copilot CLI) session as real, editable turns. It does so by translating the local IChatModel into agent-host Turn[], seeding a synthesized Copilot SDK events.jsonl, and wiring a short-lived store to bridge the untitled→real session-resource lifecycle.

Changes:

  • Add IChatModel → Turn[] translation for markdown, reasoning, inline references, and tool/sub-agent invocations.
  • Add a resource-keyed, consume-once import handoff store and thread it through session creation/rebind paths.
  • Implement Copilot SDK event-log synthesis (events.jsonl) + resume-based import, with unit/integration tests covering round-trip and editability.
Show a summary per file
File Description
src/vs/workbench/contrib/chat/test/browser/agentSessions/importLocalConversationToAgentSession.test.ts New unit tests for IChatModel → Turn[] translation behavior.
src/vs/workbench/contrib/chat/test/browser/agentSessions/agentHostImportConversationStore.test.ts New unit tests for consume-once import stash + rename handoff.
src/vs/workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.ts Stash imported conversation before opening to support eager backend creation.
src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts Register the import-conversation store as a singleton service.
src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/importLocalConversationToAgentSession.ts New translation layer from local chat model to agent-host turns/parts.
src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostUntitledProvisionalSessionService.ts Rebind path consumes imported conversation and seeds backend session creation.
src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionListController.ts Rename imported-conversation stash from untitled resource to real resource on graduation.
src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionHandler.ts Session creation path consumes imported conversation and passes it into backend create.
src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostImportConversationStore.ts New consume-once, rename-capable handoff store keyed by session resource.
src/vs/workbench/contrib/chat/browser/actions/chatContinueInAction.ts For local → Copilot CLI continuation, generate turns + carry model id instead of transcript attachment.
src/vs/platform/agentHost/test/node/protocol/copilotImportSession.integrationTest.ts New (opt-in) real SDK integration tests proving seeded history is editable (truncate).
src/vs/platform/agentHost/test/node/buildSessionEvents.test.ts New unit tests for Turn[] ↔ events.jsonl round-trip with mapSessionEvents.
src/vs/platform/agentHost/node/copilot/diskSessionFsProvider.ts New filesystem provider for session-state seeding in tests/tools.
src/vs/platform/agentHost/node/copilot/copilotSessionLauncher.ts Deduplicate BYOK model selections to avoid runtime config rejection.
src/vs/platform/agentHost/node/copilot/copilotAgent.ts Implement _importConversation: write events.jsonl, persist metadata, resume session.
src/vs/platform/agentHost/node/copilot/buildSessionEvents.ts New event-log synthesis from turns, including tool and sub-agent events.
src/vs/platform/agentHost/node/agentService.ts Assign fresh UUID turn ids for imports; seed protocol turns + set imported-session title.
src/vs/platform/agentHost/common/agentService.ts Extend create-session config with importConversation option and semantics.

Review details

  • Files reviewed: 18/18 changed files
  • Comments generated: 4
  • Review effort level: Low

Comment thread src/vs/platform/agentHost/node/copilot/buildSessionEvents.ts
Comment thread src/vs/platform/agentHost/node/copilot/diskSessionFsProvider.ts
Comment thread src/vs/workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.ts Outdated
# Conflicts:
#	src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostUntitledProvisionalSessionService.ts
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