Add source_task_id sidecar; keep User as a strict unit variant (#15)

## What Adds a `source_task_id: Option<String>` sidecar field to `sharer::InitPayload` and `viewer::DownstreamMessage::JoinedSuccessfully`. `SessionSourceType::User` stays a strict unit variant (no struct fields), matching `main`. `AmbientAgent { task_id }` is unchanged. Both new fields are gated on `#[serde(default)]` so older clients and viewers ignore them silently. The sidecar carries the conversation's server-side `ai_tasks` row id so downstream orchestration discovery can enumerate descendants without re-shaping the variant. ## Why Required by the warp client to support session sharing of *manually-shared* local orchestrator conversations. ## Related - session-sharing-server vendored-protocol mirror: https://github.com/warpdotdev/session-sharing-server/pull/449 - warp client: https://github.com/warpdotdev/warp/pull/11465 Co-Authored-By: Oz <oz-agent@warp.dev> --------- Co-authored-by: Oz <oz-agent@warp.dev>

Matthew Albright committed May 21, 2026 at 16:37 UTC 5a0ad6135809feee9da2e9efae8bd6b54b89172e
2 files changed +23 -6
src/sharer.rs
+16 -6
@@ -154,16 +154,15 @@ pub enum SessionSourceType {
154 },
155 }
156
157 -/// Internal helper that mirrors all wire representations of SessionSourceType
158 -/// (both legacy and new) so we don't recursively call SessionSourceType's
159 -/// custom Deserialize impl.
157 +/// Mirrors the legacy unit-variant form and the new struct-variant form so
158 +/// the custom `Deserialize` impl can accept both shapes.
159 #[derive(Deserialize)]
160 #[serde(untagged)]
161 enum SessionSourceTypeWire {
163 - /// Legacy representation: "User" or "AmbientAgent".
162 + /// Legacy representation: bare `"User"` or `"AmbientAgent"`.
163 Legacy(LegacySessionSourceType),
165 - /// New representation: externally tagged AmbientAgent with fields, e.g.
166 - /// { "AmbientAgent": { "task_id": "..." } }.
164 + /// New representation: externally tagged `AmbientAgent` with fields, e.g.
165 + /// `{ "AmbientAgent": { "task_id": "..." } }`.
166 New {
167 #[serde(rename = "AmbientAgent")]
168 ambient_agent: AmbientAgentFields,
@@ -263,6 +262,13 @@ pub struct InitPayload {
262 #[serde(default)]
263 pub source_type: SessionSourceType,
264
265 + /// Optional orchestrator `task_id` carried alongside `source_type`.
266 + /// Set when the sharer wants downstream orchestration discovery to find
267 + /// this share's children regardless of variant kind. Sidecar so the
268 + /// `User` variant can stay a unit and old viewers ignore it.
269 + #[serde(default)]
270 + pub source_task_id: Option<String>,
271 +
272 /// Client feature support declaration.
273 #[serde(default)]
274 pub feature_support: FeatureSupport,
@@ -474,6 +480,10 @@ impl DownstreamMessage {
480 }
481
482 /// The possible messages sent from client (sharer) to server.
483 +// `Initialize(InitPayload)` is much larger than the other variants because
484 +// `InitPayload` carries scrollback and feature-support data. Boxing it would
485 +// be wire-compatible but churn every call site; suppress the lint instead.
486 +#[allow(clippy::large_enum_variant)]
487 #[derive(Debug, Deserialize, Serialize)]
488 pub enum UpstreamMessage {
489 /// The client sends this message to start a shared session.
src/viewer.rs
+7
@@ -142,6 +142,13 @@ pub enum DownstreamMessage {
142 /// The detailed source type for this shared session.
143 #[serde(default)]
144 detailed_source_type: SessionSourceType,
145 +
146 + /// Optional orchestrator `task_id` carried alongside the source
147 + /// type, mirroring `sharer::InitPayload::source_task_id`. Lets
148 + /// viewers find this share's orchestrator task without keying
149 + /// off the source-type variant kind.
150 + #[serde(default)]
151 + source_task_id: Option<String>,
152 },
153
154 /// The server sends this message when the session was successfully rejoined.