Repository navigation
Add Teams meetings and transcripts tools (stage 2) - #11
Merged
Merged
Conversation
Stage 2 needs the Teams module's own tools to reach agents, and a SharePoint token whose audience is the user's own tenant, which no manifest built once for everyone can spell out. The registry now lists the read tools of active modules as <module>_<tool>, with an optional account the core adds and resolves. The core checks the arguments against the declared schema, the read permission before the call and again after it for the same account, drops items of conversations the agent may not read, and takes only a JSON object of at most 1 MiB back. Write tools, bad names, names taken twice or by a core tool, schemas the core cannot check and schemas declaring account are not listed; status says why. Every MCP session hears tools/list_changed when the list changes, from a scan or from the host stopping a module after crashes. A WAM scope may now be https://*.<domain>/.default, checked at load like a host pattern; it covers one lowercase DNS label on the module's host list and nothing else. Literal scopes compare as before. The scope check now runs before the tests' fake WAM too, and the approval window says what the SharePoint pattern hands over.
Review of fc631e3 (Codex, GLM): a module file that declared, say, `read` + `messages` made every read_messages call end as that module's unknown or disabled tool, even while the module waited for approval. A core tool's name now never routes to a module. Codex also found that the declaration checked was read before the account's module handle was taken: a replacement approved in between could run a tool that writes in the new version under the read permission. The call now goes ahead only if the module it reaches declares the very same tool.
Since main gained the window languages (#8), every Polish literal of the daemon needs its English beside it. The approval's text for a WAM scope pattern was Polish only; it now follows the window's language, with a test of both.
D1: local transcription is wanted, in its own spec. D2: no extra gate, open_mess is for the owner's own use, so transcripts reach agents under the account's read permission like chat messages. D3: a transcript fetch must never count as a view in the recording owner's analytics, so step 0 gets a gate that checks the counters on the user's own recording with a control, and the download path ships only if it passes. Reviewed by Codex and GLM in round 11 (both approve).
Agents can now see the Teams meetings of the user's calendar with the meeting chat each one has, which read_messages already reads. The module asks the middle tier's calendarView, as the official client does, with the authsvc token and the skype token it now keeps from the exchange; a skype token about to expire, or a 401, gets a new exchange. The live check of step 0 (docs, "Live results" 10) showed one answer for a dense month, times always with an offset, the enum values the module now maps, and no cancelled or private marker in the window. Only Teams meetings the user has not declined are returned, ids as opaque tokens, subject and organizer under untrusted; a continuation key or a field in another shape is an incompatible answer, never a silent first page. meetings.rs also holds the WebVTT parser and the paging of cues for the transcript tool that follows.
Review of 5802273 (GLM, two P3): list_meetings treated a `from` or `to` that is not text as left out and answered for the default window, and a NOTE block with `-->` in its text failed the whole transcript. A value of another type is now invalid, and NOTE, STYLE and REGION blocks are skipped before the timing line is looked for.
The owner approved reading the transcript of one recurring meeting of
their own organization, although the recording is a colleague's. The
probe made the requests of spec 4.2 steps 1-6 once, printing counts,
key names and character classes only.
What it settled: MCPS paginationInfo is {hasNextPage} and the calendar
event's iCalUID picks the occurrence; TranscriptV2 metadata times are
.NET ticks, not ISO times; the ODSP transcript id equals
metadata.transcriptId; streamContent?is=1 is WebVTT with a byte order
mark and voice tags, and the module's own parse_vtt and page handle
the real file. The allTime view counters stayed at 0 right after the
download, which is not yet a D3 result: they are read again a day
later, and the check on the owner's own recording still stands.
Review of 800ab63 (GLM P1) pointed out that the download ran on a colleague's recording before the D3 gate, while the doc called it step 0. It was the owner's explicit decision for that one meeting; the doc now says so, says what the owner accepted and that the D3 gate on the owner's own recording still has to pass before the tool ships. Also stop item 9 from saying that a download in the owner's organization is still to be captured (GLM P3).
The live download in the owner's organization (docs "Live results" 12) showed that TranscriptV2 times are .NET ticks, not ISO times, and that MCPS pages carry only hasNextPage with no continuation field. The spec now converts ticks with a format check and treats hasNextPage true as an incomplete list instead of paging blindly. It also records the owner's one-meeting exception to the D3 order, which the commit review of 800ab63 found missing from the spec; the D3 gate itself is unchanged. Review round 12 found that a VTT file over 100 000 cues was cut silently, so the last page would claim the transcript ended. The parser now returns an error instead, with a test. Round 13: both reviewers approve.
The owner wants an agent to summarize tomorrow's meeting from its transcript, before the D3 view-count check is settled. A view of a file lands in the analytics of whoever owns the place the file lies in, so the tool reads only transcripts whose location is under the user's own OneDrive site (from personalFileSite) and refuses anything else before asking for any SharePoint token. metadata.ownerId is not used: what it names is not confirmed (spec review R14). The path is spec 4.2 as checked live: MCPS contents with the MCPS token, TranscriptV2 times as .NET ticks, hasNextPage true never picked from, the origin only one of the organization's two, the ODSP list read before and after the WebVTT so a changing transcript is an error, and pages pinned to one version. SharePoint refusals are answers about the transcript, not a lost sign-in. The manifest asks for the MCPS scope and the SharePoint pattern, so the module needs approval again. Tests run the built module against a fake Teams with fixtures of the live shapes and synthetic values. Spec rounds R14-R16; both reviewers approve R16.
The owner decided on 2026-10-30 that teams_get_transcript should work for every meeting he attends, not only for recordings in his own OneDrive, and accepted that a download may count as one view in the recording owner's analytics (spec section 10, D3). Tomorrow's meeting is likely organized by someone else, so its transcript sits in the organizer's OneDrive. The refusal for transcripts outside the user's personal site is gone, and so is the path check behind it; the session now keeps only the tenant label it needs to derive the organization's two SharePoint origins. The other checks stay: only GETs, no read receipts, the owner tenant must be the user's, and no SharePoint token is asked for any other host. The manifest's SharePoint scope pattern already covers every OneDrive on the tenant's -my host, so the scopes and hosts are unchanged. The refusal test now proves that a colleague's OneDrive and a team site are read with a token for their own origin; the tests for other organizations and foreign hosts stay. Spec rounds R17 and R18.
c85deae dropped the lowercasing of the personalSiteUrl path when the path stopped being compared with transcript locations, so a site path written as /Personal/ would fail the shape check and break teams_get_transcript. SharePoint paths ignore letter case; the check does again, with a test. Found by GLM in the review of c85deae.
The first live teams_get_transcript run returned speaker names with raw references such as `ó`: the VTT parser knew only a few named entities. It now reuses the message text decoder, which handles named and numeric (decimal and hex) references and leaves invalid or out-of-range ones as text. That decoder gains `‎` and `‏`, which the VTT parser decoded before.
This was referenced Nov 3, 2026
SychPL
added a commit
that referenced
this pull request
Nov 5, 2026
#11 landed on main squashed, so this branch and main no longer share its commits and docs/protocols/teams.md conflicts. The tree is this branch's own tree unchanged; main brings nothing new beyond what the branch already had.
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.
Stacked on #10: this PR's base is
feat/module-read-tools, so its diff shows only its own commits. Merge #10 first; after its squash merge this branch is rebased ontomainand retargeted.Change
Stage 2 of the Teams module adds two read tools for agents. Both reach agents through the module tool registry of the base PR.
teams_list_meetings: the Teams meetings in the user's calendar, each with the meeting chat it has, whichread_messagesalready reads.calendarView, as the official client does. It uses the authsvc token and the skype token it now keeps from the exchange.untrusted.teams_get_transcript: the transcript of a meeting the user is in, as cues with times and speakers, paged by size.contents, then the ODSP transcript list, thenstreamContent(WebVTT).<t>-my.sharepoint.comand<t>.sharepoint.com, where<t>comes from the user'spersonalFileSite). Any other host or tenant is refused before any token is asked for.untrusted, with HTML character references decoded, named and numeric.Both tools use only GETs and no read-receipt operation, so nothing is marked as read. Reading someone else's recording may count as one view in its owner's analytics. The owner accepted this on 2026-10-30 (spec section 10, D3).
The manifest asks for the MCPS scope and the
https://*.sharepoint.com/.defaultpattern, so the module needs approval again after an update.docs/protocols/teams.mdgains the calendar and transcript facts from live step 0 checks ("Live results" 10 and 12). The live captions items are in the next PR. Because of that, "Live results" here goes from 10 to 12.Spec
.specs/2026-10-28-meetings-transcripts.md, rounds R11-R18 on this branch. Codex and GLM both approve R18. The owner's decisions D1-D3 are recorded in section 10, with the change of D3 on 2026-10-30.Tests
modules/teams/src/meetings.rs:ó,ł) and for invalid ones that stay as text,crates/open-mess-host/tests/teams/main.rsruns the built module against a fake Teams with redacted fixtures (modules/teams/tests/fixtures/). It covers listing, both pages of a transcript, a transcript changing during the read, and refusals for another organization and foreign hosts. A colleague's OneDrive and a team site are each read with a token for their own origin.cargo xtask ci(CARGO_BUILD_JOBS=3): fmt, clippy-D warnings, the wasm module build and tests all green, 593 passed, 0 failed.Commit reviews
Most commits are cherry-picks with the same patch as reviewed commits of the working branch. Each
.processedline points at the original.fe0ec2c(5802273): GLM gave two P3, a non-textfrom/toand a NOTE block with-->. Both are fixed inff2d715, with tests.06cfc14(800ab63, teams.md conflict resolved, captions items left out):605f5dbrecords it as such, and the owner's D3 decision of 2026-10-30 settles it in the spec.605f5db(be79fde, conflict resolved): Codex APPROVE, GLM APPROVE.19bb0ba(e9169cf): Codex P3 rejected. Times are converted from ticks and compared to the millisecond. The result writes them to the second, liketeams_list_meetings.1862055(c85deae): GLM noted that a/Personal/site path was rejected. Fixed with a test in0b4784d.6793b2a(a4d733a): GLM P3: there is no test for‎/‏. Not fixed, because the effect is cosmetic.Coexistence (platform modules only)
The live step 0 checks ran on the owner's corporate account while the official client was running. Only GETs were made, with no read-receipt calls, and nothing was marked as read in the official client. The one side effect is the possible analytics view above, which the owner accepted.
Checklist
unwrap/expect/panic!/ unchecked indexing in non-test code