| 1 | # Desktop prompt identity |
| 2 | |
| 3 | Desktop decision cards are owned by the controller that created them. Every |
| 4 | new prompt request carries the owning `hostId + sessionId`, session generation, |
| 5 | prompt id, turn id, runtime epoch and request kind. The kind identifies the |
| 6 | decision surface: `ask`, `approval`, `plan`, `recovery`, or `mcp`. The frontend |
| 7 | also derives an immutable request-instance key from this identity (plus approval |
| 8 | generation and permission revision where applicable) for component state and |
| 9 | Ask drafts. |
| 10 | |
| 11 | The frontend submits the complete target through `ResolvePromptForSession`. |
| 12 | Desktop validates the tab's host, session and generation while fixing the |
| 13 | controller under the App lock, releases that lock, and then resolves the exact |
| 14 | prompt. The controller checks runtime epoch, active turn, prompt owner and |
| 15 | pending state before persisting `PromptAnswered` and waking the original waiter. |
| 16 | A stale binding, turn or runtime is rejected without routing the answer to a |
| 17 | replacement controller. Failed persistence restores only that request to its |
| 18 | pending state so the user can retry. |
| 19 | |
| 20 | Prompt requests and lifecycle events expose `promptId`, `promptKind`, and |
| 21 | `turnId`. Desktop event envelopes carry the tab runtime epoch. Events without a |
| 22 | turn identity are marked `promptLegacy` and are accepted only by compatibility |
| 23 | paths. |
| 24 | |
| 25 | Older host methods such as `AnswerQuestionForTab`, `ApproveTab`, and |
| 26 | `ResolveRecoveryTab` remain available for older clients. New frontend code uses |
| 27 | `ResolvePromptForSession` and does not silently downgrade to an unfenced method. |
| 28 | When a stale response is received, the card is removed from the active decision |
| 29 | surface and one tab-scoped prompt replay is requested; only a new pending |
| 30 | identity can re-arm a card. |
| 31 | |
| 32 | Extension forms use the same rule with `SubmitExtensionFormExact`. The host |
| 33 | assigns a `formInstanceId` to every publication, pins the validated sidecar |
| 34 | client before releasing the hub lock, and applies completion only to that form |
| 35 | instance. Re-publishing the same plugin surface creates a different instance, |
| 36 | so an older completion cannot close or revive the replacement. |
| 37 | |
| 38 | Remote Serve advertises `interaction-target-v1` and |
| 39 | `extension-form-instance-v1` independently. Without the relevant capability, |
| 40 | the current client keeps the session readable but disables that card or form |
| 41 | and asks the user to upgrade; it never sends an unsafe legacy mutation. These |
| 42 | identities are transport and UI ownership fields and require no persisted |
| 43 | session migration. |
| 44 |