Files
LangBot/docs/agent-runner-pluginization/IMPLEMENTATION_PLAN.md
2026-05-29 22:41:10 +08:00

25 KiB
Raw Blame History

Agent Runner 插件化当前实现与收尾计划

2026-05-29 状态说明:本文档是实现推进计划和历史上下文,不是最新验收结论的唯一来源。当前设计入口见 README.md,协议边界见 PROTOCOL_V1.md,进度见 PROGRESS.md,下一轮测试入口见 PHASE1_QA_ACCEPTANCE_MATRIX.md

本文档面向实现 agent用来把当前 AgentRunner 插件化实现推进到可迁移状态。

当前代码已经不是从零开始的 PoC。LangBot 已经具备 registry、orchestrator、context/resource builder、result normalizer 和插件 runtime action。本计划重点描述剩余工作补齐宿主通用能力、对齐旧内置 runner 行为、完成官方 runner 插件迁移验收。

1. 最终状态

LangBot 最终只保留 Agent Runner 的宿主能力:

  • 发现 runnerAgentRunnerRegistry
  • 选择 runnerPipeline 配置和未来事件绑定配置
  • 构造上下文:AgentRunContext
  • 裁剪资源:模型、工具、知识库、文件、存储、平台能力
  • 调度执行:AgentRunOrchestrator
  • 归一结果:AgentRunResult -> 当前 Pipeline 的 Message / MessageChunk
  • 隔离错误:插件异常、协议错误、超时、结果过大不能破坏主流程
  • 迁移旧配置:把旧内置 runner 配置迁到官方 AgentRunner 插件配置
  • 转发调用:插件 runtime 只维护已安装插件本身的运行实例Pipeline 不创建插件实例或 runner 实例

LangBot 不再长期维护内置业务 runner 分支。local-agent、Dify、n8n、Coze、DashScope、Langflow、Tbox 等都迁到官方 AgentRunner 插件。

迁移期间允许旧 RequestRunner 文件继续存在,作为行为对齐基准和回退分析材料。它们不影响当前进度;真正的最终条件是主聊天执行路径不再依赖旧 runner。

1.1 当前状态快照

已完成或基本完成:

  • AgentRunnerDescriptor、runner id 解析、registry。
  • AgentRunOrchestrator 替换 ChatMessageHandler 内部 runner 调度。
  • AgentRunContextBuilderAgentResourceBuilderAgentResultNormalizer
  • ai.runner.id + ai.runner_config[id] 的读取与旧配置映射。
  • AgentRunner runtime actionLIST_AGENT_RUNNERSRUN_AGENT
  • run-scoped proxy authorization模型、工具、知识库、存储、文件。
  • EventLog / Transcript / ArtifactStore / PersistentStateStore。
  • Pipeline adapter 已委托到 event-first run(event, binding)
  • local-agent 与 Claude Code runner 已通过本地 WebUI smoke。

仍需收尾:

  • Docs final QA 与安装/发布文档整理。
  • timeout/deadline、取消、插件无输出、协议错误的端到端保护。
  • 官方 runner 插件安装/预装/迁移缺失处理。
  • 安全发布级 hardening路径隔离、权限边界、secret、MCP/skill 投影策略、资源配额、审计。此项不阻塞当前协议闭环,详见 SECURITY_HARDENING.md
  • Codex / Kimi runner 全量接入、issue-centric 队列、复杂 workflow engine 和 EBA 分支完整联调。

2. 高层架构

Pipeline MessageProcessor / future EventRouter
        |
        v
AgentRunOrchestrator
        |
        +--> AgentRunnerRegistry
        |       +--> plugin runtime LIST_AGENT_RUNNERS
        |       +--> descriptor cache / validation
        |
        +--> AgentRunContextBuilder
        +--> AgentResourceBuilder
        +--> AgentResultNormalizer
        |
        v
PluginRuntimeConnector.run_agent()
        |
        v
SDK Runtime RUN_AGENT -> plugin AgentRunner.run()

