Release: merge main into alpha/v2.0.0#350
Merged
heyitsaamir merged 11 commits intoalpha/v2.0.0from Apr 3, 2026
Merged
Conversation
Bumps [picomatch](https://github.com/micromatch/picomatch) from 4.0.3 to 4.0.4. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/micromatch/picomatch/releases">picomatch's releases</a>.</em></p> <blockquote> <h2>4.0.4</h2> <p>This is a security release fixing several security relevant issues.</p> <h2>What's Changed</h2> <ul> <li>Fix for <a href="https://github.com/micromatch/picomatch/security/advisories/GHSA-c2c7-rcm5-vvqj">CVE-2026-33671</a></li> <li>Fix for <a href="https://github.com/micromatch/picomatch/security/advisories/GHSA-3v7f-55p6-f55p">CVE-2026-33672</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/micromatch/picomatch/compare/4.0.3...4.0.4">https://github.com/micromatch/picomatch/compare/4.0.3...4.0.4</a></p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/micromatch/picomatch/commit/e5474fc1a4d7991870058170407dda8a42be5334"><code>e5474fc</code></a> Publish 4.0.4</li> <li><a href="https://github.com/micromatch/picomatch/commit/4516eb521f13a46b2fe1a1d2c9ef6b20ddc0e903"><code>4516eb5</code></a> Merge commit from fork</li> <li><a href="https://github.com/micromatch/picomatch/commit/5eceecd27543b8e056b9307d69e105ea03618a7d"><code>5eceecd</code></a> Merge commit from fork</li> <li><a href="https://github.com/micromatch/picomatch/commit/0db7dd70651ca7c8265601c0442a996ed32e3238"><code>0db7dd7</code></a> Run benchmark again against latest minimatch version (<a href="https://redirect.github.com/micromatch/picomatch/issues/161">#161</a>)</li> <li><a href="https://github.com/micromatch/picomatch/commit/95003777eb1c60dec09495a8231fa2ba4054d76a"><code>9500377</code></a> docs: clarify what brace expansion syntax is and isn't supported (<a href="https://redirect.github.com/micromatch/picomatch/issues/134">#134</a>)</li> <li><a href="https://github.com/micromatch/picomatch/commit/2661f23eca86c8b4a2b14815b9b2b3b74bd5a171"><code>2661f23</code></a> fix typo in globstars.js test name (<a href="https://redirect.github.com/micromatch/picomatch/issues/138">#138</a>)</li> <li><a href="https://github.com/micromatch/picomatch/commit/1798b07e9df59500b9cf567294d44d559032f4c7"><code>1798b07</code></a> docs: fix <code>makeRe</code> example (<a href="https://redirect.github.com/micromatch/picomatch/issues/143">#143</a>)</li> <li><a href="https://github.com/micromatch/picomatch/commit/9d76bc57a03b7f57cc4ca516c8071daf632bafd8"><code>9d76bc5</code></a> chore: undocument removed options (<a href="https://redirect.github.com/micromatch/picomatch/issues/146">#146</a>)</li> <li><a href="https://github.com/micromatch/picomatch/commit/e4d718bbfb47e4f030ab2612b5b04a9297fe272d"><code>e4d718b</code></a> Remove unused time-require (<a href="https://redirect.github.com/micromatch/picomatch/issues/160">#160</a>)</li> <li><a href="https://github.com/micromatch/picomatch/commit/38dffeb16221cc8eb8981524fb6895dd2aaaba76"><code>38dffeb</code></a> chore(deps): pin dependencies (<a href="https://redirect.github.com/micromatch/picomatch/issues/158">#158</a>)</li> <li>Additional commits viewable in <a href="https://github.com/micromatch/picomatch/compare/4.0.3...4.0.4">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/microsoft/teams.py/network/alerts). </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
… client gaps (#327) - went through the list of gaps and differences betw our clients vs. backend implementation (started with @singhk97 's list and then had Claude re-verify or re-test). - added in the missing endpoints from BF, verified that these exist in backend codebase, and manually had Claude verify them w/ my test bot **Issues Addressed** - `role` in TeamsChannelAccount comes in as `userRole` instead. updated to be `str` instead of fixed value, matching BE. - `aadObjectId` is either returned as `aadObjectId` or `objectId` (scenario dependent) - for `GET v3/teams/{id}/conversations`, needed to index into `response.json()["conversations"].` I manually tested this. - missing `tenant_id` in `TeamDetails` - No DELETE member route for` DELETE v3/conversations/{id}/members/{id}` - `GET v3/conversations/{id}/activities/{id}/members` returns `TeamsChannelAccount`, not `Account` - Extra parameters `topic` and `bot` in `CreateConversationParams` for `{service_url}/v3/conversations` - No `GET v3/conversations route` - removed `is_group` from `CreateConversationParams` - ignored on backend **New Endpoints** 1) **Paged Conversation Members** Added get_paged() to ConversationMemberClient. Supports optional page_size and continuation_token query params. For pageSize to work, the minimum is 50, by default its 200, and max is 500. 2) **Meeting Notifications** Added send_notification() to MeetingClient. Sends a targeted in-meeting notification to specific recipients on specified surfaces (e.g. meetingTabIcon, meetingStage). This is different from targeted messages, this was introduced in 2022. Requires this RSC permission `OnlineMeetingNotification.Send.Chat` and ECS flag enabled for the tenant/bot. --------- Co-authored-by: lilydu <lilydu+odspmdb@microsoft.com>
## Summary - The `devtools` package was accidentally left in the `ExcludePackageFolders` variable in the publish pipeline, preventing it from being published - Clears the exclusion list so all packages are included in both internal and public releases Fixes #329 Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
model results from comparing backend codebase to our SDK, mostly removing parameters that were not being set from service. verified by examining files and/or manually testing to double confirm **Changes**: - removed `EndOfConversation` activity - `relates_to` removed from all applicable activities (TraceActivity, ExecuteActionInvokeActivity, etc) - updated error to be `Any` instead of `Exception` b/c the deserialization would break - ConversationUpdate - `history_disclosed` removed - MessageBase - `speak`, `input_hint`, `importance`, `expiration` removed - Account - `role` removed, `type` added (applicable for mention entities) - Attachment - `id` removed --------- Co-authored-by: lilydu <lilydu+odspmdb@microsoft.com>
This PR attempts to improve the devex for dialog opening and card
submitting semantics.
For opening a dialog, before:
```diff
@app.on_message
async def handle_message(ctx: ActivityContext[MessageActivity]) -> None:
"""Handle message activities and show dialog launcher card."""
# Create the launcher adaptive card using Python objects to demonstrate SubmitActionData
# This tests that ms_teams correctly serializes to 'msteams'
card = AdaptiveCard(version="1.4")
card.body = [TextBlock(text="Select the examples you want to see!", size="Large", weight="Bolder")]
- # Use SubmitActionData with ms_teams to test serialization
- # SubmitActionData uses extra="allow" to accept custom fields
- simple_form_data = SubmitActionData.model_validate({"opendialogtype": "simple_form"})
- simple_form_data.ms_teams = TaskFetchSubmitActionData().model_dump()
- card.actions = [
- SubmitAction(title="Simple form test").with_data(simple_form_data)
- ]
+ # Use OpenDialogData to create dialog open actions with clean API
+ card.actions = [
+ SubmitAction(title="Simple form test").with_data(OpenDialogData("simple_form"))
+ ]
# Send the card as an attachment
message = MessageActivityInput(text="Enter this form").add_card(card)
await ctx.send(message)
- @app.on_dialog_open
+ @app.on_dialog_open("simple_form")
async def handle_dialog_open(ctx: ActivityContext[TaskFetchInvokeActivity]):
"""Handle dialog open events for all dialog types."""
- data: Optional[Any] = ctx.activity.value.data
- dialog_type = data.get("opendialogtype") if data else None
- if dialog_type == "simple_form":
dialog_card = AdaptiveCard.model_validate(
{
"type": "AdaptiveCard",
"version": "1.4",
"body": [
{"type": "TextBlock", "text": "This is a simple form", "size": "Large", "weight": "Bolder"},
{
"type": "Input.Text",
"id": "name",
"label": "Name",
"placeholder": "Enter your name",
"isRequired": True,
},
],
"actions": [
{"type": "Action.Submit", "title": "Submit", "data": {"submissiondialogtype": "simple_form"}}
],
}
)
return InvokeResponse(
body=TaskModuleResponse(
task=TaskModuleContinueResponse(
value=CardTaskModuleTaskInfo(
title="Simple Form Dialog",
card=card_attachment(AdaptiveCardAttachment(content=dialog_card)),
)
)
)
)
- # Default return for unknown dialog types
- return TaskModuleResponse(task=TaskModuleMessageResponse(value="Unknown dialog type"))
```
Notice how we have the `opendialogtype` that's kind of hidden away in
the code. Then, in your `on_dialog_open` you have to pull the action
out, and then handle it.
The two additions are:
1. OpenDialogData (which basically abstracts away the ugliness in favor
of a simple type).
2. And now `on_dialog_open` accepts a dialog identifier to know when to
trigger the handler.
Now, with submitting cards, we have a similar issue.
```diff
@app.on_dialog_open("simple_form")
async def handle_dialog_open(ctx: ActivityContext[TaskFetchInvokeActivity]):
"""Handle dialog open events for all dialog types."""
dialog_card = AdaptiveCard.model_validate(
{
"type": "AdaptiveCard",
"version": "1.4",
"body": [
{"type": "TextBlock", "text": "This is a simple form", "size": "Large", "weight": "Bolder"},
{
"type": "Input.Text",
"id": "name",
"label": "Name",
"placeholder": "Enter your name",
"isRequired": True,
},
],
- "actions": [
- {"type": "Action.Submit", "title": "Submit", "data": {"submissiondialogtype": "simple_form"}}
- ],
+ # Alternative: Use SubmitActionData for cleaner action-based routing
+ # SubmitAction(title="Submit").with_data(SubmitActionData("submit_simple_form"))
+ {"type": "Action.Submit", "title": "Submit", "data": {"action": "submit_simple_form"}}
}
)
return InvokeResponse(
body=TaskModuleResponse(
task=TaskModuleContinueResponse(
value=CardTaskModuleTaskInfo(
title="Simple Form Dialog",
card=card_attachment(AdaptiveCardAttachment(content=dialog_card)),
)
)
)
)
# Default return for unknown dialog types
return TaskModuleResponse(task=TaskModuleMessageResponse(value="Unknown dialog type"))
- @app.on_dialog_submit
+ @app.on_dialog_submit("submit_simple_form")
async def handle_dialog_submit(ctx: ActivityContext[TaskSubmitInvokeActivity]):
"""Handle dialog submit events for all dialog types."""
data: Optional[Any] = ctx.activity.value.data
- dialog_type = data.get("submissiondialogtype") if data else None
- if dialog_type == "simple_form":
name = data.get("name") if data else None
await ctx.send(f"Hi {name}, thanks for submitting the form!")
return TaskModuleResponse(task=TaskModuleMessageResponse(value="Form was submitted"))
- return TaskModuleResponse(task=TaskModuleMessageResponse(value="Unknown submission type"))
```
We reserve a keyword "action" to indicate what handler needs to be
called when the form is submitted. The keyword `action` was used to
align with [web
standards](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/form#action).
This also extends to general card actions too:
```diff
profile_card = AdaptiveCard(
schema="http://adaptivecards.io/schemas/adaptive-card.json",
body=[
TextBlock(text="User Profile", weight="Bolder", size="Large"),
TextInput(id="name").with_label("Name").with_value("John Doe"),
TextInput(id="email", label="Email", value="john@contoso.com"),
ToggleInput(title="Subscribe to newsletter").with_id("subscribe").with_value("false"),
ActionSet(
actions=[
ExecuteAction(title="Save")
+ .with_data(SubmitActionData("save_profile", {"entity_id": "12345"}))
.with_associated_inputs("auto"),
OpenUrlAction(url="https://adaptivecards.microsoft.com").with_title("Learn More"),
]
),
],
)
+ @app.on_card_execute_action("save_profile")
async def handle_save_profile(ctx: ActivityContext[AdaptiveCardInvokeActivity]) -> AdaptiveCardInvokeResponse:
"""Handle profile save."""
data = ctx.activity.value.action.data
print("Received save_profile action data:", data)
entity_id = data.get("entity_id")
name = data.get("name", "Unknown")
email = data.get("email", "No email")
subscribe = data.get("subscribe", "false")
age = data.get("age")
location = data.get("location", "Not specified")
response_text = f"Profile saved!\nName: {name}\nEmail: {email}\nSubscribed: {subscribe}"
if entity_id:
response_text += f"\nEntity ID: {entity_id}"
if age:
response_text += f"\nAge: {age}"
if location != "Not specified":
response_text += f"\nLocation: {location}"
await ctx.send(response_text)
return AdaptiveCardActionMessageResponse(
status_code=200,
type="application/vnd.microsoft.activity.message",
value="Action processed successfully",
)
```
#### PR Dependency Tree
* **PR #222** 👈
This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: lilydu <lilydu+odspmdb@microsoft.com>
Co-authored-by: Lily Du <lilyyduu@gmail.com>
- Default version 1.5 will be applied to payload (fixes #238) - Updated generated Adaptive Cards core file (core.py) from latest codegen output (03/31/2026) - Reapplied manual changes on top of generated code: - 3/30: these are now part of the generator and will no longer need to be manually applied - `choices_data` > `choices.data` alias in serialization/validation generators - `with_data` helper method on `SubmitActionData` - Removed legacy action wrapper classes (`IMBackAction`, `InvokeAction`, `MessageBackAction`, `SignInAction`, `TaskFetchAction`) and their tests, as generated replacements now exist directly in `core.py` - Other default values added: isEnabled, action mode defaults to "primary", action style "default", requires defaults to new HostCapabilities - `msteams` added as the single canonical field name (replacing `ms_teams`) on `AdaptiveCard`, `SubmitAction`, `Image`, and `SubmitActionData`; corresponding helpers renamed from `with_ms_teams()` to `with_msteams()` - `ExecuteAction.data` and `SubmitAction.data`: `Union[str, SubmitActionData, Dict[str, Any]]` — `Dict[str, Any]` retained for backwards compatibility - `with_data()` and `with_requires()` signatures broadened to accept `Dict[str, Any]` in addition to their typed counterparts --------- Co-authored-by: Corina Gum <>
…ort (#315) **Problem** When headers are provided to `create_streamable_http_transport` (e.g. an Authorization: Bearer token for authenticated MCP servers), the transport creates a bare `httpx.AsyncClient(headers=...) `which inherits httpx's default timeout of 5 seconds for all operations. This causes MCP tool calls to silently time out on any backend that takes longer than 5 seconds to respond — which is common for LLM-backed or database-backed MCP servers. The request hangs with no error, and the tool result is never returned to the agent. Notably, when no headers are passed, the code falls through to `streamable_http_client(url)` which internally calls `create_mcp_http_client()` — giving a 30s connect timeout and 300s read timeout. So unauthenticated servers work fine, but authenticated ones silently fail. **Fix** Replace the conditional `httpx.AsyncClient / streamable_http_client` branches with a single call using `create_mcp_http_client(headers=...),` which applies the correct MCP-recommended timeouts consistently regardless of whether headers are present. **Before** ``` if resolved_headers: async with httpx.AsyncClient(headers=resolved_headers) as http_client: # 5s timeout! async with streamable_http_client(url, http_client=http_client) as (r, w, _): yield r, w else: async with streamable_http_client(url) as (r, w, _): # 300s read timeout yield r, w ``` **After** ``` async with create_mcp_http_client(headers=resolved_headers or None) as http_client: async with streamable_http_client(url, http_client=http_client) as (r, w, _): yield r, w ``` **Impact** - No breaking changes — behaviour is identical for unauthenticated servers - Authenticated MCP servers (Bearer token, API key headers, etc.) now get the same 300s read timeout as unauthenticated ones - Simplifies the code by removing the if/else branch
## Summary Fixes #344. `PyJWKClient` was instantiated inside `validate_token()` on every call. Since `PyJWKClient` has built-in JWKS key caching (lifespan-based), creating a new instance each time defeated the cache — every token validation triggered a network fetch to the JWKS endpoint. **Fix:** Move `PyJWKClient` instantiation to `__init__()`, store as `self._jwks_client`, reuse in `validate_token()`. ### Changes - `token_validator.py`: one line added to `__init__()`, one line removed from `validate_token()` - `test_token_validator.py`: updated tests to set `validator._jwks_client` directly instead of patching the constructor (since it now runs at init time, not at validation time) ## Test plan ### Automated - [x] CI passes on Python 3.12/3.13/3.14 - [x] Local: 566 tests pass on Python 3.13 ### Manual - [x] Deploy echo bot, send multiple messages — observe that JWKS is fetched once (first request), not on every message - [x] Verify token validation still rejects invalid/expired tokens 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…314) ## Summary - Add `api://botid-{app_id}` to the valid audiences list in `TokenValidator.for_service()` and `TokenValidator.for_entra()`, matching the TypeScript SDK behavior (teams.ts#469) - Bot Framework tokens issued for bots registered with Entra ID use this audience format and were being rejected with a 401 - Add `application_id_uri` option to `AppOptions` — matches `webApplicationInfo.resource` in the Teams app manifest — for custom audience values in Entra token validation - Add parametrized test covering all three default audience formats and tests for `application_id_uri` ## Test plan - [x] All 24 token validator tests pass - [x] Verify with a bot registered via Entra ID that tokens with `aud=api://botid-{app_id}` are accepted - [x] Verify custom `application_id_uri` audiences are accepted 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary - Adds a GitHub Actions workflow that automatically analyzes new issues and sends results to a Teams channel - Uses GitHub Models API (GPT-4o) for quick triage → Adaptive Card summary - Uses GitHub Copilot CLI with full repo context for deep analysis → threaded markdown reply - Supports manual trigger via `workflow_dispatch` for any issue number ## How it works 1. **Triage** (GPT-4o, free): Categorizes the issue (bug/feature/question), assigns severity, identifies affected packages 2. **Analysis** (Copilot CLI): Reads the actual codebase to produce root cause, files to investigate, proposed approach, and complexity estimate 3. **Teams notification**: Sends triage as an Adaptive Card, then threads the Copilot analysis as a reply ## Files - `.github/workflows/issue-analysis.yml` — workflow definition - `.github/scripts/analyze_issue.py` — triage, card building, and Teams messaging ## Required secrets | Secret | Purpose | |---|---| | `TEAMS_CLIENT_ID` | Azure AD app client ID | | `TEAMS_CLIENT_SECRET` | Azure AD app client secret | | `TEAMS_TENANT_ID` | Azure AD tenant ID | | `TEAMS_CONVERSATION_ID` | Teams channel/chat ID | | `COPILOT_PAT` | PAT with Copilot Requests permission | `GITHUB_TOKEN` is automatic (provides GitHub Models API access). ## Test plan - Tested successfully in a fork --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Conversation client tests only verified non-null responses without
validating HTTP requests. Tests now capture and assert on request
methods, URLs, query parameters, and JSON payloads.
## Changes
**Added `request_capture` fixture**
- Captures HTTP requests during tests via mock transport
- Exposes `last_request` property for validation
- Returns standard `Client` instance compatible with existing APIs
**Enhanced test assertions**
- HTTP method validation (GET, POST, PUT, DELETE)
- Exact URL path validation including route parameters
- Query parameter validation (e.g., `continuationToken`)
- JSON payload validation for create/update operations (simplified to
check 1 key field per test)
- Authorization header validation for token-based authentication
**Added token authorization test**
- New `test_get_conversations_with_token` validates Bearer tokens are
properly sent in the Authorization header
## Example
Before:
```python
async def test_create_conversation(self, mock_http_client, mock_account):
response = await client.create(params)
assert response.id is not None
```
After:
```python
async def test_create_conversation(self, request_capture, mock_account):
response = await client.create(params)
# Validate request
request = request_capture._capture.last_request
assert request.method == "POST"
assert str(request.url) == "https://test.service.url/v3/conversations"
payload = json.loads(request.content)
assert payload["isGroup"] is True
```
Covers 10 methods across ConversationClient, ActivityOperations, and
MemberOperations, plus token authorization.
<!-- START COPILOT CODING AGENT TIPS -->
---
✨ Let Copilot coding agent [set things up for
you](https://github.com/microsoft/teams.py/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: heyitsaamir <48929123+heyitsaamir@users.noreply.github.com>
lilyydu
approved these changes
Apr 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Merge latest main commits into alpha/v2.0.0 for release.
Changes included
🤖 Generated with Claude Code