diff --git a/src/common/agent_prompt.rs b/src/common/agent_prompt.rs index 40465f3..21c7358 100644 --- a/src/common/agent_prompt.rs +++ b/src/common/agent_prompt.rs @@ -113,4 +113,16 @@ pub struct AgentPromptRequest { /// Optional attachments (blocks, files, etc.) referenced in the prompt. #[serde(default)] pub attachments: Vec, + + /// Idempotency key for a bootstrap request: set, together with a + /// `server_conversation_token` of `None`, when the request comes from an authenticated + /// server-side injection that must create or reuse exactly one conversation (REMOTE-2661). + /// `None` for an ordinary agent prompt request from a live viewer. + /// + /// The key exists so the injector can correlate a retry with its original attempt. + /// Deduplication belongs to the session-sharing service, which claims the key before + /// injecting, so a retry of a key it has already answered does not reach the sharer. The + /// sharer is not required to keep per-key state. + #[serde(default)] + pub idempotency_key: Option, } diff --git a/src/sharer.rs b/src/sharer.rs index 283cbff..344ea7f 100644 --- a/src/sharer.rs +++ b/src/sharer.rs @@ -17,9 +17,10 @@ use crate::common::{ CommandExecutionRequestId, ControlAction, ControlActionFailureReason, ControlActionRequestId, FeatureSupport, InputOperationId, InputReplicaId, InputUpdate, InputUpdateFailureReason, OrderedTerminalEvent, ParticipantId, ParticipantList, ParticipantPresenceUpdate, Role, - RoleRequestId, RoleRequestResponse, Selection, SelectionUpdate, SessionId, SessionSecret, - TelemetryContext, UniversalDeveloperInputContext, UniversalDeveloperInputContextUpdate, UserID, - WindowSize, WriteToPtyFailureReason, WriteToPtyRequestId, + RoleRequestId, RoleRequestResponse, Selection, SelectionUpdate, ServerConversationToken, + SessionId, SessionSecret, TelemetryContext, UniversalDeveloperInputContext, + UniversalDeveloperInputContextUpdate, UserID, WindowSize, WriteToPtyFailureReason, + WriteToPtyRequestId, }; use super::common::Scrollback; @@ -579,6 +580,26 @@ pub enum UpstreamMessage { id: AgentPromptRequestId, participant_id: ParticipantId, reason: AgentPromptFailureReason, + /// Echoes the originating request's idempotency key when it was a bootstrap request + /// (REMOTE-2661), so the server can persist the rejection under the same key a + /// caller's retry will look up. `None` for an ordinary rejection. + #[serde(default)] + idempotency_key: Option, + }, + + /// Reports the conversation the sharer created or reused for a bootstrap agent prompt + /// request — one that carried an `idempotency_key` and no `server_conversation_token` + /// (REMOTE-2661). Never sent for an ordinary agent prompt request, since the server + /// already knows that conversation's token by other means. The server must persist this + /// before the request may be treated as delivered: a lost acknowledgement here is what + /// would otherwise let a retry start a second, independent conversation. + AcknowledgeAgentPromptRequest { + id: AgentPromptRequestId, + participant_id: ParticipantId, + server_conversation_token: ServerConversationToken, + /// Echoes the originating request's idempotency key, which the server correlates + /// against its own pending wait for this bootstrap's result. + idempotency_key: String, }, /// The given control action request was denied for the specified `reason`.