关键约束:

  • ChatMessageHandler 不解析 plugin:*,不实例化 wrapper不知道 runner 组件细节。
  • PipelineService.get_pipeline_metadata() 不直接访问插件 runtime而是读取 registry。
  • RequestRunner 只作为迁移参考,不作为最终运行路径。
  • AgentRunOrchestrator 是 LangBot 侧运行编排层:负责 runner 绑定解析、资源授权、context envelope provisioning、run scope 注册、插件调用和结果归一化;不负责决定 Agent 的最终 prompt/window/压缩策略。
  • 插件是无状态执行单元:多个 Pipeline 可以绑定同一个 runner id并分别保存自己的 ai.runner_config[id];运行时 LangBot 只把当前绑定配置放入 ctx.config 转发给同一个插件 runner。
  • 禁止按 Pipeline 或 runner config 创建多个插件实例。需要跨请求持久化的状态必须走明确授权的 plugin storage / workspace storage / 外部服务,不能隐式保存在 per-pipeline 插件对象里。
  • EBA 只做字段预留,不在本轮实现 EventBus、EventRouter、平台动作执行。

3. 新增 LangBot 模块

建议新增:

src/langbot/pkg/agent/
  __init__.py
  runner/
    __init__.py
    descriptor.py
    errors.py
    id.py
    registry.py
    context_builder.py
    resource_builder.py
    orchestrator.py
    result_normalizer.py
    config_migration.py

3.1 descriptor.py

定义 LangBot 内部使用的 descriptor

class AgentRunnerDescriptor(BaseModel):
    id: str
    source: Literal["plugin"]
    label: dict[str, str]
    description: dict[str, str] | None = None
    plugin_author: str
    plugin_name: str
    runner_name: str
    plugin_version: str | None = None
    protocol_version: str = "1"
    config_schema: list[dict[str, Any]] = []
    capabilities: dict[str, bool] = {}
    permissions: dict[str, list[str]] = {}
    raw_manifest: dict[str, Any] = {}

source == "builtin" 不作为最终目标。如果实现阶段需要临时 adapter必须标记为测试过渡代码并在官方插件跑通后删除。

3.2 id.py

统一 runner id 解析和生成:

  • 插件 runner idplugin:{author}/{plugin_name}/{runner_name}
  • parse_runner_id(id) 返回结构化对象
  • 禁止业务代码手写字符串 split
  • PoC 已存在的 plugin:author/name/runner 继续作为合法 id

3.3 registry.py

职责:

  • 调用 ap.plugin_connector.list_agent_runners(bound_plugins=None) 拉取插件 runner
  • 校验 manifest
    • kind == AgentRunner
    • metadata.name 存在
    • metadata.label 存在
    • spec.protocol_version 兼容,默认 1
    • spec.config 是 list默认空
    • spec.capabilities 是 dict默认空
    • spec.permissions 是 dict默认空
  • 输出 AgentRunnerDescriptor
  • 缓存 discovery 结果,提供 refresh()
  • 单个插件 manifest 失败只记录 warning不影响其它 runner

刷新触发点:

  • 插件安装、卸载、升级、重启后
  • Pipeline metadata 请求时发现缓存为空
  • 可选 TTL优先保证正确性

3.4 context_builder.py / pipeline_adapter.py

context_builder.py 只负责从 AgentEventEnvelope + AgentBinding 构造 SDK v1 AgentRunContext。Pipeline Query 的读取、参数过滤、prompt 提取和 max-round bootstrap 映射都属于 PipelineAdapter,不再放进 context builder。

当前消息 Pipeline 进入 agent runner 的路径:

Query
  -> PipelineAdapter.query_to_event(query)
  -> PipelineAdapter.pipeline_config_to_binding(query, runner_id)
  -> PipelineAdapter.build_adapter_context(query, binding)
  -> AgentRunOrchestrator.run(event, binding, adapter_context=...)
  -> AgentRunContextBuilder.build_context_from_event(...)

Protocol v1 context 的稳定字段:

  • run_id: 新 UUID不使用 query id 作为全局 run id
  • trigger.type: 事件触发类型,例如 message.received
  • conversation: conversation/thread/launcher/sender/bot/pipeline 投影
  • event: 稳定事件上下文
  • actor: 触发者
  • subject: 当前消息、群、频道或其它事件主体
  • input: 当前事件输入,不是历史消息窗口
  • delivery: 输出 surface 和平台投递能力
  • resources: 由 resource_builder 基于 binding policy 注入
  • state: PersistentStateStore 读取的 host-managed scoped state snapshot
  • runtime: host/version/workspace/bot/query/trace/deadline
  • config: 当前 binding 对该 runner id 的配置,即 runner_config
  • bootstrap: 可选小窗口,不是完整历史
  • adapter: Pipeline 或其它入口 adapter 的元数据

