Skip to content

Add per-turn conversation and user state - #729

Merged
Mehak Bindra (MehakBindra) merged 32 commits into
mainfrom
mehakbindra-add-per-turn-state
Aug 26, 2026
Merged

Add per-turn conversation and user state#729
Mehak Bindra (MehakBindra) merged 32 commits into
mainfrom
mehakbindra-add-per-turn-state

Conversation

@MehakBindra

@MehakBindra Mehak Bindra (MehakBindra) commented Aug 13, 2026

Copy link
Copy Markdown
Member

Summary

  • add dirty-tracked, sealable per-turn conversation and user state scopes
  • expose state through AppOptions.state and ctx.state, loading before dispatch and saving in finally
  • keep keyed reads clean and require set() for persisted mutations
  • persist each scope as a raw-dictionary JSON string through an explicitly typed IStorage<string, string>
  • give state: true dedicated process-local string storage instead of casting or reusing legacy AppOptions.storage; production apps can provide state.storage
  • percent-encode conversation and user ID key segments to avoid delimiter collisions
  • treat conversation and user scopes as independent persistence units: each dirty scope is serialized and written separately, successful scopes become clean, and no cross-scope atomicity is promised
  • keep IStorage.set as a minimal key/value contract; expiration and other policies remain owned by the configured provider
  • treat malformed persisted values as absent and emit a warning without logging state contents
  • route state load/save failures through the turn error pipeline and return 500; when dispatch and save both fail, report dispatch first and save separately so neither masks the other
  • preserve state mutations on handler failures and always seal loaded state
  • emit the normalized InvokeResponse through the public activity.response event, matching its declared contract
  • deprecate direct AppOptions.storage, app.storage, and ctx.storage access in favor of state.storage or application-owned persistence
  • deprecate, but retain, IListStorage and ListLocalStorage; Python still exposes list storage, while the .NET state/storage design does not include this generic abstraction
  • add a TypeScript state example modeled on the .NET state bot

Ports the combined state foundation from microsoft/teams.py#553 and microsoft/teams.py#554, aligned with microsoft/teams.net while keeping TypeScript keyed state explicit and JSON-native. Standardized TTL and other per-write options are intentionally deferred until the SDK has a concrete cross-provider requirement.

Validation

  • focused state and activity-processing tests pass
  • changed-file lint passes
  • apps package build passes

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d
Copilot AI lite review requested due to automatic review settings August 13, 2026 22:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Introduces a new per-turn state subsystem for the apps package, enabling conversation-scoped and user-scoped mutable state that is loaded before dispatch, persisted at end-of-turn, and sealed after processing to prevent post-turn access.

Changes:

  • Added TurnState / TurnStateContainer abstractions with dirty-tracking and sealing semantics.
  • Implemented TurnStateLoader + createStateLoader to load/save JSON state envelopes with configurable key prefix and TTL.
  • Wired state into the activity processing lifecycle (AppOptions.state, ctx.state) and added focused unit + dispatch lifecycle tests.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/apps/src/state/turn-state.ts New turn-scoped state object with dirty tracking, snapshot iterators, and sealing behavior.
packages/apps/src/state/container.ts New container for per-turn conversation + user scopes, with delete + seal helpers.
packages/apps/src/state/loader.ts New loader for persisted state (keying, TTL checks, load/save/delete) and state-loader factory.
packages/apps/src/state/options.ts New StateOptions type for storage, key prefix, and TTL configuration.
packages/apps/src/state/index.ts Barrel exports for the new state subsystem.
packages/apps/src/index.ts Exposes the state subsystem as part of the public apps package API.
packages/apps/src/contexts/activity.ts Adds optional state to activity context options and propagates it in toInterface().
packages/apps/src/app.ts Adds AppOptions.state and constructs a TurnStateLoader for the processor.
packages/apps/src/app.process.ts Loads state before dispatch (when enabled) and saves+seals it in finally.
packages/apps/src/state/state.spec.ts Unit tests covering TurnState, TurnStateLoader, and createStateLoader.
packages/apps/src/app.process.spec.ts End-to-end lifecycle tests for load/expose/persist/seal, including failure cases.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/apps/src/state/state.spec.ts
Comment thread packages/apps/src/state/loader.ts Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d
@MehakBindra
Mehak Bindra (MehakBindra) marked this pull request as draft August 14, 2026 00:48
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d
@MehakBindra
Mehak Bindra (MehakBindra) marked this pull request as ready for review August 14, 2026 20:10
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d
Comment thread packages/apps/src/app.process.ts Outdated
Comment thread packages/apps/src/state/loader.ts Outdated
Comment thread packages/apps/src/state/loader.ts Outdated
Comment thread packages/apps/src/state/loader.ts Outdated
Comment thread packages/apps/src/state/loader.ts
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d
Comment thread packages/apps/src/app.process.ts
Comment thread packages/apps/src/app.process.ts
Comment thread packages/apps/src/state/loader.ts Outdated
Comment thread packages/apps/src/state/loader.ts
Comment thread packages/apps/src/app.ts Outdated
Comment thread examples/state/src/index.ts
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d

