Skip to content

feat(events): preserve CLI context on session.resume#8

Open
Purisky wants to merge 1 commit into
copilot-community-sdk:mainfrom
WorkYesJobNo:feat/preserve-raw-event-data
Open

feat(events): preserve CLI context on session.resume#8
Purisky wants to merge 1 commit into
copilot-community-sdk:mainfrom
WorkYesJobNo:feat/preserve-raw-event-data

Conversation

@Purisky
Copy link
Copy Markdown

@Purisky Purisky commented Apr 24, 2026

Summary

Copilot CLI 1.0.35+ emits session.resume events with a context object containing cwd, gitRoot, branch, baseCommit, headCommit, repository, and hostType. The current SessionResumeData silently drops this payload, leaving downstream consumers (IDE integrations, session dashboards) unable to rebind the working directory or remote without reading the session store out-of-band.

This PR adds an optional context: Option<SessionResumeContext> field to SessionResumeData, making the extension additive and backward compatible — pre-1.0.35 payloads that omit the field continue to deserialize unchanged.

Changes

  • New SessionResumeContext struct mirroring the CLI wire shape (every nested field is Option<String> since hosts can omit any subset).
  • SessionResumeData.context: Option<SessionResumeContext> with #[serde(default, skip_serializing_if = "Option::is_none")].
  • Two regression tests in events::tests:
    • test_session_resume_preserves_cli_context — full payload round-trips, every CLI field recoverable.
    • test_session_resume_without_context_is_backward_compatible — omitted field is None, no error.

Verification

  • cargo test --lib -p copilot-sdk — 130 passed / 0 failed / 0 regressions.
  • Fixture shape derived from wire traces captured in a downstream POC (Copilot CLI 1.0.35, protocol v3 over --server --stdio).

Context

Discovered while building a Rust SDK consumer that relies on session.resume to rebind UI state to the resumed session's working directory. Without this patch the only workaround is to bypass the SDK's typed event stream and re-parse the raw JSON-RPC params.data, which defeats the purpose of a typed SDK.

Follow-up (separate PR candidate): parse_event_data currently replaces failed deserialization with Unknown(Value::Null), discarding the original JSON and hiding schema drift. Worth revisiting once this context extension lands.

Copilot CLI 1.0.35+ emits session.resume events with a `context`
object containing cwd, gitRoot, branch, baseCommit, headCommit,
repository, and hostType — all dropped silently by the previous
SessionResumeData definition.

Adds an optional `context: Option<SessionResumeContext>` field with
camelCase serde rename, making the extension additive and backward
compatible: pre-1.0.35 payloads without the field still deserialize
unchanged. The new SessionResumeContext struct uses Option<String>
for every nested field to absorb host-specific omissions.

Regression fixture derived from wire traces captured during a
downstream consumer POC (DevInSip docs/POC_COPILOT_SDK.md); two
tests cover the context-present and context-absent paths. Full
lib test suite passes (130/130).
Copilot AI review requested due to automatic review settings April 24, 2026 02:01
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds support for preserving Copilot CLI 1.0.35+ session.resume workspace context in the SDK’s typed event model so downstream consumers can recover cwd/git/host metadata directly from the event stream.

Changes:

  • Extend SessionResumeData with an optional context payload.
  • Introduce SessionResumeContext matching the CLI’s context wire shape (camelCase).
  • Add regression tests covering payloads with and without context.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/events.rs
Comment on lines +1484 to +1489
/// Regression fixture derived from the Copilot CLI 1.0.35 wire trace
/// captured during DevInSip POC-3 (see docs/POC_COPILOT_SDK.md in the
/// DevInSip repo). The CLI emits `session.resume` with an additional
/// `context` object that the pre-patch SDK silently dropped, leaving
/// downstream consumers (session binding, sidebar cwd) unable to
/// recover the working directory without bypassing the SDK.
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

The regression-test doc comment references an external repo/POC document (“DevInSip POC-3”, “docs/POC_COPILOT_SDK.md”). That makes the test less self-contained and can become confusing/stale for future maintainers. Consider rewriting this comment to describe the provenance generically (e.g., “captured from Copilot CLI 1.0.35 wire trace”) without external repo names/paths.

Suggested change
/// Regression fixture derived from the Copilot CLI 1.0.35 wire trace
/// captured during DevInSip POC-3 (see docs/POC_COPILOT_SDK.md in the
/// DevInSip repo). The CLI emits `session.resume` with an additional
/// `context` object that the pre-patch SDK silently dropped, leaving
/// downstream consumers (session binding, sidebar cwd) unable to
/// recover the working directory without bypassing the SDK.
/// Regression fixture derived from a Copilot CLI 1.0.35 wire trace.
/// The CLI emits `session.resume` with an additional `context` object
/// that the pre-patch SDK silently dropped, leaving downstream
/// consumers (session binding, sidebar cwd) unable to recover the
/// working directory without bypassing the SDK.

Copilot uses AI. Check for mistakes.
Comment thread src/events.rs
Comment on lines +126 to +129
/// the session store. Older CLI versions omit the field entirely,
/// hence `Option` + `#[serde(default)]` keeps the type backward
/// compatible.
#[serde(default, skip_serializing_if = "Option::is_none")]
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

context is an Option, so missing JSON already deserializes to None without #[serde(default)]. The doc comment also implies #[serde(default)] is needed for backward compatibility, which isn’t true for Option fields. Consider dropping default here (keeping skip_serializing_if) and adjusting the comment accordingly.

Suggested change
/// the session store. Older CLI versions omit the field entirely,
/// hence `Option` + `#[serde(default)]` keeps the type backward
/// compatible.
#[serde(default, skip_serializing_if = "Option::is_none")]
/// the session store. Older CLI versions may omit the field entirely,
/// and `Option` keeps deserialization backward compatible in that case.
#[serde(skip_serializing_if = "Option::is_none")]

Copilot uses AI. Check for mistakes.
Comment thread src/events.rs
Comment on lines +141 to +153
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cwd: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub git_root: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub branch: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub base_commit: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub head_commit: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub repository: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

In SessionResumeContext, each field is Option<...> and will already default to None when omitted; the per-field #[serde(default)] is redundant and inconsistent with other Option fields in this file (which typically only use skip_serializing_if). Consider removing default on these fields to reduce annotation noise.

Suggested change
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cwd: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub git_root: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub branch: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub base_commit: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub head_commit: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub repository: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[serde(skip_serializing_if = "Option::is_none")]
pub cwd: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub git_root: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub branch: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub base_commit: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub head_commit: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub repository: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]

Copilot uses AI. Check for mistakes.
Comment thread src/events.rs
Comment on lines +1521 to +1525
let ctx = resume
.context
.as_ref()
.expect("context must round-trip through SessionResumeData");
assert_eq!(ctx.cwd.as_deref(), Some("/Users/dev/project"));
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

This test’s comments/error message say the context must “round-trip”, but the test only verifies deserialization (from_json). Either extend the test to serialize back to JSON and re-parse, or adjust the wording to avoid implying serialization coverage.

Copilot uses AI. Check for mistakes.
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