Pipeline adapter 的 prompt 和公开业务变量不进入顶层协议字段:

  • filtered params -> ctx.adapter.extra["params"]
  • legacy/effective prompt 可以暂存到 ctx.adapter.extra["prompt"],但 official runner 不应把它当作行为契约
  • max-round working window 可以保留在 Pipeline adapter 兼容层,但 official local-agent 不消费该 bootstrap/window
  • packaging 元数据 -> ctx.runtime.metadata.context_packaging

现阶段不要把新的压缩或 token-budget 裁剪塞回 Pipeline stage。Pipeline 只负责入口适配;完整历史和长期上下文由 EventLog / Transcript / pull APIs / future ContextCompressor 支撑。

3.4.1 Agentic context plan

本轮只在 PipelineAdapter 中保留 max-round working window不改变 user-round 选择规则。 EventLog / Transcript / Host pull APIs 已落地,ContextCompressor 仍是设计预留。 目标是让 Pipeline 逐步退化为入口 adapter让 AgentRunner 层拥有上下文打包职责。

建议最终拆成四个 host-side 服务:

ConversationStore / EventLog
  -> durable append-only raw messages, events, tool results, artifact refs
ConversationProjection
  -> converts events into agent-readable conversation history
PipelineAdapter bootstrap policy
  -> builds the bounded working context for one run
ContextCompressor
  -> creates and updates summaries/checkpoints when thresholds are exceeded

关键原则:

  • 完整历史属于 LangBot host不属于插件实例。插件仍是 singleton/stateless。
  • ctx.bootstrap.messages 是 optional working context window不是完整 conversation dump。
  • 每轮不能全量复制/序列化完整历史给插件 runtime否则长会话会产生 O(n) 成本和跨进程 payload 膨胀。
  • max-round 的 user-round 规则只属于 Pipeline adapter 的 bootstrap 策略。
  • LiteLLM 接入后context packaging 应升级为 token budget / summary / pull API 协作策略。
  • ContextCompressor 生成的是派生 summary/checkpoint不能覆盖或删除 raw history。
  • 重启恢复依赖持久化 store 和 summary checkpoint不依赖 SessionManager 里的进程内 conversation list。

后续 AgentRunContext 可增加:

context_request: AgentContextRequest | None
context_packaging: ContextPackagingMetadata

建议语义:

  • context_request.mode: AgentRunner manifest / binding config 请求的 max_roundtoken_budgetsummary_hybridexternal_session
  • context_request.budget: 模型窗口、预留输出 token、工具/RAG 预算等偏好
  • context_packaging.policy: Host 本次实际采用的打包策略
  • context_packaging.delivered_count: 本次下发的历史消息数
  • context_packaging.source_total_count: packager 可见的原始历史消息数
  • context_packaging.messages_complete: 本窗口是否已经包含完整历史
  • context_packaging.cursor_before: 未来通过 host API 读取更早历史的 cursor

未来需要的受限 API

api.get_conversation_messages(cursor: str | None, limit: int) -> HistoryPage
api.get_context_summary(scope: str = "conversation") -> ContextSummary | None
api.request_context_compaction(policy: dict) -> CompactionResult

这些 API 必须绑定 run_id、runner id、actor/subject scope 和资源权限Host 需要限制 page size、总字节数、deadline 和可访问 conversation。

3.4.2 Large artifacts and tool collaboration

大文件、多模态输入和工具产物不要内联进 bootstrap messages 或 tool result。后续统一用 artifact/resource ref 协作:

  • message/content 里只放小文本和必要摘要。
  • 大文件、图片、音频、长工具输出返回 artifact_idmime_typesizedigestsummaryexpires_atpermissions
  • /tmp 只能作为单次 run 的临时 staging用于插件或工具短时间读写它不是 durable store 也不能作为重启恢复依据。
  • box/object storage 是长期 artifact 的目标位置。当前分支尚未合并 box 能力,因此本轮只写文档预留,不实现 API。
  • 工具之间传递大结果时应传 artifact ref不传完整 blob。Agent 需要读取时走受限 proxy。

未来建议 API

