feat(teams-pipeline): add plugin runtime and operator cli (salvage of #21410)#22007
Merged
Conversation
Third slice of the Microsoft Teams meeting pipeline stack, salvaged onto current main. Adds the standalone teams_pipeline plugin that consumes Graph change notifications from the webhook listener, resolves meeting artifacts (transcript first, recording + STT fallback later), persists job state in a durable store, and exposes an operator CLI for inspection, replay, subscription management, and validation. Design choices follow maintainer review feedback on PR #19815: - Standalone plugin rather than bolted-on core surface (plugins/teams_pipeline/, kind: standalone in plugin.yaml). - Zero new model tools. The agent drives the pipeline by invoking the operator CLI via the terminal tool, guided by the skill that ships with a follow-up PR. - Reuses the existing msgraph_webhook gateway platform for Graph ingress. Pipeline runtime is wired in via bind_gateway_runtime and gated on plugins.enabled so gateways that don't run the plugin boot cleanly. Additions: - plugins/teams_pipeline/: runtime (gateway wiring + config builder), pipeline core, durable SQLite store, subscription maintenance helpers, Graph artifact resolution, operator CLI (list, show, run/replay, fetch dry-run, subscriptions list, subscribe, renew-subscription, delete-subscription, maintain-subscriptions, token-health, validate). - hermes_cli/main.py: second-pass plugin CLI discovery so any standalone plugin registered via ctx.register_cli_command() outside the memory-plugin convention path gets its subcommand wired into argparse without touching core. - gateway/run.py: _teams_pipeline_plugin_enabled() config gate, _wire_teams_pipeline_runtime() binding after adapter setup, and the two runner attributes used by the runtime. Credit to @dlkakbs for the entire plugin implementation.
…ment gate Two salvage follow-ups on top of @dlkakbs's plugin runtime. 1. Install a drop-scheduler when the runtime fails to build. Previously when ``build_pipeline_runtime()`` raised (e.g. missing Graph env vars, subscription store path unwritable), ``bind_gateway_runtime`` logged a warning and returned False, leaving the msgraph_webhook adapter with no scheduler at all. Incoming Graph notifications would then fall back to the adapter's default ``handle_message`` path, which produces a raw JSON dump as a user-role message — not useful and fires every time Graph retries. Now a no-op drop-scheduler is installed instead, so: - Graph notifications ack cleanly (202) so Graph stops retrying. - The failure is surfaced once in the log with the error. - No user-role messages get manufactured from raw change payloads. The adapter is still bindable later once the runtime becomes available (e.g. after the operator runs ``hermes teams-pipeline validate`` and fixes the config), since the gateway's ``_teams_pipeline_runtime`` sentinel wasn't set to a non-None value. 2. Test wiring for ``_teams_pipeline_plugin_enabled()`` gate. The happy-path runner-wiring tests monkeypatched ``bind_gateway_runtime`` but not ``_load_gateway_config``. In the hermetic test environment the real config read ran, saw no enabled plugins, and short-circuited the bind call before the test could observe it — so the test expected ``calls == [runner]`` but got ``calls == []``. Adds a ``_load_gateway_config`` monkeypatch with ``plugins.enabled = ["teams_pipeline"]`` to the happy-path tests. The explicit-disabled test ``test_gateway_runner_skips_wiring_when_teams_pipeline_plugin_disabled`` already patches the config correctly. Also renames ``test_bind_gateway_runtime_leaves_scheduler_unchanged_on_failure`` to ``test_bind_gateway_runtime_installs_drop_scheduler_on_failure`` and updates the assertion — this test contradicted the drop-scheduler test in ``tests/plugins/test_teams_pipeline_plugin.py`` which expected the scheduler to be installed. The plugin-test name (``test_bind_gateway_runtime_drops_notifications_when_unavailable``) clearly describes the intended behavior; fixing the wiring-test assertion aligns both tests. Validation: - ``scripts/run_tests.sh tests/plugins/test_teams_pipeline_plugin.py tests/gateway/test_teams_pipeline_runtime_wiring.py tests/hermes_cli/test_teams_pipeline_plugin_cli.py`` — 25/25 passed.
Contributor
🔎 Lint report:
|
| Rule | Count |
|---|---|
invalid-argument-type |
4 |
unresolved-import |
4 |
not-subscriptable |
1 |
call-non-callable |
1 |
unresolved-attribute |
1 |
invalid-assignment |
1 |
First entries
tests/hermes_cli/test_teams_pipeline_plugin_cli.py:143: [not-subscriptable] not-subscriptable: Cannot subscript object of type `None` with no `__getitem__` method
plugins/teams_pipeline/models.py:72: [invalid-argument-type] invalid-argument-type: Argument is incorrect: Expected `datetime`, found `Any | None`
plugins/teams_pipeline/pipeline.py:17: [unresolved-import] unresolved-import: Cannot resolve imported module `httpx`
plugins/teams_pipeline/pipeline.py:576: [call-non-callable] call-non-callable: Object of type `object` is not callable
plugins/teams_pipeline/runtime.py:82: [unresolved-import] unresolved-import: Module `plugins.platforms.teams.adapter` has no member `TeamsSummaryWriter`
plugins/teams_pipeline/meetings.py:100: [invalid-argument-type] invalid-argument-type: Argument is incorrect: Expected `Literal["transcript", "recording", "call_record"]`, found `str`
plugins/teams_pipeline/models.py:162: [invalid-argument-type] invalid-argument-type: Argument is incorrect: Expected `Literal["transcript", "recording", "call_record"]`, found `Any | None`
tests/plugins/test_teams_pipeline_plugin.py:331: [invalid-argument-type] invalid-argument-type: Argument to `TeamsMeetingPipeline.__init__` is incorrect: Expected `NotionWriter | None`, found `FakeNotionWriter`
plugins/teams_pipeline/pipeline.py:388: [unresolved-attribute] unresolved-attribute: Attribute `get` is not defined on `None` in union `Any | None`
plugins/teams_pipeline/models.py:58: [invalid-assignment] invalid-assignment: Object of type `datetime | None` is not assignable to attribute `expiration_datetime` of type `datetime`
tests/hermes_cli/test_teams_pipeline_plugin_cli.py:9: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/plugins/test_teams_pipeline_plugin.py:10: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
✅ Fixed issues: none
Unchanged: 4070 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
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
Salvage of #21410 onto current
main, with a drop-scheduler fallback and test-wiring fixup on top.Third slice of the MS Teams meeting pipeline stack: the standalone
teams_pipelineplugin that turns Graph change notifications into stored meeting jobs, exposes an operator CLI (hermes teams-pipeline ...), and wires itself into themsgraph_webhookadapter from #21969. No new model tools — the agent drives the pipeline via its CLI through the terminal tool.Credit to @dlkakbs — the entire plugin implementation is hers, preserved via rebase-merge.
Changes
Plugin (all new):
plugins/teams_pipeline/— runtime (gateway wiring + config builder), pipeline core, durable store, subscription maintenance helpers, Graph artifact resolution, operator CLIlist,show,run/replay,fetchdry-run,subscriptions,subscribe,renew-subscription,delete-subscription,maintain-subscriptions,token-health,validateCore wiring (minimal, gated):
hermes_cli/main.py— second-pass plugin CLI discovery so any plugin registered viactx.register_cli_command()outside the memory-plugin path gets its subcommand wired into argparse automaticallygateway/run.py—_teams_pipeline_plugin_enabled()config gate (readsplugins.enabled),_wire_teams_pipeline_runtime()method called after adapter setup, two runner attributes for runtime state. Gateways without the plugin enabled boot unchanged.Follow-ups added during salvage:
build_pipeline_runtime()raises (missing Graph env, bad store path),bind_gateway_runtimenow installs a no-op scheduler instead of leaving the adapter unbound. Previously Graph notifications would fall through to the adapter's default handler and manufacture raw-JSON user messages on every Graph retry. Drop-scheduler means notifications ack cleanly (202), failure is logged once, no synthetic messages get generated.bind_gateway_runtimebut not_load_gateway_config, so the real config read ran in the hermetic test env, saw no enabled plugins, and short-circuited the bind. Added config monkeypatch. Also renamed and realigned one test assertion that contradicted the drop-scheduler test — both tests now consistently verify that failure installs a drop-scheduler.Stale-base safeguard during salvage:
The original branch was 142 commits behind main and carried spurious reverts of unrelated recent main commits on
gateway/run.py(_resolve_home_env_varplugin-registry lookup, informative bootstrap error reporting, fallback-provider model logging). Those reverts were discarded;gateway/run.pyon this PR only carries the genuine newteams_pipelineadditions on top of currentmain. None of @dlkakbs's intended work was lost.Validation
scripts/run_tests.sh tests/plugins/test_teams_pipeline_plugin.py tests/gateway/test_teams_pipeline_runtime_wiring.py tests/hermes_cli/test_teams_pipeline_plugin_cli.py→ 25/25 passed.scripts/run_tests.sh tests/gateway/ -q→ 5056 passed, 1 pre-existing failure intest_discord_free_response(reproduces on cleanorigin/main, unrelated to this PR).origin/main, zero new failures introduced by this PR.Closes #21410.
Next in the stack: #21411 (outbound delivery via existing Teams adapter), #21412 (full setup docs + operator runbook + SKILL.md).