Checklist
🐞 问题详细描述
1. 问题概述
ReactAgent webhook 验收发现:默认 profile 的 Agent Card 正确声明 pushNotifications=false,固定 callback receiver也返回未启用响应;但调用方仍可在 SendMessage.params.pushNotificationConfig 中携带 callback 配置,请求返回 JSON-RPC result 并创建异步 Task,而不是在 Task 创建和 Agent 执行前拒绝。
代码核查确认,A2aPushNotificationCapabilityGate 当前只用于 Agent Card 和 callback receiver,没有用于 /a2a 的 inline pushconfig ingress。parser 仍会无条件保存 callback 配置并设置 returnImmediately=true,HTTP sender 和 config store 也不受push-notifications=false 控制。
这导致同一 runtime 对外出现不一致事实:Card 表示能力关闭、receiver 表示能力关闭,但 SendMessage 仍接受并启动 callback-mode异步执行。
2. 当前 runtime 代码实现
2.1 capability gate 能正确计算能力状态
文件:
service/agent-service-app/src/main/java/com/openjiuwen/service/app/controller/a2a/A2aPushNotificationCapabilityGate.java
public boolean isPushNotificationsEnabled() {
return properties.isPushNotifications()
&& sender instanceof HttpPushNotificationSender
&& callbackStore != null
&& callbackHandler != null
&& !(callbackHandler instanceof NoOpA2aPushNotificationCallbackHandler);
}
该 gate 会检查部署开关和 callback 依赖是否完整。
2.2 gate 只用于 Card 和 callback receiver
当前使用点包括:
AgentCardController:决定 Card 中的 pushNotifications;
A2aPushNotificationCallbackController:未启用时返回 501 Not Implemented。
A2aJsonRpcController 没有注入或调用该 gate。
2.3 parser 无条件启用异步接受语义
文件:
service/agent-service-app/src/main/java/com/openjiuwen/service/app/controller/a2a/A2aJsonRpcParamsParser.java
if (inlinePushConfig != null && !inlinePushConfig.isJsonNull()) {
builder.taskPushNotificationConfig(
parseTaskPushNotificationConfig(inlinePushConfig,
"params.pushNotificationConfig"));
builder.returnImmediately(true);
}
这里没有检查 A2AProperties.isPushNotifications() 或 capability gate。
2.4 controller 只校验 URL policy
文件:
service/agent-service-app/src/main/java/com/openjiuwen/service/app/controller/a2a/A2aJsonRpcController.java
SendMessage 当前执行:
var params = A2aJsonRpcParamsParser.parseMessageSendParams(request.payload());
validateInlinePushNotificationConfig(params);
EventKind result = requestHandler.onMessageSend(params, ctx);
validateInlinePushNotificationConfig() 只调用:
A2aPushNotificationCallbackUrlPolicy.validateCallbackUrl(...);
只要 URL 满足信任策略,即使 capability 为 false,请求仍进入 SDK handler。
2.5 sender 和 config store 无条件装配
文件:
service/agent-service-app/src/main/java/com/openjiuwen/service/app/autoconfigure/A2AAutoConfiguration.java
当前无条件创建:
new InMemoryPushNotificationConfigStore();
new HttpPushNotificationSender(pushConfigStore);
这意味着 push-notifications=false 时,底层 inline config 存储和 HTTP sender 仍然存在,SDK 仍可进入 callback-mode 执行。
2.6 core 不应参与门控
日志显示请求已经进入 JiuwenCoreAgentHandler 和 ReActAgent,说明拒绝发生得太晚或完全缺失。是否允许 callback 是 A2A ingress
配置决策,agent-core-java 不知道也不应知道 pushNotifications 配置,本问题与 core 实现无关。
3. 当前实现与设计的冲突
| 对比项 |
文档预期 |
当前实现/测试表现 |
| Agent Card |
关闭时声明 false |
符合 |
| 固定 callback receiver |
关闭时不暴露或返回未启用 |
符合,返回 404/501 |
| inline callback ingress |
关闭时不应接受 callback-mode 请求 |
仍返回 result 并创建 Task |
| push config 存储 |
拒绝请求时无副作用 |
parser/SDK 仍可绑定配置 |
| Agent 执行 |
门控拒绝应发生在业务执行前 |
已进入 ReactAgent/LLM |
| 能力事实一致性 |
Card、receiver、sender ingress 使用统一门控 |
gate 未用于 SendMessage ingress |
4. 建议修改方向
4.0 修改项关系说明
本问题的核心必改项只有一条:在 requestHandler.onMessageSend() 和 Task 创建之前,用统一 capability gate 拒绝关闭状态下的inline push config。其余示例用于代码组织、错误类型选择或防御性加强,不要求全部实施。
| 修改项 |
是否必须 |
关系说明 |
| 4.1 ingress 在 Task 创建前调用 capability gate |
必须 |
修复本问题的核心,不可只修改 Card 或 receiver |
InvalidParamsError 错误类型 |
可选实现 |
可以换成设计认可的 capability-disabled/unsupported A2A error,但必须返回 JSON-RPC error 且无副作用 |
4.2 给 gate 增加 requirePushNotificationsEnabled() |
可选重构 |
与直接调用 isPushNotificationsEnabled() 二选一,用于减少重复判断 |
| 4.3 固定关闭时错误表面 |
必须确定契约 |
具体错误码可选,但开发、设计和测试必须一致 |
| 4.4 条件装配/NoOp sender |
可选防御性方案 |
不能替代 8.1 ingress 门控;可在核心修复后进一步降低误投递风险 |
| 4.5 模块测试 |
必须 |
必须证明 handler、Task/config store 和 sender 均无副作用 |
推荐最小落地组合是:4.1 直接注入并调用现有 gate + 确定 4.3 错误表面 + 完成 4.5 测试。
若希望进一步统一代码,可把 4.1 中的布尔判断重构为 4.2 的 requirePushNotificationsEnabled();两种写法不需要同时保留。4.4 的 NoOp sender 是额外防线,不是本问题通过验收的前置条件。
4.1 在 Task 创建前统一门控
建议 A2aJsonRpcController 或统一的 SendMessage callback validation service 使用A2aPushNotificationCapabilityGate.isPushNotificationsEnabled()。
当请求包含 inline push config 且 gate 为 false 时,应在调用 requestHandler.onMessageSend() 前返回标准错误,确保无 Task、无配置、无Agent 执行和无 callback。
必改方向是把 capability gate 用于 A2aJsonRpcController 的 ingress。下面展示“直接调用现有 gate”的推荐最小示例:
public class A2aJsonRpcController {
private final RequestHandler requestHandler;
private final A2aPushNotificationCapabilityGate pushNotificationCapabilityGate;
public A2aJsonRpcController(RequestHandler requestHandler,
A2aPushNotificationCapabilityGate pushNotificationCapabilityGate) {
this.requestHandler = requestHandler;
this.pushNotificationCapabilityGate = pushNotificationCapabilityGate;
}
private void validateInlinePushNotificationConfig(MessageSendParams params) {
if (params == null || params.configuration() == null
|| params.configuration().taskPushNotificationConfig() == null) {
return;
}
if (!pushNotificationCapabilityGate.isPushNotificationsEnabled()) {
throw new InvalidParamsError(
"Invalid params: push notification capability is not enabled");
}
A2aPushNotificationCallbackUrlPolicy.validateCallbackUrl(
params.configuration().taskPushNotificationConfig());
}
}
现有 controller 已捕获 A2AError 并转换为 JSON-RPC error,因此 InvalidParamsError 会在调用requestHandler.onMessageSend() 前返回,不创建 Task。若团队希望区分“参数非法”和“能力关闭”,也可新增 SDK 允许的capability-disabled/unsupported error;关键是保持标准 JSON-RPC error 且无副作用。
对应 unit test 应同时验证 handler 没有被调用:
@Test
void disabledPushCapabilityRejectsInlineConfigBeforeTaskCreation() {
RequestHandler requestHandler = mock(RequestHandler.class);
A2aPushNotificationCapabilityGate gate = mock(A2aPushNotificationCapabilityGate.class);
when(gate.isPushNotificationsEnabled()).thenReturn(false);
A2aJsonRpcController controller = new A2aJsonRpcController(requestHandler, gate);
ResponseEntity<?> response = controller.handleJsonRpc(
sendMessageWithInlinePushConfig(), servletRequest());
assertThat(response.getBody().toString()).contains("error").contains("-32602");
verifyNoInteractions(requestHandler);
}
测试辅助方法名需按现有 A2aJsonRpcControllerTest 调整;核心断言是 verifyNoInteractions(requestHandler)。
4.2 统一 Card、receiver 和 ingress 的能力事实
同一个 capability 判定应同时约束:
- Agent Card 是否声明
pushNotifications=true;
- 固定 callback receiver 是否接受请求;
SendMessage 是否接受 inline push config;
- sender 是否允许实际投递。
避免出现 Card false 但发送能力仍可使用,或 Card true 但入口不可用的状态。
以下为可选重构:为避免以后不同入口各自复制判断,可以给 gate 增加统一校验方法。采用该写法后,4.1 示例中的显式布尔判断应替换掉,不需要两段判断同时保留:
public void requirePushNotificationsEnabled() {
if (!isPushNotificationsEnabled()) {
throw new InvalidParamsError(
"Invalid params: push notification capability is not enabled");
}
}
controller 使用:
if (hasInlinePushNotificationConfig(params)) {
pushNotificationCapabilityGate.requirePushNotificationsEnabled();
A2aPushNotificationCallbackUrlPolicy.validateCallbackUrl(
params.configuration().taskPushNotificationConfig());
}
receiver 仍可根据 HTTP endpoint 契约把 gate false 映射为 404/501,不必强行与 JSON-RPC ingress 使用相同 HTTP status。
4.3 明确关闭时的标准错误表面
建议在设计和实现中固定为 invalid params、capability disabled 或等价 unsupported error。HTTP 可以保持 JSON-RPC over HTTP 的正常传输语义,关键是 response 必须包含 JSON-RPC error,并且没有业务副作用。
4.4 评估条件装配或 NoOp sender
可选择让 sender/config store 条件装配,或者保持 bean 存在但在统一 gate 下拒绝使用。无论选择哪种实现,不能只依赖 Agent Card隐藏能力。
本项为可选防御性方案。如果希望从 bean 层进一步避免关闭状态下存在真实 HTTP sender,可以使用条件装配并提供 NoOp 实现。以下为示意代码:
@Bean
@ConditionalOnProperty(
prefix = "openjiuwen.service.a2a",
name = "push-notifications",
havingValue = "true")
@ConditionalOnMissingBean(PushNotificationSender.class)
public PushNotificationSender a2aHttpPushNotificationSender(
PushNotificationConfigStore pushConfigStore) {
return new HttpPushNotificationSender(pushConfigStore);
}
@Bean
@ConditionalOnMissingBean(PushNotificationSender.class)
public PushNotificationSender a2aNoOpPushNotificationSender() {
return (event, task) -> {
// capability disabled: no delivery
};
}
当前 gate 已要求 sender 是 HttpPushNotificationSender,因此 NoOp sender 会自然使 Card capability 为 false。需要注意 Spring条件 bean 的声明顺序和 @ConditionalOnMissingBean 评估时机,实际实现可以用独立配置类或显式 NoOp class 避免装配歧义。
bean 条件装配只能作为防御性措施,不能替代 ingress 拒绝:否则请求仍可能被 parser/SDK 接受并创建永远不会回调的异步 Task。
4.5 补充 runtime 模块测试
建议覆盖:
- 配置 false + inline config:拒绝且不调用 executor;
- 配置 true 但 handler/store/sender 不完整:Card false 且 ingress 拒绝;
- 完整 callback profile:Card true 且合法 inline config accepted;
- 不可信 URL:能力开启时仍按 trust policy 拒绝;
- gate false 时 config store 无新增记录、sender 无调用。
详细的环境信息描述
本次定位代码版本:
agent-runtime-java: develop / 6afd64f
agent-core-java: 730 / 921b4bde
其他辅助信息
版本信息
感谢您的贡献 🎉!
Checklist
🐞 问题详细描述
1. 问题概述
ReactAgent webhook 验收发现:默认 profile 的 Agent Card 正确声明
pushNotifications=false,固定 callback receiver也返回未启用响应;但调用方仍可在SendMessage.params.pushNotificationConfig中携带 callback 配置,请求返回 JSON-RPCresult并创建异步 Task,而不是在 Task 创建和 Agent 执行前拒绝。代码核查确认,
A2aPushNotificationCapabilityGate当前只用于 Agent Card 和 callback receiver,没有用于/a2a的 inline pushconfig ingress。parser 仍会无条件保存 callback 配置并设置returnImmediately=true,HTTP sender 和 config store 也不受push-notifications=false控制。这导致同一 runtime 对外出现不一致事实:Card 表示能力关闭、receiver 表示能力关闭,但 SendMessage 仍接受并启动 callback-mode异步执行。
2. 当前 runtime 代码实现
2.1 capability gate 能正确计算能力状态
文件:
service/agent-service-app/src/main/java/com/openjiuwen/service/app/controller/a2a/A2aPushNotificationCapabilityGate.java该 gate 会检查部署开关和 callback 依赖是否完整。
2.2 gate 只用于 Card 和 callback receiver
当前使用点包括:
AgentCardController:决定 Card 中的pushNotifications;A2aPushNotificationCallbackController:未启用时返回501 Not Implemented。A2aJsonRpcController没有注入或调用该 gate。2.3 parser 无条件启用异步接受语义
文件:
service/agent-service-app/src/main/java/com/openjiuwen/service/app/controller/a2a/A2aJsonRpcParamsParser.java这里没有检查
A2AProperties.isPushNotifications()或 capability gate。2.4 controller 只校验 URL policy
文件:
service/agent-service-app/src/main/java/com/openjiuwen/service/app/controller/a2a/A2aJsonRpcController.javaSendMessage当前执行:validateInlinePushNotificationConfig()只调用:只要 URL 满足信任策略,即使 capability 为 false,请求仍进入 SDK handler。
2.5 sender 和 config store 无条件装配
文件:
service/agent-service-app/src/main/java/com/openjiuwen/service/app/autoconfigure/A2AAutoConfiguration.java当前无条件创建:
这意味着
push-notifications=false时,底层 inline config 存储和 HTTP sender 仍然存在,SDK 仍可进入 callback-mode 执行。2.6 core 不应参与门控
日志显示请求已经进入
JiuwenCoreAgentHandler和 ReActAgent,说明拒绝发生得太晚或完全缺失。是否允许 callback 是 A2A ingress配置决策,agent-core-java 不知道也不应知道
pushNotifications配置,本问题与 core 实现无关。3. 当前实现与设计的冲突
4. 建议修改方向
4.0 修改项关系说明
本问题的核心必改项只有一条:在
requestHandler.onMessageSend()和 Task 创建之前,用统一 capability gate 拒绝关闭状态下的inline push config。其余示例用于代码组织、错误类型选择或防御性加强,不要求全部实施。InvalidParamsError错误类型requirePushNotificationsEnabled()isPushNotificationsEnabled()二选一,用于减少重复判断推荐最小落地组合是:4.1 直接注入并调用现有 gate + 确定 4.3 错误表面 + 完成 4.5 测试。
若希望进一步统一代码,可把 4.1 中的布尔判断重构为 4.2 的
requirePushNotificationsEnabled();两种写法不需要同时保留。4.4 的 NoOp sender 是额外防线,不是本问题通过验收的前置条件。4.1 在 Task 创建前统一门控
建议
A2aJsonRpcController或统一的 SendMessage callback validation service 使用A2aPushNotificationCapabilityGate.isPushNotificationsEnabled()。当请求包含 inline push config 且 gate 为 false 时,应在调用
requestHandler.onMessageSend()前返回标准错误,确保无 Task、无配置、无Agent 执行和无 callback。必改方向是把 capability gate 用于
A2aJsonRpcController的 ingress。下面展示“直接调用现有 gate”的推荐最小示例:现有 controller 已捕获
A2AError并转换为 JSON-RPC error,因此InvalidParamsError会在调用requestHandler.onMessageSend()前返回,不创建 Task。若团队希望区分“参数非法”和“能力关闭”,也可新增 SDK 允许的capability-disabled/unsupported error;关键是保持标准 JSON-RPC error 且无副作用。对应 unit test 应同时验证 handler 没有被调用:
测试辅助方法名需按现有
A2aJsonRpcControllerTest调整;核心断言是verifyNoInteractions(requestHandler)。4.2 统一 Card、receiver 和 ingress 的能力事实
同一个 capability 判定应同时约束:
pushNotifications=true;SendMessage是否接受 inline push config;避免出现 Card false 但发送能力仍可使用,或 Card true 但入口不可用的状态。
以下为可选重构:为避免以后不同入口各自复制判断,可以给 gate 增加统一校验方法。采用该写法后,4.1 示例中的显式布尔判断应替换掉,不需要两段判断同时保留:
controller 使用:
receiver 仍可根据 HTTP endpoint 契约把 gate false 映射为 404/501,不必强行与 JSON-RPC ingress 使用相同 HTTP status。
4.3 明确关闭时的标准错误表面
建议在设计和实现中固定为 invalid params、capability disabled 或等价 unsupported error。HTTP 可以保持 JSON-RPC over HTTP 的正常传输语义,关键是 response 必须包含 JSON-RPC error,并且没有业务副作用。
4.4 评估条件装配或 NoOp sender
可选择让 sender/config store 条件装配,或者保持 bean 存在但在统一 gate 下拒绝使用。无论选择哪种实现,不能只依赖 Agent Card隐藏能力。
本项为可选防御性方案。如果希望从 bean 层进一步避免关闭状态下存在真实 HTTP sender,可以使用条件装配并提供 NoOp 实现。以下为示意代码:
当前 gate 已要求 sender 是
HttpPushNotificationSender,因此 NoOp sender 会自然使 Card capability 为 false。需要注意 Spring条件 bean 的声明顺序和@ConditionalOnMissingBean评估时机,实际实现可以用独立配置类或显式 NoOp class 避免装配歧义。bean 条件装配只能作为防御性措施,不能替代 ingress 拒绝:否则请求仍可能被 parser/SDK 接受并创建永远不会回调的异步 Task。
4.5 补充 runtime 模块测试
建议覆盖:
详细的环境信息描述
本次定位代码版本:
其他辅助信息
版本信息
感谢您的贡献 🎉!