api.get_artifact_metadata(artifact_id: str) -> ArtifactMetadata
api.open_artifact_stream(artifact_id: str) -> AsyncIterator[bytes]
api.read_artifact_range(artifact_id: str, offset: int, length: int) -> bytes
api.create_temp_artifact(name: str, content_type: str, ttl_seconds: int) -> ArtifactWriter

安全约束:

  • Host 校验 artifact 是否属于当前 run、conversation、actor/subject scope 或授权资源。
  • 默认不允许插件直接读任意本地路径,包括 /tmp 任意路径。
  • 临时文件应有 TTL 和清理机制box artifact 应有 retention policy。
  • 多模态文件进入模型前,由 runner/context packager 决定传引用、摘要、缩略图还是实际 bytes。

3.5 resource_builder.py

执行前做三层裁剪:

  1. runner manifest 声明的 spec.permissions
  2. Pipeline 的 extensions_preferences
  3. 当前 Pipeline runner 绑定配置中选择的资源范围

输出写入 ctx.resources,至少覆盖:

  • models可调用模型 UUID、类型、能力摘要。包括 LLM、fallback LLM、rerank 等 runner config schema 中选择的模型类资源。
  • tools可见工具 manifest使用当前 bound plugins / MCP server 范围
  • knowledge_bases可检索知识库列表
  • storageplugin storage / workspace storage 权限摘要
  • files允许读取的配置文件、知识文件摘要
  • platform_capabilities本阶段只声明不执行平台动作

注意:旧的 unrestricted proxy action 必须二次校验,不能只靠 context 声明。AgentRunner 可用资源应来自 ctx.resources,不是插件 runtime 的全局能力。

本阶段不接入 sandbox/skills也不预留 runner 可见字段。后续相关分支合并后, 执行、文件、skill、MCP 等能力应先由 Host 侧封装成普通 tool再通过 ctx.resources.tools 进入 runnerrunner 不应识别或硬编码执行环境 provider。

资源裁剪要尽量通用,不应只写死 local-agent

  • model-fallback-selector 授权 primary/fallback LLM。
  • llm-model-selector 授权 LLM。
  • rerank-model-selector 授权 rerank 模型。
  • knowledge-base-multi-selector 授权知识库。
  • 后续新增 selector 时应在 resource builder 中统一扩展。

3.5.1 future EventRouter 预留

当前分支不实现 EBA EventRouter但 AgentRunner 协议必须从现在开始兼容非消息事件。未来不要为消息撤回、群成员加入、好友申请各写一套 runner wrapper统一入口应是

EventRouter -> AgentRunOrchestrator.run_from_event(event_request)

EBA 落地后,ConversationStore 不应只保存聊天消息,而应从 EventLog 投影生成:

Platform Adapter
  -> EventLog append raw event
  -> ConversationProjection update message/history view when applicable
  -> EventRouter resolve binding
  -> AgentRunOrchestrator.run_from_event(event_request)
  -> Context packager builds working context from projection + state + artifacts

这样消息事件、工具事件、群成员事件、好友申请事件可以共用同一套 run/session/state/resource 边界;非消息事件也不需要伪造成一条用户文本消息。

event_request 至少需要包含:

  • event_type: 稳定协议名,例如 message.recalledgroup.member_joinedfriend.request_received
  • event_id / event_timestamp
  • event_data: 平台原始 payload 摘要和 source event type
  • actor: 触发者,例如撤回操作者、新成员、好友申请人
  • subject: 事件作用对象,例如被撤回消息、群/成员关系、好友申请
  • conversation: 可选。群事件有 launcher 语义,好友申请可能还没有 conversation
  • input: 可选结构化输入。非消息事件允许 text=Nonecontents=[]
  • binding: 事件绑定解析出的 runner id、runner config、资源范围

先保留的稳定事件名:

  • message.received
  • message.recalled
  • group.member_joined
  • friend.request_received

这些事件名应作为插件协议的一部分保持稳定。平台原始事件名只能进入 event_data,不能成为 ctx.event.event_type 的公共契约。

3.6 result_normalizer.py

只接受 SDK v1 result

  • message.delta
  • message.completed
  • tool.call.started
  • tool.call.completed
  • state.updated
  • run.completed
  • run.failed
  • action.requested 允许实验性返回,但本阶段只记录 telemetry不执行

