mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-09-16 14:57:15 +00:00
feat(runner): unify plugin execution across agents and event processors
This commit is contained in:
@@ -1,30 +1,18 @@
|
||||
# Event processors and Pipeline plugin compatibility
|
||||
|
||||
Status: implemented in the 4.11 development branches of LangBot and the Plugin SDK,
|
||||
2026-09-08. The Host uv configuration pins the matching SDK commit. Deploy both
|
||||
revisions together; older SDK releases do not contain this component. Switch the
|
||||
development source pin to a published SDK release before a stable PyPI release.
|
||||
This design supersedes the automatic EBA EventListener observer broadcast in
|
||||
the earlier EBA documents. Existing Pipeline plugin behavior remains supported.
|
||||
# Runner components and Pipeline plugin compatibility
|
||||
|
||||
## Product boundary
|
||||
|
||||
Three processor types appear together in the Processors area:
|
||||
| Product | Implementation | Event entry |
|
||||
| --- | --- | --- |
|
||||
| Pipeline | Pipeline stages and an agent-capable Runner | Received messages |
|
||||
| Agent | A Runner with `spec.usages: [agent]` | Configured events |
|
||||
| Plugin processor | A Runner with `spec.usages: [event]` | Events declared in `spec.events` |
|
||||
|
||||
| Product | Implementation | Event entry | Flow ownership |
|
||||
| --- | --- | --- | --- |
|
||||
| Pipeline | Existing Pipeline stages and configuration | Received messages | Pipeline stages, including legacy plugin hooks |
|
||||
| Agent | A configured plugin AgentRunner | Supported EBA events | The selected runner |
|
||||
| Event processor | A configured plugin EventProcessor | Supported EBA events | Plugin Python handlers |
|
||||
|
||||
Use **Event processor** as the product label and **EventProcessor** as the SDK
|
||||
component name. The localized description should explain that the plugin defines
|
||||
the processing logic. It must not suggest an LLM, prompt, or visual workflow is
|
||||
required.
|
||||
|
||||
An installed component is a reusable implementation. A processor instance is a
|
||||
user-created configuration of that component. A Bot event binding selects an
|
||||
instance, not an installed plugin package directly.
|
||||
Runner is the only component for these execution styles. `spec.usages` can contain
|
||||
both `agent` and `event`; these are selection capabilities, not mutually exclusive
|
||||
execution modes. An installed component is reusable code. Users create processor
|
||||
instances, select a Runner and configure it, then bind Bot events to the instance.
|
||||
Installation alone never subscribes a component to incoming events.
|
||||
|
||||
## Legacy EventListener contract
|
||||
|
||||
@@ -57,168 +45,48 @@ information must be distinguished from fields that were dropped during conversio
|
||||
Direct Agent and Event processor execution must not synthesize Pipeline lifecycle
|
||||
hooks. Those hooks describe actual Pipeline stages.
|
||||
|
||||
## EventProcessor SDK contract
|
||||
## SDK and runtime
|
||||
|
||||
Introduce a distinct component kind instead of changing what an existing
|
||||
EventListener manifest means. One package may contain both kinds; only the legacy
|
||||
EventListener participates in Pipeline hook dispatch.
|
||||
`lbp comp Runner` generates `components/runner`. Every component uses
|
||||
`plugin:author/plugin/name` as its identity. Names are unique within a plugin.
|
||||
The component can override `async run(ctx)` and yield RunnerResult objects, or
|
||||
register typed platform callbacks through `@self.handler(EventClass)` in
|
||||
`initialize()`. Default run dispatches an exact handler, falling back to EBAEvent.
|
||||
A custom run can delegate to this dispatch with `await super().run(ctx)`.
|
||||
|
||||
Retain the familiar authoring shape:
|
||||
Both styles share RunnerContext, invocation-bound ctx.api, logs, replies, deadlines,
|
||||
cancellation, worker isolation and the run ledger. ctx.event is the envelope;
|
||||
ctx.platform_event is the typed platform payload. Each invocation owns its context;
|
||||
never put the current context or run ID on a shared component or plugin instance.
|
||||
|
||||
```python
|
||||
from langbot_plugin.api.definition.components.event_processor import EventProcessor, EventProcessorContext
|
||||
from langbot_plugin.api.entities.builtin.platform.events import MemberJoinedEvent
|
||||
class WelcomeProcessor(EventProcessor):
|
||||
async def initialize(self):
|
||||
await super().initialize()
|
||||
The runtime emits completion on normal return unless the Runner already emitted a
|
||||
terminal result. Exceptions fail the run and retain preceding results. Cancelling
|
||||
the result stream cancels execution. There is no implicit retry or hidden model
|
||||
loop. Returned text and logs do not send platform messages: replies are explicit
|
||||
ctx.reply / ctx.reply_stream actions. Pipeline retains its configured output stage.
|
||||
|
||||
@self.handler(MemberJoinedEvent)
|
||||
async def on_join(ctx: EventProcessorContext):
|
||||
await ctx.reply(f"Hello, {ctx.event.member.nickname}")
|
||||
```
|
||||
`self.plugin` continues to expose ordinary plugin APIs. ctx.api carries run-scoped
|
||||
resource grants and records tool actions. Workspace and installation authorization
|
||||
remain Host-enforced. Run identity and API operation scope are separate concepts.
|
||||
|
||||
Handlers receive typed EBA events directly. Do not maintain a second, incomplete
|
||||
mapping into plugin-only EBA wrapper classes. Preserve complete public event
|
||||
fields; compact log previews must not become the execution payload. Include the
|
||||
generic platform-specific event contract for adapter-specific events.
|
||||
## Selection and observability
|
||||
|
||||
The context belongs to one invocation and exposes the event, processor/run
|
||||
identifiers, instance configuration, logging, and authorized Host APIs. It has no
|
||||
fabricated Pipeline Query. Reuse Host run tracking, deadlines, installation
|
||||
authority, platform capabilities, and delivery records where appropriate.
|
||||
Both product selectors discover the same Runner catalog and filter by usage.
|
||||
Validate usage again before execution. Event-capable Runners must declare events;
|
||||
users can route a subset, but cannot expand the manifest capability. Unconfigured
|
||||
instances expose no event subscriptions. Workspace ownership, plugin scope and
|
||||
instance identity are checked for routing, execution, cancellation and run reads.
|
||||
|
||||
A handler returning normally completes its invocation. There is no implicit LLM
|
||||
loop, automatic second processor, or hidden retry of side effects. An exception
|
||||
marks the run failed and retains the associated log. New processing handlers do
|
||||
not use prevent_default to control another processor; routing has already chosen
|
||||
the current processor. The legacy methods keep their existing Pipeline meaning.
|
||||
Plugin processor details keep event debugging on the left and configuration/logs
|
||||
on the right. Component settings use the existing schema form. Logs and action
|
||||
results remain distinct from actual platform delivery; debug delivery is Mock.
|
||||
Agent-native interactions stay on the Agent product path; typed handlers consume
|
||||
platform events. Legacy Pipeline lifecycle hooks remain on the Pipeline path.
|
||||
|
||||
## Activation and routing
|
||||
## Validation
|
||||
|
||||
The activation sequence is explicit:
|
||||
|
||||
1. Install a plugin containing an EventProcessor component.
|
||||
2. Create an Event processor in the Processors area.
|
||||
3. Open its detail page, select a plugin component, and save its configuration.
|
||||
4. Bind a Bot event to that processor instance in the existing event routing UI.
|
||||
|
||||
Installation and processor creation alone do not subscribe to Bot events.
|
||||
The component declares the event types it handles; Bot bindings select the subset
|
||||
of supported events to deliver. One package may supply multiple components, and
|
||||
multiple instances may use the same component with independent configuration.
|
||||
|
||||
Extend the existing single-target route arbitration with `event_processor`.
|
||||
There is no automatic EBA broadcast to installed EventListeners. Keep Pipeline hook dispatch inside the Pipeline path. Existing observer
|
||||
plugins must explicitly adopt the new component and be bound by the user; do not
|
||||
create subscriptions during migration.
|
||||
|
||||
An unconfigured instance has no supported events and cannot execute. Validate
|
||||
component availability, event compatibility, Workspace ownership, and instance
|
||||
identity when configuring the instance and again at invocation. A disabled or
|
||||
unavailable plugin leaves the instance visible with an actionable unavailable
|
||||
status. It must not silently fall back to Agent or Pipeline.
|
||||
|
||||
## Compact UI
|
||||
|
||||
Creation adds a third type next to Agent and Pipeline and asks only for basic
|
||||
instance information. Select the plugin component in the detail-page header.
|
||||
Show component-defined configuration in the right pane, with Configuration and
|
||||
Logs tabs. Keep unsaved values when switching tabs, and open Logs after a debug
|
||||
run finishes. The component selector remains in the page header.
|
||||
If no component is installed, show a relevant plugin installation entry point;
|
||||
installing still does not create a binding.
|
||||
|
||||
The detail page keeps event debugging on the left while the right pane switches
|
||||
between configuration and logs. A compact run list shows event type, time, status and known
|
||||
processing duration. Selecting a row shows that run's identity, input, logs,
|
||||
actions and outcome below. There is no shared timeline between unrelated runs.
|
||||
The additive `created_at_ms`, `started_at_ms`, and `finished_at_ms` fields retain
|
||||
Host lifecycle precision for elapsed-time display.
|
||||
Keep payloads and error details collapsed until expanded. Distinguish attempted
|
||||
delivery from confirmed delivery and display the actual destination.
|
||||
|
||||
Place component identity, availability, bindings, and configuration in a compact
|
||||
secondary area. Do not add a prompt editor, model selector, or flow designer.
|
||||
The plugin implements the processing flow in code.
|
||||
|
||||
## Delivery sequence and acceptance
|
||||
|
||||
1. Repair and regression-test Pipeline EventContext handling and legacy payload
|
||||
conversion independently of the new processor feature.
|
||||
2. Add the SDK component, context, manifest/scaffolding, and explicit invocation
|
||||
contract; verify registration, event coverage, and process isolation.
|
||||
3. Add Host instance management, event routing, execution tracking, and matching
|
||||
HTTP/MCP/skill surfaces. Turn off automatic EBA observer dispatch in this step.
|
||||
4. Add creation, binding, availability, logs, and delivery trace UI with i18n.
|
||||
5. Exercise a real packaged plugin through installation, explicit instance
|
||||
creation, Bot binding, invocation, logging, and reply delivery.
|
||||
|
||||
Acceptance must prove that installation alone invokes no handlers; one matching
|
||||
binding invokes exactly the chosen component; instance configuration and run
|
||||
history remain separate; all declared EBA events retain their fields; unavailable
|
||||
components fail visibly; and legacy plugins keep the documented Pipeline hook
|
||||
order and behavior. Unit tests alone do not establish a successful live plugin
|
||||
installation or platform delivery.
|
||||
|
||||
|
||||
## Implemented transport and APIs
|
||||
|
||||
`lbp comp EventProcessor` scaffolds a component in `components/event_processor`.
|
||||
Its manifest uses `kind: EventProcessor` and `spec.events`, for example
|
||||
`[group.member_joined]`. `spec.config` defines instance parameters. A component
|
||||
that calls `ctx.reply()` declares `spec.permissions.tools: [detail, call]`.
|
||||
|
||||
References use `event_processor:author/plugin/component`, separate from
|
||||
`plugin:author/plugin/runner`. Both kinds share the existing run transport,
|
||||
installation authorization, deadlines and run ledger. The trusted Host selects
|
||||
the component kind; the worker invokes only that exact kind and name.
|
||||
There is no model invocation in the EventProcessor base class.
|
||||
|
||||
`EventProcessorContext` provides `event`, `run_id`, `config`, `api`, `log()` and
|
||||
`reply()`. `api` is the existing run-scoped Host proxy. Use `ctx.config` for instance
|
||||
parameters; plugin installation configuration remains separate. Handlers may
|
||||
register the `EBAEvent` base class as a catch-all. An exact typed handler takes
|
||||
precedence over that fallback. Multiple handlers for the same type run in their
|
||||
registration order, within one invocation.
|
||||
|
||||
HTTP instance management uses `/api/v1/agents` with `kind: event_processor`.
|
||||
Metadata at `/api/v1/agents/_/metadata` lists installed `event_processors`.
|
||||
Creation accepts `component_ref` and `parameters`; the Host derives the supported
|
||||
event patterns from the component. Bot bindings use `target_type: event_processor`
|
||||
and the created instance UUID as `target_id`.
|
||||
|
||||
- `GET /api/v1/agents/{id}/runs?before_id=...` lists this instance's runs.
|
||||
- `GET /api/v1/agents/{id}/runs/{run_id}/events?after_sequence=...` pages its logs
|
||||
and action results. A run from another instance or Workspace is rejected.
|
||||
- The corresponding MCP tools are `get_processor_metadata`, `list_processor_runs`
|
||||
and `get_processor_run_events`, alongside processor CRUD.
|
||||
- `/api/v1/agents/{id}/debug` accepts a full typed EBA event in `data`. Platform
|
||||
actions use Mock; other authorized tools retain their configured behavior.
|
||||
|
||||
The detail page polls run updates, keeps payload details collapsed and separates
|
||||
logs from platform delivery. Completed handlers produce no synthetic reply text.
|
||||
|
||||
|
||||
## Verification (2026-09-08)
|
||||
|
||||
- SDK API, scaffolding and Plugin Runtime suites: 684 passed.
|
||||
- Host runner, service, controller, MCP and adapter regression suites: 971 passed.
|
||||
- Pipeline and registry regression suites: 243 passed, one environment-dependent skip.
|
||||
- Frontend unit suite: 74 passed; TypeScript and changed-file lint checks passed.
|
||||
- Real packaged-plugin tests cover installation, component-kind separation,
|
||||
invocation, mock platform delivery, instance isolation and persisted logs.
|
||||
- Authenticated Edge testing created an instance and a loopback OneBot bot, saved
|
||||
a member-join binding, injected one native notice and received exactly one
|
||||
`send_group_msg` response. The detail page displayed the completed run,
|
||||
localized action name, destination and returned message ID. No external IM
|
||||
account or live model was involved.
|
||||
- Browser regression covers expanding long payloads, reaching the last log and
|
||||
pagination without duplication. Eight unrelated existing browser failures were
|
||||
reproduced against the pre-change commit; the full suite is not green.
|
||||
- Repository-wide lint also retains the pre-existing duplicate `send_image_msg`
|
||||
in the WeCom customer-service library and existing formatting failures outside
|
||||
this change. The i18n check has the same pre-existing diagnostics as its baseline.
|
||||
|
||||
Native Agent interaction-resumption is not enabled for EventProcessor bindings;
|
||||
its input contract is the platform EBA event collection, not a synthetic Agent
|
||||
continuation. Plugins should handle platform events through their typed handlers.
|
||||
SDK tests cover both execution styles, event matrices, concurrent contexts,
|
||||
termination, cancellation and permissions. Packaged CLI tests generate, build and
|
||||
execute the published component. Core tests cover usage-filtered discovery, event
|
||||
routing, Workspace authorization and real plugin-runtime transport. RunnerDemo
|
||||
provides multi-step actions, configuration isolation and controlled failures.
|
||||
|
||||
Reference in New Issue
Block a user