Skip to content

Commit 07bbd93

Browse files
dlkakbsteknium1
authored andcommitted
feat(teams-pipeline): add plugin runtime and operator cli
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.
1 parent ea86714 commit 07bbd93

14 files changed

Lines changed: 3332 additions & 1 deletion

File tree

gateway/run.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -847,6 +847,15 @@ def _platform_config_key(platform: "Platform") -> str:
847847
return "cli" if platform == Platform.LOCAL else platform.value
848848

849849

850+
def _teams_pipeline_plugin_enabled() -> bool:
851+
"""Return True when the standalone Teams pipeline plugin is enabled."""
852+
config = _load_gateway_config()
853+
enabled = cfg_get(config, "plugins", "enabled", default=[])
854+
if not isinstance(enabled, list):
855+
return False
856+
return "teams_pipeline" in enabled or "teams-pipeline" in enabled
857+
858+
850859
def _load_gateway_config() -> dict:
851860
"""Load and parse ~/.hermes/config.yaml, returning {} on any error.
852861

@@ -1154,6 +1163,9 @@ def __init__(self, config: Optional[GatewayConfig] = None):
11541163
# Per-session reasoning effort overrides from /reasoning.
11551164
# Key: session_key, Value: parsed reasoning config dict.
11561165
self._session_reasoning_overrides: Dict[str, Dict[str, Any]] = {}
1166+
# Teams meeting pipeline runtime (bound later when msgraph_webhook adapter exists).
1167+
self._teams_pipeline_runtime = None
1168+
self._teams_pipeline_runtime_error: Optional[str] = None
11571169
# Track pending exec approvals per session
11581170
# Key: session_key, Value: {"command": str, "pattern_key": str, ...}
11591171
self._pending_approvals: Dict[str, Dict[str, Any]] = {}
@@ -1251,6 +1263,37 @@ def __init__(self, config: Optional[GatewayConfig] = None):
12511263
self._background_tasks: set = set()
12521264

12531265

1266+
def _wire_teams_pipeline_runtime(self) -> None:
1267+
"""Bind the Teams meeting pipeline runtime to Graph webhook ingress.
1268+
1269+
No-op when the msgraph_webhook adapter isn't running or the
1270+
teams_pipeline plugin isn't enabled — lets the gateway start cleanly
1271+
whether or not the user has opted into the pipeline.
1272+
"""
1273+
if Platform.MSGRAPH_WEBHOOK not in self.adapters:
1274+
return
1275+
if not _teams_pipeline_plugin_enabled():
1276+
logger.debug("Teams pipeline plugin is disabled; skipping runtime wiring")
1277+
return
1278+
try:
1279+
from plugins.teams_pipeline.runtime import bind_gateway_runtime
1280+
except Exception as exc:
1281+
logger.warning("Teams pipeline runtime import failed: %s", exc)
1282+
return
1283+
try:
1284+
bound = bind_gateway_runtime(self)
1285+
except Exception as exc:
1286+
logger.warning("Teams pipeline runtime wiring failed: %s", exc)
1287+
return
1288+
if bound:
1289+
logger.info("Teams pipeline runtime bound to msgraph webhook ingress")
1290+
elif self._teams_pipeline_runtime_error:
1291+
logger.warning(
1292+
"Teams pipeline runtime unavailable: %s",
1293+
self._teams_pipeline_runtime_error,
1294+
)
1295+
1296+
12541297
def _warn_if_docker_media_delivery_is_risky(self) -> None:
12551298
"""Warn when Docker-backed gateways lack an explicit export mount.
12561299

@@ -3304,7 +3347,8 @@ async def start(self) -> bool:
33043347

33053348
# Update delivery router with adapters
33063349
self.delivery_router.adapters = self.adapters
3307-
3350+
self._wire_teams_pipeline_runtime()
3351+
33083352
self._running = True
33093353
self._update_runtime_status("running")
33103354

hermes_cli/main.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10052,7 +10052,9 @@ def cmd_plugins(args):
1005210052
# =========================================================================
1005310053
try:
1005410054
from plugins.memory import discover_plugin_cli_commands
10055+
from hermes_cli.plugins import discover_plugins, get_plugin_manager
1005510056

10057+
seen_plugin_commands = set()
1005610058
for cmd_info in discover_plugin_cli_commands():
1005710059
plugin_parser = subparsers.add_parser(
1005810060
cmd_info["name"],
@@ -10061,6 +10063,23 @@ def cmd_plugins(args):
1006110063
formatter_class=__import__("argparse").RawDescriptionHelpFormatter,
1006210064
)
1006310065
cmd_info["setup_fn"](plugin_parser)
10066+
if cmd_info.get("handler_fn") is not None:
10067+
plugin_parser.set_defaults(func=cmd_info["handler_fn"])
10068+
seen_plugin_commands.add(cmd_info["name"])
10069+
10070+
discover_plugins()
10071+
for cmd_info in get_plugin_manager()._cli_commands.values():
10072+
if cmd_info["name"] in seen_plugin_commands:
10073+
continue
10074+
plugin_parser = subparsers.add_parser(
10075+
cmd_info["name"],
10076+
help=cmd_info["help"],
10077+
description=cmd_info.get("description", ""),
10078+
formatter_class=__import__("argparse").RawDescriptionHelpFormatter,
10079+
)
10080+
cmd_info["setup_fn"](plugin_parser)
10081+
if cmd_info.get("handler_fn") is not None:
10082+
plugin_parser.set_defaults(func=cmd_info["handler_fn"])
1006410083
except Exception as _exc:
1006510084
logging.getLogger(__name__).debug("Plugin CLI discovery failed: %s", _exc)
1006610085

plugins/teams_pipeline/__init__.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""Teams meeting pipeline plugin.
2+
3+
Registers only operator-facing CLI surfaces. The agent should invoke these via
4+
the terminal tool; no model tools are added by this plugin.
5+
"""
6+
7+
from __future__ import annotations
8+
9+
from plugins.teams_pipeline.cli import register_cli, teams_pipeline_command
10+
11+
12+
def register(ctx) -> None:
13+
ctx.register_cli_command(
14+
name="teams-pipeline",
15+
help="Inspect and operate the Microsoft Teams meeting pipeline",
16+
setup_fn=register_cli,
17+
handler_fn=teams_pipeline_command,
18+
description=(
19+
"Operator CLI for the Microsoft Teams meeting pipeline. "
20+
"Lists jobs, inspects stored runs, replays jobs, validates Graph "
21+
"setup, and maintains Graph subscriptions."
22+
),
23+
)

0 commit comments

Comments
 (0)