diff --git a/docs/agent-runner-pluginization/IMPLEMENTATION_PLAN.md b/docs/agent-runner-pluginization/IMPLEMENTATION_PLAN.md index cdae3e31..6da645ab 100644 --- a/docs/agent-runner-pluginization/IMPLEMENTATION_PLAN.md +++ b/docs/agent-runner-pluginization/IMPLEMENTATION_PLAN.md @@ -158,7 +158,7 @@ class AgentRunnerDescriptor(BaseModel): - `run_id`: 新 UUID,不使用 query id 作为全局 run id - `trigger.type`: `message.received` - `conversation`: launcher、sender、bot、pipeline、历史消息 -- `event`: message event envelope 子集 +- `event`: message event envelope 子集,`event_type` 使用稳定协议名,平台/SDK 原始事件名放入 `event_data.source_event_type` - `actor`: sender - `subject`: 当前消息或 launcher - `prompt`: 宿主已处理的有效 prompt,即 `query.prompt.messages` @@ -211,6 +211,34 @@ ctx.prompt + ctx.messages + [current_user_message_from_ctx.input] - `knowledge-base-multi-selector` 授权知识库。 - 后续新增 selector 时应在 resource builder 中统一扩展。 +### 3.5.1 future EventRouter 预留 + +当前分支不实现 EBA EventRouter,但 AgentRunner 协议必须从现在开始兼容非消息事件。未来不要为消息撤回、群成员加入、好友申请各写一套 runner wrapper;统一入口应是: + +```text +EventRouter -> AgentRunOrchestrator.run_from_event(event_request) +``` + +`event_request` 至少需要包含: + +- `event_type`: 稳定协议名,例如 `message.recalled`、`group.member_joined`、`friend.request_received` +- `event_id` / `event_timestamp` +- `event_data`: 平台原始 payload 摘要和 source event type +- `actor`: 触发者,例如撤回操作者、新成员、好友申请人 +- `subject`: 事件作用对象,例如被撤回消息、群/成员关系、好友申请 +- `conversation`: 可选。群事件有 launcher 语义,好友申请可能还没有 conversation +- `input`: 可选结构化输入。非消息事件允许 `text=None`、`contents=[]` +- `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: diff --git a/docs/agent-runner-pluginization/PHASE1_QA_ACCEPTANCE_MATRIX.md b/docs/agent-runner-pluginization/PHASE1_QA_ACCEPTANCE_MATRIX.md new file mode 100644 index 00000000..c3638c46 --- /dev/null +++ b/docs/agent-runner-pluginization/PHASE1_QA_ACCEPTANCE_MATRIX.md @@ -0,0 +1,165 @@ +# Agent Runner 插件化 Phase 1 QA 验收矩阵 + +本文档用于指导测试 agent 验收 Phase 1:Agent Runner 插件化是否已经达到旧内置 runner 的对外效果。 + +Phase 1 的目标是让当前聊天 Pipeline 在选择插件化 AgentRunner 后,用户可感知行为与旧内置 runner 保持一致。Phase 2/EBA 不纳入本轮验收。 + +## 1. 验收边界 + +本轮必须验收: + +- Pipeline 仍按现有消息入口运行。 +- Runner 由插件提供,并通过 `AgentRunOrchestrator` 调用。 +- `local-agent` 插件达到旧内置 local-agent 的主要行为 parity。 +- 官方外部 runner 插件至少完成 smoke 验收。 +- 旧 Pipeline 配置兼容,新配置可保存并生效。 +- 权限裁剪、错误隔离、运行状态更新不破坏主流程。 + +本轮不验收: + +- EBA EventBus。 +- EBA EventRouter。 +- 消息撤回、群成员加入、好友申请等非消息事件的真实接入。 +- `action.requested` 平台动作执行。 +- 新平台 API 权限模型。 + +上述非目标只允许检查协议预留是否存在,不允许作为 Phase 1 阻塞项。 + +## 2. 状态定义 + +测试 agent 只能使用以下状态: + +| 状态 | 含义 | +| --- | --- | +| PASS | 按本矩阵步骤执行,所有通过条件满足,并记录证据。 | +| FAIL | 环境可用,但功能行为不满足通过条件。 | +| BLOCKED | 因缺少密钥、外部服务不可用、账号/OAuth 未完成、测试数据缺失等环境问题无法执行。必须写清阻塞原因。 | +| N/A | 当前插件或平台明确不支持该能力。必须引用 manifest capability、文档或配置说明。 | + +不能使用“看起来正常”“大概通过”“未完全测试”等模糊状态。 + +## 3. 总体验收条件 + +Phase 1 可以关闭的最低条件: + +- 所有 P0 case 必须 PASS。 +- `local-agent` 的 P1 parity case 必须 PASS,除非该能力旧内置 runner 也不支持,此时可标 N/A。 +- 官方外部 runner smoke case 至少对已具备凭据和服务的插件 PASS;缺凭据的插件可标 BLOCKED,但必须保留配置页面截图或日志说明。 +- 没有会导致主聊天路径不可用、插件 runtime 崩溃、Pipeline 配置丢失、权限绕过的未解决 FAIL。 +- 所有 FAIL/BLOCKED 都必须记录复现步骤、日志位置、截图或请求/响应摘要。 + +推荐测试前先运行: + +```bash +uv run --frozen pytest tests/unit_tests/agent +``` + +Host 侧 agent runner 单测不通过时,不应进入 UI parity QA。 + +## 4. 证据要求 + +每个 case 至少记录: + +- LangBot commit、SDK commit、相关 runner 插件 commit。 +- Pipeline UUID/name、runner id、runner config 摘要。 +- WebUI 截图或浏览器操作记录。 +- 后端日志中对应 query id/run id 的关键行。 +- 对外部 runner,记录外部服务响应摘要或错误码。 + +用户可见流程必须通过 WebUI 或真实消息平台验证。API/curl 只能作为诊断证据,不能单独让 UI case PASS。 + +## 5. P0 环境与主链路 + +| ID | 场景 | 步骤 | 通过条件 | +| --- | --- | --- | --- | +| P0-ENV-01 | LangBot 服务可用 | 启动后端和前端,打开 WebUI。 | WebUI 可登录/访问;后端无启动异常;插件系统按配置启用。 | +| P0-ENV-02 | 插件 runtime 可用 | 查看插件列表或后端日志。 | runtime 已启动;官方 runner 插件处于可用状态;无循环重启。 | +| P0-ENV-03 | Runner registry 可发现插件 runner | 打开 Pipeline AI runner 配置。 | runner 下拉列表来自插件 registry;至少能看到 `plugin:langbot/local-agent/default`。 | +| P0-ENV-04 | 默认 Pipeline 可创建 | 新建 Pipeline 或读取默认 Pipeline。 | 默认配置使用 `ai.runner.id` 与 `ai.runner_config`;默认 runner 可保存。 | +| P0-ENV-05 | 主聊天路径调用插件 runner | 使用默认 `local-agent` Pipeline 发送一条普通消息。 | 后端日志显示走 `AgentRunOrchestrator` / `RUN_AGENT`;用户收到正常回复;旧内置 runner 不应作为主路径执行。 | +| P0-ENV-06 | 单测基线 | 运行 `uv run --frozen pytest tests/unit_tests/agent`。 | 全部通过;若失败,必须先修复或记录为 P0 FAIL。 | + +## 6. P1 local-agent parity + +`local-agent` 是 Phase 1 的主验收对象。以下 case 需要和旧内置 local-agent 的用户可见行为对齐。 + +| ID | 场景 | 步骤 | 通过条件 | +| --- | --- | --- | --- | +| P1-LA-01 | 普通文本对话 | 绑定 `plugin:langbot/local-agent/default`,发送普通文本。 | 回复正常生成;conversation history 写入用户消息和助手消息。 | +| P1-LA-02 | 有效 prompt | 配置 system prompt,并通过 PromptPreProcessing 插件或现有预处理改变 prompt。 | runner 使用 host 处理后的 `ctx.prompt`,不是只读取静态 `ctx.config.prompt`;回复体现有效 prompt。 | +| P1-LA-03 | 历史消息 | 连续多轮对话,第二轮引用第一轮内容。 | runner 可读到历史 `ctx.messages`;第二轮能基于上下文回答。 | +| P1-LA-04 | 流式输出 | 使用支持流式的 adapter/WebUI,开启流式模型或流式 runner。 | UI 逐步更新;后端接收 `message.delta`;最终没有重复消息或空白卡片。 | +| P1-LA-05 | 非流式输出 | 使用不支持流式或关闭流式的路径。 | 只输出最终消息;不会创建异常流式卡片。 | +| P1-LA-06 | 工具调用 | 绑定一个可调用工具,提问触发工具。 | `ctx.resources.tools` 只包含授权工具;runner 能获取工具详情并调用;最终回复包含工具结果。 | +| P1-LA-07 | 工具权限裁剪 | 不绑定某工具,但让 runner 尝试调用。 | 调用被拒绝;错误不泄露未授权工具详情;Pipeline 不崩溃。 | +| P1-LA-08 | RAG 检索 | 绑定知识库并提问命中文档。 | `ctx.resources.knowledge_bases` 包含所选知识库;runner 可检索;回复引用或使用检索内容。 | +| P1-LA-09 | RAG 权限裁剪 | 不绑定知识库或绑定另一个知识库。 | 未授权知识库不可检索;错误可控。 | +| P1-LA-10 | rerank | 绑定 rerank 模型并启用知识库检索排序。 | runner 可通过授权 rerank 模型排序;无权限时不允许调用。 | +| P1-LA-11 | fallback model | 配置 primary 和 fallback,模拟 primary 失败。 | fallback 被调用;用户得到可用回复或明确失败提示;日志能区分 primary/fallback。 | +| P1-LA-12 | remove-think | 开启输出 `remove-think`,使用会产生 think 内容的模型。 | 用户最终回复不包含被移除的 think 内容;插件 runner 走 runtime metadata 或 API 参数保持旧行为。 | +| P1-LA-13 | 多模态图片 | 发送图片输入。 | `ctx.input.contents` / `ctx.input.attachments` 保留图片;支持视觉模型时可正常处理;不支持时错误提示可控。 | +| P1-LA-14 | 文件输入 | 发送文件或文件 URL。 | runner 可看到文件 attachment 摘要;支持文件处理时正常处理;不支持时不崩溃。 | +| P1-LA-15 | 会话状态 | runner 返回 `state.updated`,下一轮继续对话。 | state 被 host 接收并作用于下一轮;conversation id 等兼容旧行为。 | +| P1-LA-16 | 异常处理 | 让 runner 返回 `run.failed` 或抛异常。 | ChatMessageHandler 使用 Pipeline 的异常策略;用户提示符合配置;runtime 和后续请求不受影响。 | +| P1-LA-17 | 无输出保护 | runner 完成但不返回消息。 | 不产生空白成功回复;应按受控失败处理或明确记录缺陷。 | + +## 7. P1 配置兼容与迁移 + +| ID | 场景 | 步骤 | 通过条件 | +| --- | --- | --- | --- | +| P1-CFG-01 | 读取旧配置 | 使用只包含 `ai.runner.runner = local-agent` 和旧 `ai.local-agent` 配置的 Pipeline。 | 能解析为 `plugin:langbot/local-agent/default`;旧配置值生效。 | +| P1-CFG-02 | 保存新配置 | 在 WebUI 修改 runner 和 runner config 后保存。 | 数据库存储 `ai.runner.id` 和 `ai.runner_config[id]`;刷新页面后不丢失。 | +| P1-CFG-03 | runner 切换 | 同一 Pipeline 从 local-agent 切到另一个官方 runner,再切回。 | 每个 runner 的绑定配置独立保存;切换不污染其它 runner config。 | +| P1-CFG-04 | 插件缺失 | 配置引用一个未安装或未启动的 runner。 | WebUI/后端给出可理解错误;Pipeline 不因 metadata 加载失败整体不可用。 | +| P1-CFG-05 | bound plugin 授权 | Pipeline 只绑定部分插件。 | 未绑定插件的 runner 不能执行;已绑定插件正常执行。 | + +## 8. P1 权限与隔离 + +| ID | 场景 | 步骤 | 通过条件 | +| --- | --- | --- | --- | +| P1-AUTH-01 | 模型权限 | runner 尝试调用不在 `ctx.resources.models` 的模型。 | Host action 拒绝;错误包含 run/session 维度信息;不会调用实际模型。 | +| P1-AUTH-02 | 工具权限 | runner 尝试调用不在 `ctx.resources.tools` 的工具。 | Host action 拒绝;不会越权执行工具。 | +| P1-AUTH-03 | 知识库权限 | runner 尝试检索不在 `ctx.resources.knowledge_bases` 的知识库。 | Host action 拒绝;不会返回未授权知识库内容。 | +| P1-AUTH-04 | 存储权限 | manifest 未声明 storage 权限时访问 plugin/workspace storage。 | 访问被拒绝;普通插件非 AgentRunner 的兼容路径不受影响。 | +| P1-AUTH-05 | run_id 生命周期 | runner 结束后继续使用旧 run_id 调 host action。 | session 已注销;请求被拒绝。 | +| P1-AUTH-06 | 插件身份隔离 | A 插件 runner 的 run_id 被 B 插件使用。 | Host 拒绝 identity mismatch。 | + +## 9. P2 官方外部 runner smoke + +以下 case 是 smoke,不要求和 local-agent 一样覆盖全部能力。若缺少外部服务凭据,状态标 BLOCKED,并记录缺失项。 + +| ID | Runner | 步骤 | 通过条件 | +| --- | --- | --- | --- | +| P2-EXT-01 | `dify-agent` | 配置 chat/agent/workflow 中至少一种可用应用并发送消息。 | runner 可选、配置可保存、请求成功或外部服务错误被清晰返回。 | +| P2-EXT-02 | `n8n-agent` | 配置 webhook 和认证方式并发送消息。 | webhook 被调用;返回内容进入 LangBot 回复;认证失败时提示明确。 | +| P2-EXT-03 | `coze-agent` | 配置 Coze 应用并发送文本,若可用再测图片。 | 文本回复正常;多模态能力按 manifest/配置表现;思维链处理不污染最终回复。 | +| P2-EXT-04 | `dashscope-agent` | 配置 agent 或 workflow 并发送消息。 | 调用成功;失败时错误可控且不影响后续请求。 | +| P2-EXT-05 | `langflow-agent` | 配置 flow endpoint 并发送消息。 | 普通或 SSE 流式响应能归一为 LangBot 消息。 | +| P2-EXT-06 | `tbox-agent` | 配置 Tbox 应用并发送消息。 | 回复正常;多模态输入按插件能力处理。 | + +## 10. P2 事件预留检查 + +这些只检查协议预留,不要求真实平台事件接入。 + +| ID | 场景 | 步骤 | 通过条件 | +| --- | --- | --- | --- | +| P2-EVT-01 | 消息事件名稳定 | 触发普通消息 runner。 | `ctx.trigger.type` 和 `ctx.event.event_type` 为 `message.received`;平台原始类型保存在 `ctx.event.event_data.source_event_type`。 | +| P2-EVT-02 | 非消息事件名预留 | 检查 host 侧保留事件名。 | `message.recalled`、`group.member_joined`、`friend.request_received` 作为稳定协议名存在。 | +| P2-EVT-03 | action.requested 预留 | 让测试 runner 返回 `action.requested`。 | Host 只记录日志,不执行平台动作,不影响主流程。 | + +## 11. 退出标准 + +QA agent 完成后应输出一份报告,至少包含: + +- 总状态:PASS / FAIL / BLOCKED。 +- 每个 case 的状态表。 +- 所有 FAIL 的复现步骤和建议归属仓库。 +- 所有 BLOCKED 的环境缺口。 +- 是否建议关闭 Phase 1,进入 Phase 2/EBA。 + +建议关闭 Phase 1 的条件: + +- P0 全 PASS。 +- P1 全 PASS,或只有旧内置 runner 同样不支持的 N/A。 +- P2 外部 runner smoke 对可用凭据全部 PASS。 +- 剩余问题均为 EBA 预留、外部服务凭据、或非阻塞体验问题。 diff --git a/docs/agent-runner-pluginization/PROGRESS.md b/docs/agent-runner-pluginization/PROGRESS.md index 4b259764..babacd29 100644 --- a/docs/agent-runner-pluginization/PROGRESS.md +++ b/docs/agent-runner-pluginization/PROGRESS.md @@ -12,7 +12,7 @@ | Phase 1 | 核心架构(Registry、Orchestrator、上下文模型) | ✅ 完成 | | Phase 2 | 权限、能力声明、资源注入 | ✅ 完成 | | Phase 3 | 内置 runner 迁移到插件 | ✅ 完成(7/7) | -| Phase 4 | EBA 事件支持 | 🔲 未开始(message event/actor/subject 上下文已预填充) | +| Phase 4 | EBA 事件支持 | 🔲 未开始(已预留稳定事件名,message event/actor/subject 上下文已预填充) | --- @@ -74,7 +74,7 @@ ### 低优先级 / 未来 -- [ ] EBA 完整集成 — message event/actor/subject 上下文已填充,完整事件路由与非消息事件仍待实现 +- [ ] EBA 完整集成 — 稳定事件名与 message event/actor/subject 上下文已预留,完整事件路由与非消息事件仍待实现 - [ ] 平台 API 动作执行 — `action.requested` 结果类型存在但未执行 --- @@ -91,5 +91,6 @@ ## 相关文档 - [README.md](./README.md) — 总体设计 +- [PHASE1_QA_ACCEPTANCE_MATRIX.md](./PHASE1_QA_ACCEPTANCE_MATRIX.md) — Phase 1 agent QA 验收矩阵 - [OFFICIAL_RUNNER_PLUGINS.md](./OFFICIAL_RUNNER_PLUGINS.md) — 官方插件仓库计划 - [IMPLEMENTATION_PLAN.md](./IMPLEMENTATION_PLAN.md) — 具体实施细节 diff --git a/docs/agent-runner-pluginization/README.md b/docs/agent-runner-pluginization/README.md index 5f6b0b00..e8678fa9 100644 --- a/docs/agent-runner-pluginization/README.md +++ b/docs/agent-runner-pluginization/README.md @@ -374,6 +374,40 @@ LangBot 执行前做三层裁剪: 因此文档和代码命名应避免把当前任务称为 EBA 实现。推荐使用 `agent-runner-pluginization`、`AgentRunContext`、`AgentRunResult` 等命名。 +### 7.1 现在必须预留的事件适配方式 + +后续消息撤回、群成员加入、新好友申请等事件不要再走“伪造一条用户文本消息”的方式接入 AgentRunner。正确方向是让未来 `EventRouter` 构造同一份 `AgentRunContext`,然后复用当前 `AgentRunOrchestrator` 的 registry、resource builder、result normalizer 和插件调用协议。 + +当前先固定这些公共协议约束: + +- 顶层 `ctx.event.event_type` 使用稳定协议名,不暴露 SDK 类名或平台原始事件名。 +- 平台原始事件名、平台 payload、适配器细节放进 `ctx.event.event_data`。 +- `ctx.input.text` 可以为空;runner 不能假设所有触发都是一段用户文本。 +- `ctx.actor` 表示触发动作的主体,`ctx.subject` 表示被操作或被关注的对象。 +- 需要平台动作时,runner 只能返回 `action.requested`;当前阶段只记录,真正执行等统一平台 API 和权限模型落地。 + +已预留的事件类型: + +| event_type | actor | subject | input | +| --- | --- | --- | --- | +| `message.received` | 发消息的人 | 当前消息 | 文本、图片、文件等消息内容 | +| `message.recalled` | 撤回操作者,未知时为系统 | 被撤回消息 | 通常为空,原消息摘要放 `event_data` | +| `group.member_joined` | 新成员或邀请人,按平台 payload 标明 | 群/成员关系 | 通常为空,可把欢迎上下文放 `event_data` | +| `friend.request_received` | 申请人 | 好友申请 | 验证消息或申请理由 | + +未来 EventRouter 的最小调用链应是: + +```text +Platform Adapter + -> EventRouter normalize platform payload + -> resolve event binding: event_type + bot/workspace/scope -> runner id + config + -> AgentRunOrchestrator.run_from_event(event_request) + -> AgentRunContextBuilder.build_context_from_event(event_request) + -> PluginRuntimeConnector.run_agent() +``` + +`run_from_event()` 不能重新实现一套 runner 调用逻辑,只能复用当前 `run_from_query()` 已经使用的 registry、资源裁剪、session registry、状态更新和结果归一化能力。这样 Pipeline 消息入口和 EBA 非消息入口不会分裂成两套协议。 + ## 8. 分阶段落地 ### Phase 1:整理当前分支 @@ -441,3 +475,7 @@ SDK: - 当前 runner 配置先跟 Pipeline 绑定,仍然在 Pipeline 的 AI runner stage 中执行;后续需要支持直接与 Bot 的事件触发器绑定。 - Pipeline/Event 绑定只保存 runner id 和绑定配置,不创建插件实例或 runner 实例;插件 runner 按无状态转发调用处理,跨请求状态必须显式存储。 - 内置 `RequestRunner` 最终强制迁移为插件形态,避免长期保留“内置 runner 分支”和“插件 runner 分支”两套执行体系。 + +## 12. QA 验收 + +Phase 1 收尾进入 agent QA 时,使用 [PHASE1_QA_ACCEPTANCE_MATRIX.md](./PHASE1_QA_ACCEPTANCE_MATRIX.md) 作为验收标准。该矩阵只验收 Agent Runner 插件化 parity,不验收 EBA EventBus、EventRouter 或平台动作执行。 diff --git a/src/langbot/pkg/agent/runner/__init__.py b/src/langbot/pkg/agent/runner/__init__.py index 1c3a17a4..986320c9 100644 --- a/src/langbot/pkg/agent/runner/__init__.py +++ b/src/langbot/pkg/agent/runner/__init__.py @@ -17,6 +17,13 @@ from .result_normalizer import AgentResultNormalizer from .orchestrator import AgentRunOrchestrator from .config_migration import ConfigMigration from .session_registry import AgentRunSessionRegistry, AgentRunSession, get_session_registry +from .events import ( + MESSAGE_RECEIVED, + MESSAGE_RECALLED, + GROUP_MEMBER_JOINED, + FRIEND_REQUEST_RECEIVED, + RESERVED_EVENT_TYPES, +) __all__ = [ 'AgentRunnerDescriptor', @@ -37,4 +44,9 @@ __all__ = [ 'AgentRunSessionRegistry', 'AgentRunSession', 'get_session_registry', -] \ No newline at end of file + 'MESSAGE_RECEIVED', + 'MESSAGE_RECALLED', + 'GROUP_MEMBER_JOINED', + 'FRIEND_REQUEST_RECEIVED', + 'RESERVED_EVENT_TYPES', +] diff --git a/src/langbot/pkg/agent/runner/context_builder.py b/src/langbot/pkg/agent/runner/context_builder.py index cfe7ce85..17e92de8 100644 --- a/src/langbot/pkg/agent/runner/context_builder.py +++ b/src/langbot/pkg/agent/runner/context_builder.py @@ -12,6 +12,7 @@ from ...core import app from .descriptor import AgentRunnerDescriptor from .config_migration import ConfigMigration from .state_store import get_state_store +from . import events as runner_events # Internal models for SDK v1 context protocol matching SDK v1 resources.py @@ -183,7 +184,7 @@ class AgentRunContextBuilder: # Build trigger trigger: AgentTrigger = { - 'type': 'message.received', + 'type': runner_events.MESSAGE_RECEIVED, 'source': 'pipeline', 'timestamp': int(time.time()), } @@ -407,7 +408,12 @@ class AgentRunContextBuilder: return default def _build_event(self, query: pipeline_query.Query) -> dict[str, typing.Any]: - """Build a minimal event envelope from the platform message event.""" + """Build a minimal EBA-compatible event envelope from the message query. + + The public event_type must be a stable AgentRunner protocol name. Keep + platform or SDK class names inside event_data so future EventRouter + events can share the same top-level naming contract. + """ message_event = getattr(query, 'message_event', None) event_data: dict[str, typing.Any] = {} @@ -420,6 +426,10 @@ class AgentRunContextBuilder: event_data = {} event_data.pop('source_platform_object', None) + source_event_type = getattr(message_event, 'type', None) if message_event else None + if source_event_type: + event_data.setdefault('source_event_type', source_event_type) + message_chain = getattr(query, 'message_chain', None) message_id = getattr(message_chain, 'message_id', None) if message_id == -1: @@ -429,7 +439,7 @@ class AgentRunContextBuilder: event_timestamp = int(event_time) if isinstance(event_time, (int, float)) else None return { - 'event_type': getattr(message_event, 'type', None) or 'message.received', + 'event_type': runner_events.MESSAGE_RECEIVED, 'event_id': str(message_id or getattr(query, 'query_id', '')), 'event_timestamp': event_timestamp, 'event_data': event_data, diff --git a/src/langbot/pkg/agent/runner/events.py b/src/langbot/pkg/agent/runner/events.py new file mode 100644 index 00000000..53ea266e --- /dev/null +++ b/src/langbot/pkg/agent/runner/events.py @@ -0,0 +1,25 @@ +"""Canonical AgentRunner event names reserved for future EBA integration.""" +from __future__ import annotations + + +MESSAGE_RECEIVED = 'message.received' +"""A normal message entered the current Pipeline.""" + +MESSAGE_RECALLED = 'message.recalled' +"""A platform message was recalled or deleted.""" + +GROUP_MEMBER_JOINED = 'group.member_joined' +"""A new member joined a group/channel conversation.""" + +FRIEND_REQUEST_RECEIVED = 'friend.request_received' +"""A new friend/contact request was received.""" + + +RESERVED_EVENT_TYPES = frozenset( + { + MESSAGE_RECEIVED, + MESSAGE_RECALLED, + GROUP_MEMBER_JOINED, + FRIEND_REQUEST_RECEIVED, + } +) diff --git a/tests/unit_tests/agent/test_orchestrator_integration.py b/tests/unit_tests/agent/test_orchestrator_integration.py index 68bb927c..4e9bea95 100644 --- a/tests/unit_tests/agent/test_orchestrator_integration.py +++ b/tests/unit_tests/agent/test_orchestrator_integration.py @@ -281,7 +281,8 @@ async def test_orchestrator_runs_fake_plugin_with_authorized_context(): assert context["config"]["timeout"] == 30 assert context["runtime"]["deadline_at"] is not None assert context["params"] == {"public_param": "visible"} - assert context["event"]["event_type"] == "FriendMessage" + assert context["event"]["event_type"] == "message.received" + assert context["event"]["event_data"]["source_event_type"] == "FriendMessage" assert context["actor"]["actor_id"] == "user_001" assert context["actor"]["actor_name"] == "Alice" assert context["subject"]["subject_id"] == "msg_001"