Skip to content

Release: merge main into alpha/v2.0.0#350

Merged
heyitsaamir merged 11 commits intoalpha/v2.0.0from
main
Apr 3, 2026
Merged

Release: merge main into alpha/v2.0.0#350
heyitsaamir merged 11 commits intoalpha/v2.0.0from
main

Conversation

@heyitsaamir
Copy link
Copy Markdown
Collaborator

Summary

Merge latest main commits into alpha/v2.0.0 for release.

Changes included

🤖 Generated with Claude Code

dependabot bot and others added 11 commits March 26, 2026 16:48
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 />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=picomatch&package-manager=npm_and_yarn&previous-version=4.0.3&new-version=4.0.4)](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>
@heyitsaamir heyitsaamir merged commit 698ff6d into alpha/v2.0.0 Apr 3, 2026
10 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.

6 participants