Skip to content

Update Dispatcher ClientIO interactions #2183

Merged
GeorgeNgMsft merged 18 commits intomainfrom
dev/georgeng/clientio_improvements_1
Apr 10, 2026
Merged

Update Dispatcher ClientIO interactions #2183
GeorgeNgMsft merged 18 commits intomainfrom
dev/georgeng/clientio_improvements_1

Conversation

@GeorgeNgMsft
Copy link
Copy Markdown
Contributor

@GeorgeNgMsft GeorgeNgMsft commented Apr 10, 2026

This PR addresses comments in #2178

Changes :
Explicit client-initiated cancellation (cancelInteraction) instead of auto-cancelling interactions on client disconnect.

PR #2178 (already in main) implemented the async deferred-promise pattern with cancelByConnection — when a client disconnects, all its pending interactions are immediately cancelled. This branch replaces that with:

  • Interactions survive disconnect — they remain pending until timeout (10 min) or an explicit call, allowing a reconnecting client to still respond
  • New cancelInteraction(interactionId) method on Dispatcher — clients cancel explicitly (e.g. user dismisses UI), not implicitly on disconnect
  • Always-log invariant — logPendingInteraction is called unconditionally, even when no clients are connected at broadcast time, so the interaction appears in DisplayLog for session replay

GeorgeNgMsft and others added 15 commits April 8, 2026 13:33
Proposes converting the blocking askYesNo, proposeAction, and
popupQuestion callbacks to a non-blocking deferred-promise pattern
modeled on the existing requestChoice/respondToChoice mechanism.
Covers protocol changes, DisplayLog schema extensions, pending
interaction management, replay/reconnection strategy, and a
phased implementation plan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cks (Phases 1-8)

Convert askYesNo, proposeAction, and popupQuestion from blocking RPC
callbacks to a non-blocking deferred-promise pattern with correlation
IDs, enabling multi-client broadcast and resilient disconnect handling.

- Define PendingInteractionRequest/Response types and discriminated
  union for askYesNo, proposeAction, popupQuestion interaction types
- Add PendingInteractionManager with create/resolve/cancel lifecycle,
  per-connection cleanup, and timeout support
- Extend DisplayLog with logPendingInteraction and logInteractionResolved
  for interaction persistence and replay
- Add requestInteraction/interactionResolved fire-and-forget methods to
  ClientIO interface and wire through RPC client/server layers
- Add respondToInteraction to Dispatcher interface with RPC support
- Rewrite SharedDispatcher to use deferred promises instead of blocking
  callbacks, with broadcast to all clients and first-responder resolution
- Add requestInteraction/interactionResolved stubs to all ClientIO
  implementations (shell, CLI, browser extension, console, MCP, tests)
- Add @typeagent/dispatcher-types dependency to agent-server and protocol

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolved conflicts in favour of explicit cancelInteraction approach over
the auto-cancel-on-disconnect (cancelByConnection) approach from #2178.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

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

This PR updates the agent-server deferred ClientIO interaction flow to avoid implicitly cancelling pending interactions on client disconnect, introducing an explicit client-driven cancellation API and updating interaction queuing/logging behavior for replay.

Changes:

  • Adds Dispatcher.cancelInteraction(interactionId) to dispatcher types and RPC client/server plumbing.
  • Removes connection-scoped tracking/cancellation from PendingInteractionManager (drops connectionId and cancelByConnection).
  • Updates agent-server SharedDispatcher interaction handling and refreshes related tests/docs.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
ts/packages/dispatcher/types/src/dispatcher.ts Adds cancelInteraction() to the public Dispatcher interface.
ts/packages/dispatcher/rpc/src/dispatcherTypes.ts Extends dispatcher RPC invoke contract with cancelInteraction.
ts/packages/dispatcher/rpc/src/dispatcherServer.ts Wires cancelInteraction through the RPC server handler.
ts/packages/dispatcher/rpc/src/dispatcherClient.ts Adds RPC client method for cancelInteraction.
ts/packages/dispatcher/dispatcher/src/dispatcher.ts Adds base dispatcher stub that throws for cancelInteraction.
ts/packages/dispatcher/dispatcher/src/context/pendingInteractionManager.ts Removes connection-based ownership and cancelByConnection; simplifies create().
ts/packages/dispatcher/dispatcher/test/pendingInteractionManager.spec.ts Updates tests for new create() signature and removes cancelByConnection coverage.
ts/packages/agentServer/server/src/sharedDispatcher.ts Removes disconnect-driven interaction cancellation; adds explicit cancelInteraction behavior and adjusts queuing/logging behavior.
ts/packages/agentServer/docs/async-clientio-design.md Updates the design doc to describe explicit cancellation and always-log behavior.
Comments suppressed due to low confidence (1)

ts/packages/agentServer/docs/async-clientio-design.md:82

  • The doc says reconnecting clients can respond to pending interactions, but later states JoinSessionResult.pendingInteractions mirrors broadcast eligibility based on requestId.connectionId. Since connectionId is server-assigned per connection and changes on reconnect, those two statements conflict. Clarify the intended routing identity (stable client id vs connection id) and update either the implementation or this doc section accordingly.
Disconnecting a client does **not** automatically cancel pending interactions. Interactions remain pending until they time out or a client explicitly calls `cancelInteraction`. This allows a reconnecting client to respond to the same interaction within the timeout window.

Clients that want to cancel an interaction (e.g., on user dismissal) call `dispatcher.cancelInteraction(interactionId)`, which triggers:

1. `interactionCancelled` broadcast to all clients (so they can dismiss stale UI)
2. An `interaction-cancelled` entry in the `DisplayLog`

### DisplayLog Integration

Three entry types are logged to `DisplayLog`:

- `pending-interaction` — uses `interaction.timestamp` (the creation time of the request, not wall-clock at log time) to keep replay order consistent
- `interaction-resolved` — response is JSON-safe normalized before storage
- `interaction-cancelled`

### Session Join / Reconnection

`JoinSessionResult` includes `pendingInteractions: PendingInteractionRequest[]`, populated by `getPendingInteractions(connectionId, filter)`. This mirrors the `broadcast` eligibility rules exactly — a joining client only receives interactions it would have been sent during normal operation.


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

Comment thread ts/packages/agentServer/server/src/sharedDispatcher.ts
Comment thread ts/packages/agentServer/server/src/sharedDispatcher.ts Outdated
Comment thread ts/packages/agentServer/server/src/sharedDispatcher.ts
Comment thread ts/packages/agentServer/server/src/sharedDispatcher.ts Outdated
Comment thread ts/packages/dispatcher/rpc/src/dispatcherClient.ts
@GeorgeNgMsft GeorgeNgMsft marked this pull request as ready for review April 10, 2026 23:04
@GeorgeNgMsft GeorgeNgMsft added this pull request to the merge queue Apr 10, 2026
Merged via the queue into main with commit d36d31c Apr 10, 2026
21 checks passed
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