Skip to content

feat(teams-pipeline): add plugin runtime and operator cli (salvage of #21410)#22007

Merged
teknium1 merged 2 commits into
mainfrom
hermes/hermes-f0bc99f0
May 8, 2026
Merged

feat(teams-pipeline): add plugin runtime and operator cli (salvage of #21410)#22007
teknium1 merged 2 commits into
mainfrom
hermes/hermes-f0bc99f0

Conversation

@teknium1

@teknium1 teknium1 commented May 8, 2026

Copy link
Copy Markdown
Contributor

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_pipeline plugin that turns Graph change notifications into stored meeting jobs, exposes an operator CLI (hermes teams-pipeline ...), and wires itself into the msgraph_webhook adapter 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 CLI
  • CLI commands: list, show, run/replay, fetch dry-run, subscriptions, subscribe, renew-subscription, delete-subscription, maintain-subscriptions, token-health, validate

Core wiring (minimal, gated):

  • hermes_cli/main.py — second-pass plugin CLI discovery so any plugin registered via ctx.register_cli_command() outside the memory-plugin path gets its subcommand wired into argparse automatically
  • gateway/run.py_teams_pipeline_plugin_enabled() config gate (reads plugins.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:

  • Drop-scheduler fallback — when build_pipeline_runtime() raises (missing Graph env, bad store path), bind_gateway_runtime now 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.
  • Test-wiring fixup — the happy-path wiring tests monkeypatched bind_gateway_runtime but 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_var plugin-registry lookup, informative bootstrap error reporting, fallback-provider model logging). Those reverts were discarded; gateway/run.py on this PR only carries the genuine new teams_pipeline additions on top of current main. 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.py25/25 passed.
  • scripts/run_tests.sh tests/gateway/ -q5056 passed, 1 pre-existing failure in test_discord_free_response (reproduces on clean origin/main, unrelated to this PR).
  • Full gateway + hermes_cli cross-directory run: 9048 passed, 13 pre-existing cross-test-pollution failures identical to clean 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).

dlkakbs and others added 2 commits May 8, 2026 10:48
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.
@github-actions

github-actions Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-f0bc99f0 vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 7781 on HEAD, 7766 on base (🆕 +15)

🆕 New issues (12):

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.

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.

2 participants