@lilyydu Lily Du (lilyydu) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM but looks liek there's some merge conflicts!

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e719380b-bd6a-4ae8-9650-6ffcee53fb7d
@MehakBindra
Mehak Bindra (MehakBindra) added this pull request to the merge queue Aug 26, 2026
Merged via the queue into main with commit 6bfccef Aug 26, 2026
9 checks passed
@MehakBindra
Mehak Bindra (MehakBindra) deleted the mehakbindra-add-per-turn-state branch August 26, 2026 23:14
pull Bot pushed a commit to Mattlk13/teams.ts that referenced this pull request Aug 28, 2026
## Summary

Adds a first-class, per-connection OAuth lifecycle to
`@microsoft/teams.apps`, modeled on `microsoft/teams.net` and using the
turn-state support from microsoft#729.

- adds `OAuthFlow` for silent token lookup, interactive sign-in,
sign-out, and completion/failure callbacks
- supports `AppOptions.oauthFlows`, `app.addOAuthFlow(...)`, and
`app.getOAuthFlow(...)`
- routes token exchange, verify-state, and sign-in failure invokes
across multiple connections
- persists pending attribution and exchange deduplication in turn state,
with bounded process-local fallback when state is disabled
- adds `ctx.getConnectionStatus()`, including direct token checks that
correct stale silent-SSO status results
- handles channel/group-chat OAuth cards consistently with Teams SSO
behavior
- replaces the Graph-only example with a Graph and GitHub OAuth example

## Public API

```ts
const app = new App({ oauthFlows: ['graph', 'github'] });
const graph = app.getOAuthFlow('graph');

graph
  .onSignInComplete(async (ctx, token) => { /* use token */ })
  .onSignInFailure(async (ctx, failure) => { /* handle failure */ });

const token = await graph.signIn(ctx);
await graph.signOut(ctx);
const statuses = await ctx.getConnectionStatus();
```

## Compatibility and behavior

- retains existing OAuth settings, context helpers, events, routes,
response shapes, explicit legacy connection overrides, and the
TypeScript sign-in activity override
- registered flows do not enable eager per-turn token lookup
- omitted state is enabled internally for registered flows; explicit
`state: false` keeps bounded process-local tracking
- expected token misses (`400`, `404`, `412`) trigger interactive
fallback; unexpected service or transport failures propagate
- verify-state continues only for connection misses; unexpected HTTP
failures preserve their status
- successful exchanges are marked processed before callbacks, so retries
remain deduplicated if a callback throws

## Breaking changes

- `ctx.signin()` previously treated every token lookup failure as a
missing token and sent an OAuth card. It now sends a card only for
expected token-miss responses (`400`, `404`, `412`). Unexpected service
and transport failures propagate and may produce a `500` for the turn
instead of silently switching to interactive sign-in.
- OAuth invoke spans are unified under `microsoft.teams.oauth`, with
`oauth.operation` distinguishing token exchange, verification, and
failure. Existing `oauth.result` values also move to lifecycle-specific
values. Dashboards and alerts using the previous span names or result
values must migrate; this must be called out in the release changelog.
The non-HTTP error value remains `exception`.

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0d88953d-cdee-4a28-ac2d-36a5fee72da5
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.

4 participants