diff --git a/internal/acp/orchestrator.go b/internal/acp/orchestrator.go index da33adb..4de7330 100644 --- a/internal/acp/orchestrator.go +++ b/internal/acp/orchestrator.go @@ -613,13 +613,19 @@ func (o *SessionOrchestrator) openClawArtifactPrepare( func isOpenClawUnknownMethodError(errorPayload map[string]any, method string) bool { message := strings.ToLower(strings.TrimSpace(shared.StringArg(errorPayload, "message", ""))) - code := strings.ToUpper(strings.TrimSpace(shared.StringArg(errorPayload, "code", ""))) if message == "" { return false } + // 消息形如「unknown method: 」已明确指向「网关不认识该方法」,足以判定, + // 据此走 graceful fallback(如 openClawFallbackSessionPreparePayload)。 + // + // 注意:不能再用严格的 code 白名单来 gate。真实网关常以数字 JSON-RPC code + // (-32601 method not found / -32600 invalid request / -32002 等) 回传, + // 经 shared.StringArg(fmt.Sprint) 会被字符串化为 "-32601"/"-32002", + // 旧实现只接受 {"", INVALID_REQUEST, METHOD_NOT_FOUND},导致 fallback 失效、 + // session.prepare 直接以 -32002 硬失败整轮任务。 return strings.Contains(message, "unknown method") && - strings.Contains(message, strings.ToLower(strings.TrimSpace(method))) && - (code == "" || code == "INVALID_REQUEST" || code == "METHOD_NOT_FOUND") + strings.Contains(message, strings.ToLower(strings.TrimSpace(method))) } func openClawFallbackSessionPreparePayload(params map[string]any) map[string]any { diff --git a/internal/acp/orchestrator_normalize_result_test.go b/internal/acp/orchestrator_normalize_result_test.go index e4e9372..4536dd5 100644 --- a/internal/acp/orchestrator_normalize_result_test.go +++ b/internal/acp/orchestrator_normalize_result_test.go @@ -282,3 +282,50 @@ func TestTaskGetArtifactExportReceivesRequiredArtifactExtensions(t *testing.T) { t.Fatalf("expected expectedFileCountByExtension to reach export, got %#v", exportParams) } } + +func TestIsOpenClawUnknownMethodErrorAcceptsNumericGatewayCodes(t *testing.T) { + const method = "xworkmate.session.prepare" + cases := []struct { + name string + payload map[string]any + want bool + }{ + { + name: "string invalid_request code", + payload: map[string]any{"code": "INVALID_REQUEST", "message": "unknown method: xworkmate.session.prepare"}, + want: true, + }, + { + name: "numeric -32002 (real gateway shape that previously hard-failed)", + payload: map[string]any{"code": float64(-32002), "message": "unknown method: xworkmate.session.prepare"}, + want: true, + }, + { + name: "numeric -32601 method not found", + payload: map[string]any{"code": float64(-32601), "message": "Unknown method: xworkmate.session.prepare"}, + want: true, + }, + { + name: "empty code", + payload: map[string]any{"message": "unknown method: xworkmate.session.prepare"}, + want: true, + }, + { + name: "unrelated error must not be swallowed", + payload: map[string]any{"code": float64(-32002), "message": "gateway socket closed"}, + want: false, + }, + { + name: "unknown method for a different method name", + payload: map[string]any{"code": float64(-32601), "message": "unknown method: chat.send"}, + want: false, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + if got := isOpenClawUnknownMethodError(tc.payload, method); got != tc.want { + t.Fatalf("isOpenClawUnknownMethodError(%v) = %v, want %v", tc.payload, got, tc.want) + } + }) + } +}