映射:

  • message.delta.data.chunk -> provider_message.MessageChunk
  • message.completed.data.message -> provider_message.Message
  • run.completed.data.message -> provider_message.Message
  • run.failed -> 抛出受控异常,让 ChatMessageHandler 使用现有错误策略
  • 工具和状态事件默认不 yield 到 Pipeline只记录 debug/telemetry

防护:

  • 未知 type warning 后忽略
  • 单 result 序列化大小限制
  • provider message schema 校验失败转 run.failed
  • 插件没有输出任何消息时,按 runner failed 处理

3.7 orchestrator.py

核心入口:

async def run_from_query(query: pipeline_query.Query) -> AsyncGenerator[Message | MessageChunk, None]:
    runner_id = resolve_runner_id(query.pipeline_config)
    descriptor = await registry.get(runner_id, bound_plugins=query.variables.get("_pipeline_bound_plugins"))
    ctx = await context_builder.from_query(query, descriptor)
    async for raw in plugin_connector.run_agent(...):
        async for message in result_normalizer.normalize(raw):
            yield message

必须覆盖:

  • runner id 不存在
  • 插件系统关闭
  • runner 不在 bound plugins 范围内
  • 插件 runtime 断连
  • runner 协议版本不兼容
  • run 超时
  • task cancellation

4. 配置模型直接切换

配置模型表达的是 Pipeline 到 runner id 的绑定,不表达插件实例。插件安装后由 plugin runtime 管理单个插件运行实例;不同 Pipeline 选择同一个 runner id 时,只是保存不同的 runner_config[id],调用时随 AgentRunContext.config 传入。

目标格式:

{
  "ai": {
    "runner": {
      "id": "plugin:langbot/local-agent/default",
      "expire-time": 0
    },
    "runner_config": {
      "plugin:langbot/local-agent/default": {}
    }
  }
}

兼容读取:

  • 优先读 ai.runner.id
  • 没有 id 时读旧 ai.runner.runner
  • 旧内置 runner 名通过迁移表映射:
    • local-agent -> plugin:langbot/local-agent/default
    • dify-service-api -> plugin:langbot/dify-agent/default
    • n8n-service-api -> plugin:langbot/n8n-agent/default
    • coze-api -> plugin:langbot/coze-agent/default
    • dashscope-app-api -> plugin:langbot/dashscope-agent/default
    • langflow-api -> plugin:langbot/langflow-agent/default
    • tbox-app-api -> plugin:langbot/tbox-agent/default

写入策略:

  • 新 UI 只写 ai.runner.idai.runner_config
  • 后端 update 接口接受旧字段,但保存时归一成新格式
  • migration 最后统一落库

5. 需要修改的 LangBot 范围

必须修改:

  • src/langbot/pkg/core/app.py
    • 增加 agent_runner_registry / agent_run_orchestrator 属性
  • src/langbot/pkg/core/stages/build_app.py
    • 初始化 Agent 子系统
  • src/langbot/pkg/pipeline/process/handlers/chat.py
    • 删除 PluginAgentRunnerWrapper
    • 删除内置 runner 查找逻辑
    • 调用 orchestrator
  • src/langbot/pkg/api/http/service/pipeline.py
    • metadata 从 registry 生成
  • src/langbot/pkg/plugin/connector.py
    • list_agent_runners() / run_agent() 增加协议校验和 bound plugin 参数
  • src/langbot/pkg/plugin/handler.py
    • proxy action 二次权限校验
  • src/langbot/pkg/pipeline/preproc/preproc.py
    • 不再只为 local-agent 构造工具、知识库、模型
    • 对所有 agent runner 保留 multimodal input
  • src/langbot/pkg/pipeline/pipelinemgr.py
    • runner name 监控改读 runner.id
  • src/langbot/templates/metadata/pipeline/ai.yaml
    • runner 字段从 runner 迁到 id
  • src/langbot/templates/default-pipeline-config.json
    • 默认 runner 改为官方 local-agent 插件 id
  • web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx
    • 当前 runner 改读 ai.runner.id
    • runner 配置区改写入 ai.runner_config[id]

最终删除或停用:

  • src/langbot/pkg/provider/runner.py 的业务注册路径
  • src/langbot/pkg/provider/runners/* 的运行入口

可以暂时保留文件作为官方插件迁移参考,但不应被运行时引用。

6. 收尾实现顺序

Step 1补齐宿主上下文

  • SDK AgentRunContext 保持 event-firstevent/input/delivery/resources/context/state/runtime/config/bootstrap/adapter
  • LangBot context builder 只从 AgentEventEnvelope + AgentBinding 写入稳定协议字段。
  • Pipeline adapter 可以把公开业务变量写入 ctx.adapter.extra["params"]legacy/effective prompt 若保留在 ctx.adapter.extra["prompt"],也只属于 adapter metadata。
  • 保持 ctx.config 只表达静态绑定配置。

Step 2增强宿主 AgentRun proxy action

  • invoke_llm / invoke_llm_stream 通过 run_id/query_id 找回当前 Query。
  • 自动合并 model persisted extra_args 与 action-level override。
  • 自动应用 pipeline remove-think,并允许 action 显式 override。
  • call_tool 传回当前 Query恢复旧工具调用上下文。
  • retrieve_knowledge 保持 bot_uuidsender_idsession_name 等 settings。
  • invoke_rerank 使用 run-scoped model authorization。

Step 3泛化资源构建

  • 按 manifest permissions + bound plugins/MCP + runner config schema 构造资源。
  • 支持 primary/fallback LLM、rerank model、KB selector。
  • 不把 local-agent 特例扩散到通用资源层。

Step 4local-agent parity

  • 使用静态绑定配置 ctx.config["prompt"],不读取 ctx.adapter.extra["prompt"]
  • 通过 Host history API 拉取 transcript不读取 ctx.bootstrap.messagesctx.adapter.adapter_messages
  • 当前 user message 从 ctx.input.contents 构造,保留多模态内容。
  • RAG 只替换/插入文本部分,不丢图片/文件。
  • streaming/non-streaming 默认跟随 runtime.metadata.streaming_supported
  • 首轮 fallback 成功后tool loop 固定使用 committed model。
  • tool loop 继续传可用 tools支持多步工具调用。
  • rerank 通过授权模型资源调用。

Step 5端到端保护和测试

  • 插件无输出时按 runner failed 处理。
  • timeout/deadline 覆盖 plugin runtime、模型调用和外部 runner 调用。
  • runner 协议错误转受控错误。
  • 覆盖 local-agent 用户可见行为普通回复、流式、工具、多步工具、KB、rerank、多模态、绑定 prompt、history API。

Step 6官方 runner 迁移

  • 官方插件 ready 后移除内置 runner registry
  • 删除或隔离 provider runners 的运行引用
  • 测试旧 runner 名只能通过 migration 映射到插件 id

Step 7历史配置迁移

  • 写 persistence migration
  • 更新 default pipeline config
  • 对已存在 Pipeline 执行旧字段到新字段迁移
  • 对监控/日志里的 runner 字段改用新 id

7. 测试要求

单测:

  • runner id parse / format
  • registry manifest 校验、失败隔离、bound plugins 过滤
  • context builder 从 query 生成完整 v1 context
  • resource builder 三层裁剪
  • result normalizer 对每种 result type 的映射
  • 旧配置 resolve 和 migration

集成测试:

  • fake AgentRunner 插件可被 Pipeline 选择
  • streaming 输出仍能更新 message card
  • 插件异常返回用户可理解错误,不中断 runtime
  • runner 不在 bound plugins 时不可执行
  • 未授权工具 / 知识库 / 模型 proxy 调用被拒绝
  • local-agent Pipeline 配置迁到官方插件 id

8. 验收标准

  • LangBot Pipeline 可以选择插件 AgentRunner 并完成非流式和流式回复。
  • ChatMessageHandler 不包含插件 runner 解析和 wrapper。
  • PipelineService 不直接拼插件 runner metadata。
  • 所有 runner 配置使用 ai.runner.id + ai.runner_config
  • 插件 runtime 不为每个 Pipeline 或 runner 配置创建插件实例;runner_config 只作为绑定配置随 ctx.config 传入。
  • 主聊天路径不再通过旧内置 runner 执行业务 runner。迁移期间旧文件可以保留。
  • 插件只能访问 ctx.resources 授权的模型、工具、知识库和文件。
  • 宿主 action 能为 AgentRunner 调用恢复必要 Query 语义,插件不需要拿裸 Query。
  • 官方 local-agent 插件对外行为与旧内置 local-agent 对齐。
  • EBA 相关字段只作为 context/result 预留,不执行平台动作。